Java多线程内存可见性实现 synchronized (java 学习笔记)
这只是我自学JAVA的小小笔记。学习于网上资源(百度,csdn,慕课网),不足之处,请多指教。
在多线程机制中,我们不能避免的一个重要问题就是线程之间的数据争用,就像一个工厂里的两条生产线,都要对生产线出来的产品总量数进行修改,在某一时刻,两个产品同时从生产线出来,但是产品数目这个变量只有一个,所以更新过程必须得分先后进行。换言之就像先把这个变量的操作权限给一条生产线,修改完了再释放给另一条生产线用。Java中的解决方案通常使用synchronized 和volatile关键字对线程进行管理。
java的内存模型(JMM)
Java的内存模型描述了java程序中各种变量的访问规则,以及在JVM中将变量储存到内存和从内存读出变量这样的底层细节。
多线程内存模型:java的线程内存是独立的,换言之每个线程都有其自己的工作内存,且线程之间的工作内存是不能互相访问的,这是保护线程内的数据安全的一种机制。在程序中的变量都会存在一个主内存里,但每个线程都有自己的工作内存,里面保存的是线程中使用到主内存变量的一个拷贝。如果多个线程都同时使用到这个变量,那么每个线程工作内存都会存在这个变量的副本,这个变量也叫线程的共享变量。
可见性:一个线程对共享变量的修改,能够被其他线程看到,比如设计一个科学计算软件,负责计算的代码块在一个线程里运行,当结果算出,负责更新UI的线程能够及时地使用到这个结果。
规定1:线程对共享变量的所有操作都必须 在自己的工作过内存中进行,不能直接在主内存中进行读写。
规定2:不同线程之间无法直接访问其他线程工作内存中的变量,线程间的变量值传递需要通过主内存完成。
如果一个变量需要跨线程使用,必须通过主内存来完成更新,换言之,主内存相当于一个桥梁,一个线程把共享内存更新了,那必须将新的数据更新到主内存中,另外一个线程就从主内存中获取更新后的数据。
导致共享变量在线程间不可见的原因:.
1.线程的交叉执行;
2.重排序结合线程交叉执行
3.共享变量更新后的值没有在工作内存与主内存之间及时更新。
Synchronized实现
Synchronized可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码。当两个并发线程访问同一个对象object中的这个加锁同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。然而,当一个线程访问object的一个加锁代码块时,另一个线程仍然可以访问该object中的非加锁代码块。
Synchronized特性:
·原子性(同步特性)
·内存可见性
JMM关于Synchronized的两条规定:
·线程解锁前,必须把共享变量的最新值刷新到主内存中;
·线程加锁时,讲清空工作内存中共享变量的值,从而使用共享变量是需要从主内存中重新读取最新的值(加锁与解锁需要统一把锁)
线程执行互斥锁代码的过程:
1.获得互斥锁
2.清空工作内存
3.从主内存拷贝最新变量副本到工作内存
4.执行代码块
5.将更改后的共享变量的值刷新到主内存中
6.释放互斥锁
关于互斥锁,可以点此了解http://baike.baidu.com/link?url=dI6S1ybzJK9U3PdML7SfyC0VgD6Ez967cWfrRfS9Z-cl3lux3rmN8PrZy7QhjMAgF4iFxPGj21Ign3o-AXd2-K
代码演示
public class TEST {
private int A = 0;
private int B = 0;
/*在方法权限修饰符与返回值之间加入synchronized关键字*/
public synchronized void demo(){
A = 5;
B = 10;
}
public void demo2(){
System.out.println(A+","+B);
}
/*创建内部线程类*/
private class threaddemo extends Thread{
int flag = 0;
/*通过构造方法决定启动哪个方法*/
public threaddemo(int a){
flag = a;
}
public void run(){
if(flag==1)
demo();
else if(flag==2)
demo2();
}
}
public static void main(String[] args) {
TEST test = new TEST();
test.new threaddemo(1).start();
test.new threaddemo(2).start();
}
}
运行结果:5,10
如果不加synchronized关键字,由于线程的交叉执行,则可能会出现以下结果:
0,10 —–出现这种情况是由于编译器对demo()方法内进行了重排序;
5,0 —–这种情况由于先执行demo()里的A= 5;然后线程跳到demo2()运行输出
0,0 —–首先执行demo2()线程的结果
5,10
小结
在进行多线程程序的编写时,应该在需要保证可见性的地方加入安全保护,防止出现工作内存与主内存之间数据更新不及时的情况。