一. volatile -线程间通信方式:
保证可见性:
1)写:当某个线程修改了某个共享变量的值,会立即将修改值从自己工作内存刷新回主内存,并立即通知其他线程
2)读:所有线程读取该变量值时候,必须去主内存读取,不能读取cpu缓存
保证有序性:内存屏障,禁止重排序
只能修饰在变量上,使得 cpu 每次对于该变量的修改和读取都从内存中操作,而不是从CPU cache 中操作,保证共享变量对所有线程的可见性,但是并不能保证原子性
二. 悲观锁 --- synchronized :
synchronized --- 非公平锁 和 可重入锁
当某个线程访问被 synchronized 修饰的方法或代码块时,会先检查有没有其他线程在占用该对象锁,如果有,则该线程进入阻塞状态,直到占用该锁的线程使用完毕释放锁;synchronized 能够保证同一时刻只能有一个线程能访问,可修饰在类,方法,变量上,保证多线程下操作的可见性和原子性
1)对象锁 --- synchronized 加在非静态方法上:普通同步方法
对象锁:普通同步方法使用同一个锁,对象锁,当某一个对象调用同步方法获得该锁时,其余的对象在访问该类同步方法会被阻塞,直到当前对象同步方法执行完成,释放出锁
class Phone {
public synchronized void sendEmail(){
try { TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}
System.out.println("-----sendEmail");
}
public synchronized void sendSMS(){
System.out.println("-----sendSMS");
}
public void sendHello(){
System.out.println("-----sendHello");
}
}
public class Lock1Demo{
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sendEmail();
},"t1").start();
new Thread(()->{
phone.sendSMS();
},"t2").start();
new Thread(()->{
phone.sendHello();
},"t3").start();
}
}
运行结果:
2) 类锁 --- synchronized 加在静态方法上: 静态同步方法
类锁:静态同步方法使用同一个锁,类锁,当某一个静态同步方法的对象调用者获得该锁时,其余对象的静态同步方法也会被阻塞,直到当前静态同步方法执行完成,释放出锁
class Phone {
public static synchronized void sendEmail(){
try { TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}
System.out.println("-----sendEmail");
}
public static synchronized void sendSMS(){
System.out.println("-----sendSMS");
}
// 非synchronized 修饰, 不受类锁和对象锁影响
public void sendHello(){
System.out.println("-----sendHello");
}
}
/**
* 执行结果:
* -----sendHello
* -----sendEmail
* -----sendSMS
*
* */
注意:类锁和对象锁不是一把锁,并不构成竞争条件
class Phone {
public static synchronized void sendEmail(){
try { TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}
System.out.println("-----sendEmail");
}
// 对象锁, 不受类锁影响
public synchronized void sendSMS(){
System.out.println("-----sendSMS");
}
// 非synchronized 修饰, 不受类锁和对象锁影响
public void sendHello(){
System.out.println("-----sendHello");
}
}
/**
* 执行结果:
* -----sendSMS
* -----sendHello
* -----sendEmail
*
* */
3)synchronized --- 静态代码块
// this 代表当前锁住的对象资源
synchronized (this){
System.out.println("-----sendHello");
}
4)缺点:由于线程阻塞引起的线程频繁睡眠和唤醒极大的消耗了 CPU 资源
三. 乐观锁--- CAS + 版本号机制:
当多个线程竞争一个共享资源时,只有一个线程会胜出,但是其它竞争失败的线程并不会放弃占用cpu,而是循环刷新访问对于该资源锁的请求,直到获取该资源的锁;CAS只能保证变量的原子性并不能保证变量的可见性,所以一般都是使用 CAS + Volatile 实现变量的多线程
1. JUC 并发包中的原子类都存放在 java.util.concurrent.atomic 类路径下
原子类创建的变量,通过原子类提供的 CAS 方法可以实现该变量在多线程情况下的线程安全性
1)当没有使用原子类时:
public class Main {
volatile int number = 0;
// 读操作
public int getNumber(){
return this.number;
}
// 写操作 必须加锁
public synchronized void writeNumber(){
this.number++;
}
}
2)有了原子类之后:
public class Thread01 {
public static void main(String[] args) {
AtomicInteger number = new AtomicInteger(0);
// 获取 number 的值
number.get();
// 设置 number 的值
number.set(123);
}
}
2. CAS 原理:
CAS 内部维护需要读写的内存值(V)、expecValue(A)、newValue(B)
CAS 是一条 CPU 原子指令(cmpxchg 指令),是依靠 Unsafe 类的 CAS 方法如(compareAndSwapxxxx)底层实现即为CPU指令 cmpxchg
用 do-while 循环来实现 CAS 循环
@HotSpotIntrinsicCandidate
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!weakCompareAndSetInt(o, offset, v, v + delta));
return v;
}
3. 简单测试
可以看到expectValue = 11,newValue = 20,因为内存旧值 = 10,不等于 expectValue,说明在这段时间内有其他线程操作了对象 atomicInteger ,修改了其值,所以本次修改失败,修改结果res = false,atomicInteger.get() = 10
import java.util.concurrent.atomic.AtomicInteger;
public class TestConCurrent {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(10);
boolean res = atomicInteger.compareAndSet(11, 20);
System.out.println("原子对象的修改结果:"+res);
System.out.println(atomicInteger.get());
}
}
4. 自旋CAS缺点:
- 未获得锁的线程如果一直循环执行,会给CPU带来非常大的执行开销
- CAS 保证的是对一个变量执行操作的原子性,如果对多个变量操作时,CAS 目前无法直接保证操作的原子性
- ABA 问题:当某线程提交操作进行比较时发现内存数据旧数据A和预期值A一致,但是可能该数据被其他线程修改成B,然后又被其他线程修改回A;真正要做到严谨的CAS机制,我们在compare阶段不仅要比较期望值A和地址V中的实际值,还要比较变量的版本号是否一致