一、volatile关键字
并发编程1–并发问题的三个陷阱
已经写了,解决可见性问题和有序性问题就可以使用volatile来修饰变量
1、保证可见性
volatile修饰变量后,该变量的数据更改在操作系统会做两件事情:
①、把当前的CPU处理器的缓存中修改后的A变量写回系统内存
②、其他CPU缓存了该变量的内存地址里的址无效
所以能保证了变量的可见性
2、保证有序性
可见性问题是通过禁用缓存的方式解决的,那么有序性问题可以通过禁用编译优化来解决吗?JAVA内存模型里提供了六项HAPPEN-BEFORE规则:顺序性规则、volatile变量规则、传递性、锁的规则、线程join规则、线程start规则。加起来的意思大概就是:
1)当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;
2)在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。
所以volatile解决了指令重排序,保证了变量的有序性和可见性,但是牺牲了缓存和编译优化
二、原子操作类
3、保证原子性
java.util.concurrent.atomic 提供了原子更新基本类型、原子更新数组、原子更新引用类型、原子字段类来保证操作的原子性。
三、synchronized关键字
刚开始学锁,一定是学的synchronized关键字。
1、同步锁-synchronized:
在Java中,每一个对象都拥有一个锁标记(monitor),也称为监视器,多线程同时访问某个对象时,线程只有获取了该对象的锁才能访问。可以使用synchronized关键字来标记一个方法或者代码块,当某个线程调用该对象的synchronized方法或者访问synchronized代码块时,这个线程便获得了该对象的锁,其他线程暂时无法访问这个方法,只有等待这个方法执行完毕或者代码块执行完毕,这个线程才会释放该对象的锁,其他线程才能执行这个方法或者代码块。它修饰的对象有以下几种:
- 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
- 修饰一个普通方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
- 修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
- 修饰一个类,其作用的范围是synchronized后面括号括起来的部分,作用的对象是这个类的所有对象
换言之:
1)当一个线程正在访问一个对象的synchronized方法,那么其他线程不能访问该对象的其他synchronized方法。这个原因很简单,因为一个对象只有一把锁,当一个线程获取了该对象的锁之后,其他线程无法获取该对象的锁,所以无法访问该对象的其他synchronized方法。
2)当一个线程正在访问一个对象的synchronized方法,那么其他线程能访问该对象的非synchronized方法。这个原因很简单,访问非synchronized方法不需要获得该对象的锁,假如一个方法没用synchronized关键字修饰,说明它不会使用到临界资源,那么其他线程是可以访问这个方法的。
3)如果一个线程A需要访问对象object1的synchronized方法fun1,另外一个线程B需要访问对象object2的synchronized方法fun1,即使object1和object2是同一类型),也不会产生线程安全问题,因为他们访问的是不同的对象,所以不存在互斥问题。
2、synchronized的可重入性:
在Java内部,同一个线程调用自己类中其他synchronized方法/块时不会阻碍该线程的执行,同一个线程对同一个对象锁是可重入的,同一个线程可以获取同一把锁多次,也就是可以多次重入。原因是Java中线程获得对象锁的操作是以线程为单位的,而不是以调用为单位的。就是说同一线程对同一个对象锁是可重入的,而且同一个线程可以获取同一把锁多次,也就是可以多次重入。
3、重量级锁
synchronized是一个重量级锁,它能保证原子性和可见性,但是笨重和繁琐,性能差。