欢送大家搜寻“小猴子的技术笔记”关注我的公众号,有问题能够及时和我交换。
在多线程之间,共享变量的值是线程不平安的,因为线程在开始运行之后都会领有本人的工作空间,而从本人工作空间把批改的值刷新回主存的时候须要CPU的调度。因而,一个线程看到的变量可能并不是最新的。
咱们假如有个Share类中寄存了一个共享的变量“count”。
public class Share { public int count = 10000; public void decrement() { count--; } public int getCount() { return count; }}
而后有两个线程能够对这个共享的变量进行操作,每个线程都调用了5000次“decrement()”办法类进行共享变量的值批改:
public class ShareThread01 implements Runnable{ private Share share; public AccountThread01(Share share) { this.share = share; } @Override public void run() { for (int i = 0; i < 5000; i++) { share.decrement(); } }}
public class ShareThread02 implements Runnable{ private Share share; public AccountThread02(Share share) { this.share = share; } @Override public void run() { for (int i = 0; i < 5000; i++) { share.decrement(); } }}
如果下面的代码依照预期的执行,那么最初的后果应该是0。请执行上面的代码进行验证:
public class ShareTest { public static void main(String[] args) throws InterruptedException { while(true) { Share share = new Share(); Thread t1 = new Thread(new ShareThread01(share)); Thread t2 = new Thread(new ShareThread02(share)); t1.start(); t2.start(); TimeUnit.SECONDS.sleep(2); System.out.println(share.getCount()); } }}
运行下面的程序,你会发现每次输入的后果是不一样的。因为文章结尾曾经说过,这是因为Java在多个线程同时拜访同一个对象的成员变量的时候,每个线程都领有了这个对象变量的拷贝。因而在程序执行的过程中,一个线程所看到的变量并不一定是最新的。
兴许你想到了应用之前学习的“volatile”关键字来使共享变量进行内存可见,保障线程平安。于是上述程序改成了如下:
public class ShareThread02 implements Runnable{ private volatile Share share; public ShareThread02(Share share) { this.share = share; } @Override public void run() { for (int i = 0; i < 5000; i++) { share.decrement(); } }}
而后再次运行测试程序也会发现,所输入的后果并不是你想要的后果。请记住:关键字“volatile”只是保障了多线程之间共享变量的内存可见性,它并不保障共享变量的原子性。
这个时候关键字“synchronized”就派上了用场。它能够保障,同一个时刻只有一个线程可能拜访被“synchronized”润饰的办法或者代码块。将上述代码改成上面这样:
public class Share { public int count = 10000; public synchronized void decrement() { count--; } public int getCount() { return count; }}
再次运行测试程序,这个时候你会发现,每次失去的后果都是一样的。那么为什么增加了关键字“synchronized”之后就可能依照预期的进行执行呢?
通过执行“javap -v Share.class”来看看底层做了哪些润饰:
能够看到,在办法上进行了关键字”synchronized“的润饰,底层的实现是标记了一个"ACC_SYNCHRONIZED"的标识。代码如果遇到了这个标识,就示意获取到了对象的监视器monitor(monitor对象是由C++实现的),这个获取的过程是排他的,也就是同一时刻只能有一个线程获取到由synchronized所爱护对象的监视器。
除此之外,关键字”synchronized“还能够对代码块进行加锁:
public class Share { public int count = 10000; public void decrement() { synchronized (Share.class) { count--; } } public int getCount() { return count; }}
将上述代码执行“javap -v Share.class”反编译之后,能够看到如下:
”synchronized“关键字锁代码块的时候他提供了“monitor enter”和“monitor exit”两个JVM指令,它可能保障任何时候线程执行到“monitor enter”胜利之前都必须从主内存中获取数据。“monitor exit”退出之后,共享变量被更新后的值刷新到主内存中,因而”synchronized“关键字还能够保障内存的可见性。
如果你仔细观察就会发现,15和21有两个“monitor exit”那么为什么会有两个“monitor exit”呢?咱们做这样一个假如:如果线程启动执行的过程中忽然遇到异样了,这个时候线程该怎么办呢?总不能始终持有锁吧!于是线程就会开释锁。因而,第二个“monitor exit”是为了线程遇到异样之后开释锁而筹备的。
欢送大家搜寻“小猴子的技术笔记”关注我的公众号,有问题能够及时和我交换。