1. CAS ( 无锁优化,自旋锁,乐观锁)
全名(Compare And Set)
基本原理类似于这样的工作原理:
需要读写的内存位置(V)、预期原值(A)、新值(B)。如果内存位置与预期原值的A相匹配,那么将内存位置的值更新为新值B。如果内存位置与预期原值的值不匹配,那么处理器不会做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前值。)CAS其实就是一个:我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。这其实和乐观锁的冲突检测+数据更新的原理是一样的。
基本就是下面的代码逻辑
cas(Value , Expected ,NewValue)
if(Value=Expected){
Value=NewValue;
}else{
else try again or fail;
}
需要读写的内存位置(Value)、预期原值(Expected)、新值(NewValue)。
如果内存位置Value与预期原值的Expected匹配,那么将内存位置的值更新为新值,Value=NewValue。
如果内存位置与预期原值的值不匹配,那么处理器不会做任何操作。
底层是CPU的原语言支持
1.1 AtomicXXX
- 如果要进行一个数值的自增,我们需要使用synchronized来进行锁定,每次只有一个线程进行自增。
测试代码:
public class C2_Atomic {
static int m = 0;
public static void main(String[] args) throws InterruptedException {
C2_Atomic c2_atomic = new C2_Atomic();
List<Thread> list = new ArrayList<>();
//创建10个线程,并启动。每个线程增加1000,理论上是10000
//但是如果不增加synchronized,就会导致m++数据错误。
for (int i = 0; i < 10; i++) {
list.add(new Thread(c2_atomic::hello,"thread"+i));
}
list.forEach(thread ->{
thread.start();
});
//等3s等待所有的线程都执行完
sleep(1000);
System.out.println(m);
}
public synchronized void hello(){
System.out.println(Thread.currentThread().getName()+"----m:"+m);
for (int i = 0; i < 1000; i++) {
m++;
}
System.out.println(Thread.currentThread().getName()+":"+m);
}
}
可以synchronized可以实现,我们也可以用AtomicXXX相关的类实现。
这里提一下,为什么 m不用volatile进行修饰,也可以实现呢。volatile用来保证共享数据可见性,这里为什么不用呢。
synchronized在修改了本地内存中的变量后,解锁前会将本地内存修改的内容刷新到主内存中,确保了共享变量的值是最新的,也就保证了可见性。
可以这么理解,我们知道数据不一致的原因是由于cup读取缓存导致的,当数据更新时,没有刷新缓存,导致脏读取。这里有加入了sychronized,只能一个线程一个线程去获取m的值,在上一个线程执行完之前,下一个线程并没有读取m的值,也没有缓存,所以不会导致脏读。每次读取都会直接从内存中获取,而内存中的数据是最新的。所以这里可以不使用volatile。
- AtomicInterger相关的的实现
测试代码:
package com.tzw.juc.c2_Atomic;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import static java.lang.Thread.sleep;
public class C2_Atomic2 {
AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
C2_Atomic2 c2_atomic = new C2_Atomic2();
List<Thread> list = new ArrayList<>();
//创建10个线程,并启动。每个线程增加1000,理论上是10000
for (int i = 0; i < 10; i++) {
list.add(new Thread(c2_atomic::hello,"thread"+i));
}
list.forEach(thread ->{
thread.start();
});
//等3s等待所有的线程都执行完
sleep(1000);
System.out.println(c2_atomic.count);
}
public void hello(){
System.out.println(Thread.currentThread().getName()+"----m:"+count);
for (int i = 0; i < 1000; i++) {
//等同于 m++,这个利用了CAS的原理保证了同步性。
//所以不用Synchronized也能够实现原子性
count.incrementAndGet();
}
System.out.println(Thread.currentThread().getName()+":"+count);
}
}
结果分析:
-
使用AtomicInteger .incrementAndGet() 方法,不需要加Synchronized
-
基本的工作原理:
基本就是下面的代码逻辑
cas(Value , Expected ,NewValue)
if(Value=Expected),判断当前值和期望的当前值是否一致。
Value=NewValue ,如果一致,进行+1操作,并赋值。当前值是0,期望的当前值也是0,给+1的新值赋值给变量
else try again or fail,如果不一致,try again(自旋),等待下一次获取cup资源进行执行判断。
底层是CPU的原语言支持 -
ABA问题。(通俗来说就是1变成2,然后2又变成1,还是可以继续执行的)
eg:例如a线程期望的当前值是100,而当前实际的值也是100,但是当前实际的值,可能是被其他线程经过多次变更,可能由100变成99,又由99变成了100.这就是ABA,这种无所谓,只要求我再判断的时候当前值和期望的当前值是一样的就行,就能够继续+1操作,对结果不影响。
ABA问题,如果是基础类型,不影响结果。
但是如果是一个引用类型,就可能会出现问题。例如你的你朋友跟你复合,那么中间就不知道有几个。。。。。所以如果要解决这个问题,还需要比较版本号,除了比较内容相等之外,还需要比较版本号。
1.1.1 Unsafe
CAS的实现底层依赖的都是Unsafe这个类。
- 直接操作内存空间
- 直接生成类实例,
- 直接类或者实例变量
- Unsafe 相当于C C++ 指针
- Unsafe 通过allocateMemory方法分配内存。通过freeMemory方法释放内存
- C语言:malloc(分配内存) free(释放内存) C++ : new(分配内存) delete(释放内存)
1.1.2 不同方式的递增执行效率比较
- synchronized 的count++。
- cas的ActomicXXX的自旋锁
- LongAdder。cas的分段锁实现。
package com.tzw.juc.c2_Atomic;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.LongAdder;
import static java.lang.Thread.sleep;
/**
* 测试三种自增的效率
* 1. synchronized
* 2. AtomicXXX
* 3. LongAdder
*
*
*/
public class C2_Atomic3 {
private static int count;
private static AtomicInteger count2 = new AtomicInteger();
private static LongAdder count3 = new LongAdder();
public static void main(String[] args) throws Exception{
C2_Atomic3 c2_atomic3 = new C2_Atomic3();
//-----------------计算synchronized方式的耗时------------
List<Thread> list = new ArrayList<>();
//1. 创建1000个线程
for (int i = 0; i <1000 ; i++) {
list.add(new Thread(c2_atomic3::getCount1));
}
//2.线程开始时间
long start = System.currentTimeMillis();
//3.线程启动
list.forEach(thread -> thread.start());
//4.将线程加入到主线程中,先执行1000个线程,线程执行完后继续执行主线程
for (Thread thread : list) {
thread.join();
}
//5.记录线程结束时间
long end = System.currentTimeMillis();
//6.答应最终结果,计算耗时
System.out.println("synchronized:"+"count="+count+"--耗时:"+(end-start));
//-----------------计算AtomicInteger的耗时--------------------------
List<Thread> list2 = new ArrayList<>();
for (int i = 0; i <1000 ; i++) {
list2.add(new Thread(c2_atomic3::getCount2));
}
long start2 = System.currentTimeMillis();
list2.forEach(thread -> thread.start());
for (Thread thread : list2) {
thread.join();
}
long end2 = System.currentTimeMillis();
System.out.println("atomicInteger:"+"count2="+count2.get()+"--耗时:"+(end2-start2));
//--------------------计算LongAdder的耗时------------------------
List<Thread> list3 = new ArrayList<>();
for (int i = 0; i <1000 ; i++) {
list3.add(new Thread(c2_atomic3::getCount3));
}
long start3 = System.currentTimeMillis();
list3.forEach(thread -> thread.start());
for (Thread thread : list3) {
thread.join();
}
long end3 = System.currentTimeMillis();
System.out.println("longAdder:"+"count3="+count3.longValue()+"--耗时:"+(end3-start3));
}
/**
* synchronized的实现
*/
public synchronized void getCount1(){
for (int i = 0; i < 100; i++) {
count++;
}
}
/**
* AtomicInteger的实现
*/
public void getCount2(){
for (int i = 0; i < 100; i++) {
count2.incrementAndGet();
}
}
/**
* LongAdder的实现。
*/
public void getCount3(){
for (int i = 0; i < 100; i++) {
count3.increment();
}
}
}
执行结果:
synchronized:count=100000--耗时:51
atomicInteger:count2=100000--耗时:34
longAdder:count3=100000--耗时:23
分析:这个只是当前线程数,当前代码量的执行结果。并不能说明longder就比synchronized和atomitInterger效率快。这需要根据实际的场景和数据量去做测试。找到符合自己最优的用法。
1.1.3 乐观锁 悲观锁
-
悲观锁,就是悲观认为当我们操作数据的时候数据一定会被其他线程修改,所以在操作的时候直接上锁。这样本线程就会独占资源,其他的线程就无法获取到锁,就进入阻塞状态直到锁释放后才能去操作数据。
悲观锁有哪些:例如关系数据库中的行锁,表锁,读锁,写锁等都是在操作之前就先上锁。synchronized 和ReentrantLock等独占锁也是悲观锁。 -
乐观锁,就是乐观的认为当我们在操作数据的时候数据并没有被其他线程修改,所以去操作的数据的时候并不会上锁。而是在操作的时候判断一下期望的值和实际的值是否一致,如果一致就更新,如果不一致就自旋。利用的CAS算法实现。乐观锁的好处在于省去了锁的开销,加大了系统的整个吞吐量。乐观锁适用执行速度快,代码量少的场景。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
1.2 Lock ReenTrantLock
- 底层AQS
- 可以用来替换synchronized ,悲观锁。
和synchronized相似,都是添加锁,其他线程需要阻塞等待,直到获取到锁。
/**
* 利用ReenTrankLock代替synchronized
*
*
*/
public class C3_ReenTrantLock2 {
Lock lock = new ReentrantLock();
public static void main(String[] args) {
C3_ReenTrantLock2 c3_reenTrankLock = new C3_ReenTrantLock2();
//启动thread1,lock锁被线程thread1持有,在解锁之前都不会释放锁,所以thread2必须等thread1执行完才能执行m2方法
new Thread(c3_reenTrankLock::m1,"thread1").start();
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(c3_reenTrankLock::m2,"thread2").start();
}
public void m1() {
try {
lock.lock();
for (int i = 0; i < 10; i++) {
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
}finally {
lock.unlock();
}
}
public synchronized void m2(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName()+":m2---");
} finally {
lock.unlock();
}
}
}
- 使用ReenTrantLock锁,需要手动的获取锁lock(),释放锁unlock()方法
- synchronized在抛出异常后自动释放锁,但是lock在抛出异常后,不会释放锁,所以一定记住在finally块中调用unlock()方法
- 功能比synchronized强大。可以调用ryLock()尝试获取锁方法,还可以设置尝试时间
使用trylock,并且可重入,测试代码
package com.tzw.juc.C3_Lock;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import static java.lang.Thread.currentThread;
import static java.lang.Thread.sleep;
/**
*
* 比起lock.lock,通过tryLock尝试获取锁,如果返回true说明获取到锁,进行执行
* 如果没有获取到锁。则跳过 加锁的代码块,继续往下执行
* 还能够指定尝试时间,lock.tryLock(time),如果时间到了还没有获取到锁,则跳过 加锁的代码块,继续往下执行。
* 可重入锁,同一个线程能够获取到锁。
*/
public class C3_ReenTrantLock3 {
Lock lock = new ReentrantLock();
public static void main(String[] args) {
C3_ReenTrantLock3 c3_reenTrankLock = new C3_ReenTrantLock3();
//启动thread1,lock锁被线程thread1持有,在解锁之前都不会释放锁,所以thread2必须等thread1执行完才能执行m2方法
new Thread(c3_reenTrankLock::m1,"thread1").start();
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(c3_reenTrankLock::m2,"thread2").start();
}
public void m1() {
try {
lock.lock();
for (int i = 0; i < 10; i++) {
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
if(i==3){
m2();
}
}
}finally {
lock.unlock();
}
}
public void m2(){
try {
//比起lock.lock,通过tryLock尝试获取锁,如果返回true说明获取到锁,进行执行
// 如果没有获取到锁。则跳过 加锁的代码块,继续往下执行
// 还能够指定尝试时间,lock.tryLock(time),如果时间到了还没有获取到锁,则跳过 加锁的代码块,继续往下执行。
// 可重入锁,同一个线程能够获取到锁。
if(lock.tryLock(5,TimeUnit.SECONDS)){
System.out.println(Thread.currentThread().getName()+":m2---");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if(lock.tryLock())lock.unlock();
}
System.out.println(Thread.currentThread().getName()+":尝试获取锁5秒,如果5秒内没有获取到锁,就跳过锁定的代码继续执行");
}
}
- 可重入锁
- Lock的方法 :lock trylock Unlock
- 打断锁 interrupt。
之前聊过关于线程的interrupt()方法调用,通知线程该结束了,如果线程处于阻塞状态(sleep,wait等),则会结束阻塞状态并抛出异常,在异常中进行处理,是停止线程还是继续执行。
public class C3_ReenTrantLock_interrupt {
Lock lock = new ReentrantLock();
public static void main(String[] args) {
C3_ReenTrantLock_interrupt c3_reenTrankLock = new C3_ReenTrantLock_interrupt();
Thread thread1 = new Thread(c3_reenTrankLock::m1, "thread1");
thread1.start();
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread1.interrupt();
}
public void m1() {
try {
lock.lock();
for (int i = 0; i < 10; i++) {
sleep(1000);
System.out.println(i);
}
} catch (InterruptedException e) {
System.out.println("暂停");
} finally {
lock.unlock();
}
}
}
ReenTrantLock提供了一个方法lockInterruptibly()方法,这个方法和lock()方法是一样的作用,都是加锁。
不同的是,如果在调用当调起lockInterruptibly()方法,在阻塞状态等待获取锁的时候,如果调用thread.interrupt(),也如同阻塞方法一样,会抛InterruptedException异常,可以在catch块中处理异常。
测试代码:
public class C3_ReenTrantLock_interrupt {
Lock lock = new ReentrantLock();
public static void main(String[] args) {
C3_ReenTrantLock_interrupt c3_reenTrankLock = new C3_ReenTrantLock_interrupt();
Thread thread1 = new Thread(c3_reenTrankLock::m1, "thread1");
Thread thread2 = new Thread(c3_reenTrankLock::m2, "thread2");
thread2.start();
thread1.start();
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread1.interrupt();
}
public void m1() {
try {
//thread2获取了锁,thread1在等待锁的释放,此时是阻塞状态。
//此时如果线程调用了interrupt方法,则会抛出InterruptedException。
lock.lockInterruptibly();
System.out.println("标记,因为sleep也会对interrupt有响应,所以排除sleep的影响进行打印");
for (int i = 0; i < 10; i++) {
System.out.println(i);
sleep(5000);
}
} catch (InterruptedException e) {
System.out.println("暂停");
} finally {
lock.unlock();
}
}
public void m2() {
try {
lock.lock();
while(true){
}
}finally {
lock.unlock();
}
}
}
- 公平锁 true:
/**
* 公平锁
*/
public class C3_ReenTrantLock5 {
Lock lock = new ReentrantLock(true);//设置为true是公平锁,但不是绝对的公平
public static void main(String[] args) {
C3_ReenTrantLock5 c3_reenTrankLock = new C3_ReenTrantLock5();
new Thread(c3_reenTrankLock::m1,"thread1").start();
new Thread(c3_reenTrankLock::m1,"thread2").start();
}
public void m1() {
for (int i = 0; i < 10; i++) {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + ":" + i);
}finally {
lock.unlock();
}
}
}
}
不是绝对公平的交替输出
1.3 CountDownLatch (倒数,门闩)
- countDown()
- await()
/**
* 倒计时门闩,当倒计时count减为0时,门闩开启。
* 如果没有减到0,则会一直阻塞。
*/
public class C4_CountDown {
private static CountDownLatch countDownLatch = new CountDownLatch(1);
public static void main(String[] args) {
C4_CountDown c4_countDown = new C4_CountDown();
//开启线程
new Thread(c4_countDown::hello,"thread1").start();
System.out.println("start");
try {
//门闩还插着,阻塞。当thread1线程经过三秒后执行倒计时。当倒计是为0,打开门闩
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("倒计时结束。。。。");
}
public void hello(){
//三秒后倒数
try {
sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//调用一次,倒计时减一。直到减为0,打开门闩
countDownLatch.countDown();
}
}
1.4 CyclicBarrier 线程栅栏
满人,发车,相当于法令枪。
CyclicBarrier cyclic = new CyclicBarrier(20).
cyclic.await(); 阻塞,等堆积20个打开。
线程堆积够20个,打开栅栏,执行这20个,然后在关上,等下在积累够20个,在执行。。。
/**
* CyclicBarrier线程栅栏
* 线程数阻塞到一定的数量,打开栅栏释放一次,然后重新积累。
* 可以想像成水库开闸放水,当水库积累到一定的量,放一次,然后关上,在积累到一定的量,在放一次。。。
* 就一个栅栏,循环的开和关。
*
*/
public class C5_CyclicBarrier {
//设置栅栏的阻塞数,当达到这个数时,会打开栅栏。
//第二个参数,是一个线程,但满足条件时执行一次线程。
private static CyclicBarrier cyclicBarrier = new CyclicBarrier(20,()->{ System.out.println("满20"); });
public static void main(String[] args) {
C5_CyclicBarrier c5_cyclicBarrier = new C5_CyclicBarrier();
//开启100个线程,满20个释放一次。栅栏总共执行5次开启关闭
for (int i = 0; i < 100; i++) {
new Thread(c5_cyclicBarrier::hello).start();
if(i==19){
try {
sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public void hello(){
try {
//阻塞,当没有满足线程数时一直处于阻塞状态
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("开始执行。。。。");
}
}
1.5 Phaser
分阶段栅栏,相当于过关游戏。一关一关过。
婚礼的例子
- 注册线程数量7.
- 当线程数量到达7的时候,放下第一个栅栏,自动进入第一个1阶段,自动调用onAdvance方法
- 当第一个阶段所有线程都执行完,放下第二个栅栏,进入第二个阶段
- 以此类推
/**
* 1. 继承Phaser重写onAdvance方法
* 2. 多线程执行hello方法
* 3. hello方法中phaser.arriveAndAwaitAdvance();方法等人齐,阻塞。当线程数到达设置值时自动调用onAdvance方法
* 4. 执行onAdvance中的代码,每一个arriveAndAwaitAdvance()方法都相当于时一个栅栏,一个关卡,一个case的值
* 5. 线程数达到设定值,打开栅栏(过关)。继续执行,当遇到下一个每一个arriveAndAwaitAdvance阻塞等待,达到设定值,打开下一个栅栏
* 通过下一关。类比闯关游戏。
* 6. phaser还可以删除线程或者增加新的线程
*
*/
public class C6_Phaser {
private static Phaser phaser = new MarriagePhaser();
public static void main(String[] args) {
phaser.bulkRegister(7);
C6_Phaser c6_phaser=new C6_Phaser();
for(int i=0; i<5; i++) {
new Thread(c6_phaser::hello,"thread"+i).start();
}
new Thread(c6_phaser::hello,"xinlang").start();
new Thread(c6_phaser::hello,"xinniang").start();
}
public void hello(){
//0到达
System.out.println(Thread.currentThread().getName()+"到达");
phaser.arriveAndAwaitAdvance();
//1吃完
System.out.println(Thread.currentThread().getName()+"吃完");
phaser.arriveAndAwaitAdvance();
//2离开
System.out.println(Thread.currentThread().getName()+"离开");
phaser.arriveAndAwaitAdvance();
//3洞房
if(Thread.currentThread().getName().equals("xinlang")||Thread.currentThread().getName().equals("xinniang")){
System.out.println(Thread.currentThread().getName()+"洞房");
phaser.arriveAndAwaitAdvance();
// //4生子
System.out.println(Thread.currentThread().getName()+"生子");
phaser.arriveAndAwaitAdvance();
//照顾孩子
System.out.println(Thread.currentThread().getName()+"照顾孩子");
phaser.arriveAndAwaitAdvance();
}else{
System.out.println(Thread.currentThread().getName()+"散场");
phaser.arriveAndDeregister();
}
}
static class MarriagePhaser extends Phaser {
@Override
protected boolean onAdvance(int phase, int registeredParties) {
switch (phase) {
case 0:
System.out.println("所有人到齐了!" + registeredParties);
System.out.println();
return false;
case 1:
System.out.println("所有人吃完了!" + registeredParties);
System.out.println();
return false;
case 2:
System.out.println("所有人离开了!" + registeredParties);
System.out.println();
return false;
case 3:
System.out.println("婚礼结束!新郎新娘抱抱!" + registeredParties);
System.out.println();
return false;
case 4:
System.out.println("生子 " + registeredParties);
System.out.println();
return false;
case 5:
System.out.println("照顾孩子 " + registeredParties);
System.out.println();
return true;
default:
return true;
}
}
}
}
1.6 ReadWriterLock 读写锁
- 共享锁 readLock,read线程之间可以共享
- 排他锁(互斥锁),write线程互斥
public class C3_ReadWriteLock {
private Lock lock = new ReentrantLock();
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
//获取读锁
private Lock readLock = readWriteLock.readLock();
//获取写锁
private Lock writerLock = readWriteLock.writeLock();
public static void main(String[] args) {
C3_ReadWriteLock c3_readWriteLock = new C3_ReadWriteLock();
for (int i = 0; i <10 ; i++) {
new Thread(c3_readWriteLock::read).start();
}
for (int i = 0; i <10 ; i++) {
new Thread(c3_readWriteLock::write).start();
}
}
/**
* 读锁是共享锁,线程允许获取锁
*/
public void read(){
try {
readLock.lock();
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":读取");
} finally {
if(readLock!=null) readLock.unlock();
}
}
/**
* 写锁是排他锁,线程只能一个一个获取,相当于ReentrantLock
*/
public void write(){
try {
writerLock.lock();
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":写入");
} finally {
if(writerLock!=null) writerLock.unlock();
}
}
}
1.7 Semaphore
- 限流
- 场景,高速收费口限流
/**
* 信号量,做限流用
* 同一时间只能让4个线程通过。
*
* 一定要记得释放锁。
*
*/
public class C7_Semaphore {
// 同一时间只能让4个线程通过
private Semaphore semaphore = new Semaphore(4);
public static void main(String[] args) {
C7_Semaphore c7_semaphore = new C7_Semaphore();
for (int i = 0; i < 10; i++) {
new Thread(c7_semaphore::hello,"thread+"+i).start();
}
}
public void hello(){
try {
semaphore.acquire();
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
System.out.println(Thread.currentThread().getName()+":....");
}
}
1.8 Exchanger 线程交换
两个线程之间就行数据交换,例如小游戏两个任务进行装备交换。
exchanger 两个线程之间的交换,当一个线程调起exchanges()方法时,就进入阻塞,直到另一个线程调起exchangges()方法,这两个线程就发生交换。
public class C8_Exchanger {
private static Exchanger exchanger = new Exchanger();
public static void main(String[] args) {
new Thread(()->{
String s = "thread1";
try {
s = (String) exchanger.exchange(s);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+s);
},"thread1").start();
new Thread(()-> {
String s="thread2";
try {
s = (String) exchanger.exchange(s);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+s);
},"thread2").start();
new Thread(()-> {
String s="thread3";
try {
s = (String) exchanger.exchange(s);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+s);
},"thread3").start();
}
}
以上代码thread1 和thread2 进行交换
thread3没有其他线程交换了,所以会阻塞。
1.9 LockSupport
1.9.1 LockSupport
- park方法,线程停止阻塞。
- unPark,线程放开,线程继续往下走。
- 和wait方法 notify方法一个意思,不同的是,可以先调用unPark方法,然后调用park方法,此时park方法不会阻塞。而是继续执行。
- 当时如果先调用一个unpark方法,然后调用连个park方法,那么虽然第一个park方法不起作用,但是第二个park方法会阻塞。内部维护了一个count,只能一对一。
/**
* 1. LockSupport.park() 阻塞
* LockSupport.unPark(thread) 指定的线程退出阻状态 ,继续执行
* 2. 可以执行LockSupport.unPark(thread),在执行park(),此时线程不会进入阻塞状态,继续执行。
* 但是如果在执行过程中连续调用两次park,那么第一次会继续执行,第二次会被阻塞。
* 也就是说,同一个线程的unpark 和 park是一对一的关系。
*/
public class C9_LockSupport {
private static Thread thread1;
public static void main(String[] args) {
C9_LockSupport c9_lockSupport = new C9_LockSupport();
thread1 = new Thread(c9_lockSupport::hello, "thread1");
Thread thread2 = new Thread(c9_lockSupport::hello2, "thread2");
thread1.start();
thread2.start();
}
public void hello(){
LockSupport.park();
System.out.println("解除 park第一次");
LockSupport.park();
System.out.println("解除 park第二次");
}
public void hello2() {
LockSupport.unpark(thread1);
}
}
1.9.2 wait 和 notify
package com.tzw.juc.c9_LockSupport;
import static java.lang.Thread.sleep;
/**
* wait notify 方法都是Object的方法
* wait notify需要在synchronized方法或者块中使用。
* wait,当前线程进入阻塞状态
* notify,唤醒阻塞的线程,继续执行。
*
*
*/
public class C9_Wait_notify {
public static void main(String[] args) throws InterruptedException {
C9_Wait_notify c9_lockSupport2 = new C9_Wait_notify();
Thread thread1 = new Thread(c9_lockSupport2::hello, "thread1");
thread1.start();
Thread thread2 = new Thread(c9_lockSupport2::hello2, "thread2");
thread2.start();
}
public synchronized void hello(){
try {
sleep(1000);
//wait方法是对象Object的方法,这里是this。
//this.wait(),当前线程进入阻塞状态,让出锁。别的线程可以获取锁
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"wait 结束");
}
public synchronized void hello2(){
System.out.println("hello2");
//当线程1调用wait后,让出锁,线程2可以执行,
// notify 叫醒线程1
notify();
System.out.println("调用notify,解除锁定");
}
}
2. 总结
- synchronized
同步阻塞,维护一个对象锁。原子性,可见性() ,顺序性
- volatile
- 可见性(MESI一致性协议)。2. 禁止指令重排(内存屏障)
- AtomicXXX
乐观锁,自旋锁
- LongAdder
分段自旋锁
- RecntrantLock
实现lock接口,通过lock() 方法 ,unlock()方法,灵活使用tryLock(time)方法
lock方法和synchronized有区别,synchronized在遇到异常时会自动释放锁,而lock方法不会,所以使用时一定在finally块中,调用unLock释放锁。
- CountDownLotch 门闩
await() 方法 和 countDown方法,维护一个count,count=0时,释放锁。
- CyclicBrarrier
循环栅栏,类似 跑步时的法令枪,或者水库闸门
- 设置满足释放的量。2. 阻塞方法await。3. 当达到量释放栅栏,,一次性放空,然后重新开始积累
- Phaser
分段栅栏,类似于闯关游戏,多个关卡。一家人就要整整齐齐,线程必须都执行完,才能进入下一关。
- 设置闯关线程数量 2. arriveAndAwaitAdvance进行拦截,到达这里的线程数达到设定值,一家人都到了 。 自动调用onAdvance方法
需要进程Phaser,重新onAdvance方法。
- ReadWriterLock
readLock()方法。获取读锁,读锁线程之间共享。
writerLock()方法。获取写锁,写锁排他锁,互斥锁。线程独占。相当于ReenTrantLock
- Semaphore
信号量,限流。 相当于高速路过收费站
- 设置同一时间可以通过的线程数。 2. semaphore.acquire()限流,上锁,只允许设置的线程数经过 3. 解锁semaphore.release(),解锁后下一波线程通过。
- ExChanger
交换锁,游戏中交换装备。
exChanger(value)方法进行阻塞,当另一个线程调用exChange(value2)时,方法解锁,交换数据。一对一的。
- LockSuppot
park()阻塞,unpark()解锁,相当于wait() 和 notify().
unpark(thread)可以在park()之前执行,也能起到唤醒功能。unpark 和 park时一对一的关系,一个unpark只能让一个park通过
- wait notify notifyAll
wait 和 notify时对象Object的方法,所以需要和synchronized配置使用。和对象锁一起使用。
wait方法,会让出当前线程的锁,所以线程会阻塞
notify方法能够唤醒阻塞状态的线程,线程继续执行。
- interrupte 中断
中断线程。比起stop方法,更加的优雅,stop方法现在已经被废弃了。
interrupte是通知线程要结束。 但至于人加要不要结束还是需要自己控制。
单独调用interrupted是不起作用的,interrupte需要和阻塞方法sleep, wait等配合使用,使用时调用interrupte方法会抛InterrupteException,需要catch异常,在catch中处理到底要不要中断线程。
3 哈哈
对于对象锁来说,就是线程需要hold住对象才能执行。eg: synchronized wait notify等
对于cas字段锁,AQS