多线程volatile和synchronized
2020-02-06 本文已影响0人
二狗不是狗
一. 内存模型
image.png
- 寄存器是中央处理器内的组成部份。寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令、数据和位址。在中央处理器的控制部件中,包含的寄存器有指令寄存器(IR)和程序计数器(PC)。在中央处理器的算术及逻辑部件中,包含的寄存器有累加器(ACC)。
- Cache即高速缓冲存储器,是位于CPU与主内存间的一种容量较小但速度很高的存储器。用于解决内存速率低于CPU的运算速率的冲突。一般分为L1/L2/L3三级缓存。
二.内存模型的三个特性
1.原子性
- 在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行。
- 只有简单的读取、赋值(而且必须是将数字赋值给某个变量,变量之间的相互赋值不是原子操作)才是原子操作。
- Java内存模型只保证了基本读取和赋值是原子性操作,如果要实现更大范围操作的原子性,可以通过synchronized和Lock来实现。由于synchronized和Lock能够保证任一时刻只有一个线程执行该代码块,那么自然就不存在原子性问题了,从而保证了原子性。
// 原子性,直接将数值10赋值给x,也就是说线程执行这个语句的会直接将数值10写入到工作内存中
x = 10;
// 它先要去读取x的值,再将x的值写入工作内存
// 虽然读取x的值以及 将x的值写入工作内存 这2个操作都是原子性操作,但是合起来就不是原子性操作了。
y = x;
// 包括3个操作:读取x的值,进行加1操作,写入新的值
x++;
// 包括3个操作:读取x的值,进行加1操作,写入新的值
x = x + 1;
2.可见性
- 对于可见性,Java提供了volatile关键字来保证可见性。当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
- 而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。
- 另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。
3.有序性
- java内存模型规定如下的部分顺序,不符合以下规则的需要通过使用volitile 或者synchronized Lock进行操控。
- 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的
- 锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作
- volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
- 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
- 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
- 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
- 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
- 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始
三.volatile关键字
- 保证可见性:使用volatile修饰的变量,保证了不同线程之间对共享变量操作时的可见性,也就是说当一个线程修改后,另一个线程会立即看到最新的值。
- 保证有序性:禁止对指令进行重新排序操作
- 不能保证原子性:像num++这个操作上,因为num++不是个原子性的操作,而是个复合操作:1.读取;2.加一;3.赋值。所以,在多线程环境下,有可能线程A将num读取到本地内存中,此时其他线程可能已经将num增大了很多,线程A依然对过期的num进行自加,重新写到主存中,最终导致了num的结果不合预期。
四.synchronized关键字
- synchroized可以保证原子性,可见性以及有序性
public class RespUtils {
private static int sum = 0;
private Integer objectSum() {
for (int m = 1; m<=100000; m++) { sum++; }
System.out.println("函数内返回=" + sum);
return sum;
}
private synchronized Integer synObjectSum() {
for (int m = 1; m<=100000; m++) { sum++; }
System.out.println("函数内返回=" + sum);
return sum;
}
private Integer synObjectBlockSum() {
synchronized (this) {
for (int m = 1; m<=100000; m++) { sum++; }
System.out.println("函数内返回=" + sum);
return sum;
}
}
private static Integer classSum() {
for (int m = 1; m<=100000; m++) { sum++; }
System.out.println("函数内返回=" + sum);
return sum;
}
private synchronized static Integer synClasssSum() {
for (int m = 1; m<=100000; m++) { sum++; }
System.out.println("函数内返回=" + sum);
return sum;
}
private static Integer synClassBlockSum() {
synchronized (RespUtils.class) {
for (int m = 1; m<=100000; m++) { sum++; }
System.out.println("函数内返回=" + sum);
return sum;
}
}
public static void main(String[] args) throws Exception {
final RespUtils respUtils = new RespUtils();
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return respUtils.objectSum();
}
};
new Thread(new FutureTask<>(callable)).start();
new Thread(new FutureTask<>(callable)).start();
}
}
1.实例锁-普通方法
private Integer objectSum() {
for (int m = 1; m<=100000; m++) { sum++; }
System.out.println("函数内返回=" + sum);
return sum;
}
上面代码输出打印(结果不确定):
函数内返回=116780
函数内返回=116780
2.实例锁-对象本身
final RespUtils respUtils = new RespUtils();
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
synchronized (respUtils) {
return respUtils.objectSum();
}
}
};
new Thread(new FutureTask<>(callable)).start();
new Thread(new FutureTask<>(callable)).start();
上面代码输出打印(结果确定):
函数内返回=100000
函数内返回=200000
3.实例锁-对象方法
private synchronized Integer synObjectSum() {
for (int m = 1; m<=100000; m++) { sum++; }
System.out.println("函数内返回=" + sum);
return sum;
}
上面代码输出打印(结果确定):
函数内返回=100000
函数内返回=200000
4.实例锁-对象代码块
private Integer synObjectBlockSum() {
synchronized (this) {
for (int m = 1; m<=100000; m++) { sum++; }
System.out.println("函数内返回=" + sum);
return sum;
}
}
上面代码输出打印(结果确定):
函数内返回=100000
函数内返回=200000
5.全局锁-普通方法
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return RespUtils.classSum();
}
};
new Thread(new FutureTask<>(callable)).start();
new Thread(new FutureTask<>(callable)).start();
上面代码输出打印(结果不确定):
函数内返回=114195
函数内返回=114195
6.全局锁-类本身
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
synchronized (RespUtils.class) {
return RespUtils.classSum();
}
}
};
new Thread(new FutureTask<>(callable)).start();
new Thread(new FutureTask<>(callable)).start();
上面代码输出打印(结果确定):
函数内返回=100000
函数内返回=200000
7.全局锁-类方法
private synchronized static Integer synClasssSum() {
for (int m = 1; m<=100000; m++) { sum++; }
System.out.println("函数内返回=" + sum);
return sum;
}
上面代码输出打印(结果确定):
函数内返回=100000
函数内返回=200000
8.全局锁-类代码块
private static Integer synClassBlockSum() {
synchronized (RespUtils.class) {
for (int m = 1; m<=100000; m++) { sum++; }
System.out.println("函数内返回=" + sum);
return sum;
}
}
上面代码输出打印(结果确定):
函数内返回=100000
函数内返回=200000
五.使用注意事项
1.实例锁-注意事项
- 实例锁的意思是要执行某段代码必须获得具体对象实例的锁。实例锁-对象本身是指要执行线程synchronized (object){ }内部代码必须获得对象实例的锁;实例锁-对象方法是指要执行对象方法内部代码必须获得对象实例的锁;实例锁-对象代码块是指要执行代码块synchronized (this) { }内部代码必须获得对象实例的锁。
- 实例锁只针对需要获取对象实例锁才能执行的代码,而不针对不需要获取对象实例锁的代码。比如说调用了某对象的synObjectSum方法,还能同时访问该对象的objectSum方法。
- 实例锁是针对对象的,对于两个不同的对象可以并发访问。比如说创建了两个对象respUtils1和respUtils2,多线程可以并发访问这两个对象的synObjectSum()方法。
2.全局锁-注意事项
- 全局锁的意思是要执行某段代码必须获得Class全局的锁。全局锁-类本身是指要执行线程synchronized (RespUtils.class) { }内部代码必须获得Class全局的锁;全局锁-类方法是指要执行类方法内部代码必须获得Class全局的锁;全局锁-类代码块是指要执行代码块synchronized (RespUtils.class) { }内部代码必须获得Class全局的锁。
- 全局锁只针对需要获取Class全局的锁才能执行的代码,而不针对不需要Class全局的锁的代码。比如说调用了某类的synClasssSum方法,还能同时访问该对象的classSum方法。
3.注意事项
- 实例锁和全局锁是不同的锁,所以两个线程可以分别获取到实例锁和全局锁并发同时执行
new Thread(new Runnable() {
@Override
public void run() {
synchronized (new RespUtils()) {
new RespUtils().objectSum();
}
}
}, "线程1").start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (RespUtils.class) {
new RespUtils().objectSum();
}
}
}, "线程2").start();
上面代码输出打印(结果不确定):
函数内返回=114195
函数内返回=114195