SynchronizedlockvolatileThreadLocal原子性总结Condition
Posted 自自然卷
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SynchronizedlockvolatileThreadLocal原子性总结Condition相关的知识,希望对你有一定的参考价值。
1. Synchronized
首先Synchronized锁住的是对象
1.1 互斥:两个线程去调用同一对象的synchronized代码块,只有一个线程能够获得锁,另个线程会被阻塞
1.2 共享:类中有多个Synchronized代码块,这些代码块共享对象上同一个锁,即类中有Synchronized方法f()、g(),如果有一个线程获得了对象上的锁,调用f()方法,另个线程调用该对象g()需要等待前一个线程执行完f()函数
1.3 可重入性:JVM跟踪对象被加锁的次数,同一个线程可以多次获得对象上的锁,线程给对象加锁时计数加1,当线程退出Synchronized代码块时计数减1,只有当计数减为0时其他线程才能调用该线程的Synchronized方法
参考《Thinking in Java》
以下为猜想:
synchronized实现原理:1.Synchronize是个对象加锁,所以对象上有个标志位,即对象锁,当标志位为0表示对象已经资源被占用不能使用,当标志位为1表示对象可以使用,在调用结束后
2. volatile与原子性
首先线程公有的变量:实例字段、静态字段等才有线程安全问题。
对计算机来说,为了提高效率在内存和处理器之间加入了高速缓存cache,运算结束后将运算结果从缓存同步回内存中。JVM也去定义了自己的内存模型来屏蔽各种硬件和操作系统内存访问的差异。JVM利用了硬件的各种特性(寄存器、高速缓存和指令集中某些特定指令)来获取更好的执行速度。
Java内存模型规定了所有变量都存储在主内存,但是对于变量的操作(读取、赋值)即计算过程都在线程的工作内存中进行,在线程的工作内存中保存了该线程使用到的变量的主内存副本(如果是主内存的一个对象,不会一次把整个对象拷贝一次,该线程访问到的对象的字段可能被拷贝)。
工作内存差不多对应于虚拟机栈中的部分。
Java线程 <----------------------->工作内存<--------------------> Save
Java线程 <----------------------->工作内存<--------------------> 和Load <----------------> 主内存
Java线程 <----------------------->工作内存<--------------------> 操作
普通变量:A线程修改普通变量的值,线程B只有在线程A将普通变量写回主内存
volatile变量:对volatile变量的修改操作能立即反映到其他线程中,线程在读/使用volatile变量时候,必须经过 从主内存读取数据并保存到线程工作区 -> 使用线程工作区变量的过程,每次使用volatile变量必须从主内存刷新最新值;在修改volatile变量时必须经过 在线程工作区修改 -> 回写到主内存,每次修改变量后必须立刻同步回主内存。
volatile变量与普通变量的区别:在使用volatile变量时必须先从主内存读取,而普通变量可能是使用线程工作区的变量,比如下面的程序片段,如果stop是普通变量,那么除第一次从主内存读取stop变量后的每次循环可能都是使用线程工作区中保存的副本,而其他线程修改stop时这个线程不能立即感应到。volatile每次使用时都必须从主内存中读取,就不存在这样的问题。但是在线程工作区已经保存有普通变量副本时,是否在每次使用该变量时候一定使用该副本?以下是例程1、2:
例程1.
public class Main{ static boolean stop; public static void main(String[] args) throws InterruptedException{ Thread t1 = new Thread(){ int i = 0; public void run(){ while(!stop){ // System.out.println(); } } }; t1.start(); TimeUnit.SECONDS.sleep(1); stop = true; } }
例1程序中线程t1无法停止,因为程序被编译成
if(!stop){ while(true){} }参考《Effective Java》
例程2.
public class Main{ static boolean stop; public static void main(String[] args) throws InterruptedException{ Thread t1 = new Thread(){ int i = 0; public void run(){ while(!stop){ System.out.println(); } } }; t1.start(); TimeUnit.SECONDS.sleep(1); stop = true; } }
此处加了System.out.println()在我的机器上成功停止了线程运行,那么可以确定程序没有被编译成例程1那样。所以while(!stop)时在使用stop变量是否一定每次都从主内存复制stop到工作内存?如果是,那普通变量和volatile还有区别吗?
public class Main{ static volatile boolean stop; public static void main(String[] args) throws InterruptedException{ Thread t1 = new Thread(){ int i = 0; public void run(){ while(!stop){ System.out.println(++i); } } }; t1.start(); TimeUnit.SECONDS.sleep(1); stop = true; } }
public class Main{ static boolean stop; public static void main(String[] args) throws InterruptedException{ Thread t1 = new Thread(){ int i = 0; public void run(){ while(!stop){ System.out.println(++i); } } }; t1.start(); TimeUnit.SECONDS.sleep(1); stop = true; } }
上面两段程序运行输出i的最终值并没有显然的差别,所以似乎在此时volatile变量和普通变量并没有区别。参考下面两段程序:
public class Main{ static boolean stop; public static void main(String[] args) throws InterruptedException{ Thread t1 = new Thread(){ int i = 0; public void run(){ while(!stop){ } } }; t1.start(); TimeUnit.SECONDS.sleep(1); stop = true; } }
public class Main{ static volatile boolean stop; public static void main(String[] args) throws InterruptedException{ Thread t1 = new Thread(){ int i = 0; public void run(){ while(!stop){ } } }; t1.start(); TimeUnit.SECONDS.sleep(1); stop = true; } }加上volatile后线程t1在大约1s后停止了,而不加volatile线程t1不会停止,原因在上面已论述。所以volatile的一个作用是可以禁止指令重排序。参考《深入理解Java虚拟机》。
另一个问题:对volatile变量而言,修改变量总是要经过修改副本的值然后刷新到主内存两个步骤,那么这两个步骤是否不可被中断?如果可以被中断那么就不能立即刷新到主存。猜测是不能被中断的,两个操作合并到一起,是原子操作。
volatile还有更高级的特性,可参考 http://tutorials.jenkov.com/java-concurrency/volatile.html
里面介绍 Happens-Before Guarantee特点。
参考《Thinking in Java》、《深入理解Java虚拟机》
此外,同步会导致向主存刷新。
原子操作:一旦操作开始,那么它一定不会在可能发生的上下文切换之前(切换到其他线程执行)执行完毕。基本类型的读写操作都是原子的,除了long和double类型,JVM会将64位long、double变量的读写分为两个32位操作来执行。
volatile和原子性:
Java中count++这样的操作不是原子操作,即使将count设置为volatile变量也不能在多线程中保证安全性,简单例子就是多个线程都去执行count++,并打印出来,并且count为volatile变量,但是打印出来count并不会是递增的。如果希望打印出的count是递增的必须将count++加上同步操作。
3. ThreadLocal
先上链接 http://tutorials.jenkov.com/java-concurrency/threadlocal.html 中的Example:
public class ThreadLocalExample { public static class MyRunnable implements Runnable { private ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(); @Override public void run() { threadLocal.set( (int) (Math.random() * 100D) ); try { Thread.sleep(2000); } catch (InterruptedException e) { } System.out.println(threadLocal.get()); } } public static void main(String[] args) { MyRunnable sharedRunnableInstance = new MyRunnable(); Thread thread1 = new Thread(sharedRunnableInstance); Thread thread2 = new Thread(sharedRunnableInstance); thread1.start(); thread2.start(); thread1.join(); //wait for thread 1 to terminate thread2.join(); //wait for thread 2 to terminate } }
ThreadLocal是一个类,利用它的set、get方法可以在不同线程中都去使用同一个对象的副本。如果多个线程希望使用相同的初始值,但是又不希望在不同线程中相互干扰,这时可以使用ThreadLocal类。
4. Lock & unLock
5. Condition
额外的知识点:CPU动态调度
参考<<程序员的自我修养 链接、装载与库>>1.6节
先看下面的单例模式代码,书中为C++代码:
volatile T* pInst = 0; T* GetInstance(){ if(pInst == NULL){ lock(); if(pInst == NULL){ pInst = new T; } unlock(); } return pInst; }
C++里new包含两个步骤:
1. 分配内存
2. 调用构造函数
所以pInst = new T包含三个步骤:
1. 分配内存
2. 在内存位置上调用构造函数
3. 将内存的地址赋值给pInst
但2,3两个步骤顺序可以颠倒,这样会造成一种情况:pInst已经不是NULL,但对象仍未构造完毕,这时如果有另个线程调用GetInstance那么该线程执行到第一个if语句时判断不满足条件而直接执行return pInst,这样该线程将获得pInst地址,但其实此时pInst对象并未执行完构造函数,如果此线程后续还使用pInst则会造成错误。
以上问题的根本原因是cpu乱序执行了步骤2,3
很早写的文章没完成,今天补补坑,回头继续补坑
以上是关于SynchronizedlockvolatileThreadLocal原子性总结Condition的主要内容,如果未能解决你的问题,请参考以下文章