欢送大家搜寻“小猴子的技术笔记”关注我的公众号,有问题能够及时和我交换。
在多线程之间,共享变量的值是线程不平安的,因为线程在开始运行之后都会领有本人的工作空间,而从本人工作空间把批改的值刷新回主存的时候须要 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”是为了线程遇到异样之后开释锁而筹备的。
欢送大家搜寻“小猴子的技术笔记”关注我的公众号,有问题能够及时和我交换。