各式锁的实际应用
乐观锁 cas(要改的对象,期望的值,要给的值)无锁操作,其实是一个乐观锁…cas本身可以看成是一个锁
- automic : 一种使用 cas 实现的原子性操作
原子操作的简单方法:
函数 | 效果 | 备注 |
---|---|---|
AtomicInteger a = new AtomicInteger(0); | int a = 0; | 创建对象a并且赋初值为0; |
a.incrementAndGet( ); | i++; | 对原值+1后返回; |
a.getAndIncrement( ); | ++i; | 对原值返回后+1; |
a.addAndGet(i); | a+=i; | 返回a+i; |
a.getAndAdd(i); | a+=i; | 返回原值之后给a+i; |
在线程很多的情况下:LongAdder(分段锁:在线程多的时候有优势) > Atomic > synchronized。
Synchronized 的可重入性:
//可重入: synchronized void m1(){ for(int i = 1;i<10;i++){ try{ TimeUtil.SECONDS.sleep(1);// 睡一秒 }catch(InterruptedException e){ e.printStackTrace(); } sout(i); } } synchronized void m2(){sout("m2...");} public static void main(String[] args){ T01_ReentrantLock1 r1 = new T01_ReentrantLock1(); new Thread(r1::m1).start(); try{ TimeUtil.SECONDS.sleep(1);// 睡一秒 }catch(InterruptedException e){ e.printStackTrace(); } new Thread(r1::m2).start(); }
输出结果:0 1 23 4 5 6 7 8 9 m2…
代码修改:synchronized
//可重入: synchronized void m1(){ for(int i = 1;i<10;i++){ try{ TimeUtil.SECONDS.sleep(1);// 睡一秒 }catch(InterruptedException e){ e.printStackTrace(); } sout(i); if(i == 2){ new Thread(r1::m2).start(); } } } synchronized void m2(){sout("m2...");} public static void main(String[] args){ T01_ReentrantLock1 r1 = new T01_ReentrantLock1(); new Thread(r1::m1).start(); try{ TimeUtil.SECONDS.sleep(1);// 睡一秒 }catch(InterruptedException e){ e.printStackTrace(); } }
输出结果:0 1 2 m2 … 3 4 5 6 7 8 9
lock()
:替代 synchronized 的方法;
Lock lock = new ReentrantLock();
-
特点:
- 需要手动上锁
lock.lock( );
- 需要手动解锁
lock.unlock( )
; - 防止进程出错而导致死锁,需要
try{ …… }catch( ){ …… }
- 需要手动上锁
-
优点:
-
可以使用
tryLock()
尝试上锁; -
当
synchronized
遇到锁之后只能等待,而tryLock()
可以自定义等待时间; -
locked = lock.tryLock(SECONDS(时间长度),TimeUtil.SECONDS(时间格式:秒));
-
-
常用方法:
方法 | 参数 | 用法 |
---|---|---|
.lock( ); | null | 锁定 |
.unlock( ); | null | 释放 |
.tryLock(n,TimeUtil.SECONDS); | 时间长度 时间单位 | 等待参数 时间过程中:如果当前进程释放了,则锁定; 不释放则不锁定; |
.lockInterruptibly( ); | null; | 可以相应被打断的锁; |
.interrupt( ); | Null; | 打断这个锁; |
公平锁
ReetrantLock lock = new ReentrantLock( true );
- 概念:
- 当执行队列中有线程正在排队的时候:
- 公平锁:继续等待,排队执行;
- 不公平锁:不等待,直接抢,有可能抢到第一个执行;
- 当执行队列中有线程正在排队的时候:
- 创建方式:
- 在创建锁的时候加个
true
创建出来的就是公平锁;
- 在创建锁的时候加个
public class T05_ReentrantLock extends Thread(){
private stratic ReentrantLock lock = new ReentrantLock(true);
public void run(){
for(int i = 0;i<100;i++){
lock.lock();
try{
Sout(Thread.currentThread().getName()+"获得锁");
}finally{
lock.unlock();
}
}
}
}
一个倒计时的门栓 CountDownLatch
CountDownLatch latch = CountDownLatch( threads.length );
//创建一个length长度的门栓
.await()
阻塞
原join()
当前线程结束自动往前走
.countDown()
原子性–
栅栏工具 CyclicBarrier
循环栅栏工具
// 一个参数:不到20的时候,等待,到了20个,这20个发车,再来的继续等待
CyclicBarrier barrier = new CyclicBarrier(20);
// 两个参数:
CyclicBarrier barrier = new CyclicBarrier(20,run);
run(){ Sout("满员,发车!"); }
//lambdo 表达式
CyclicBarrier barrier = new CyclicBarrier(20,()->Sout("满员,发车!"));
同步进行的 Phaser
按照不同的阶段对线程进行划分。
-
使用场景:
- 遗传算法
- 现实生活一步一步执行的场景(如:婚礼)
- 像是一个一个栅栏一样
-
使用方法:
-
自定义一个类,继承
Phaser
类;static class MarrigePhaser extends Phaser
-
重写
onAdvance
方法;(栅栏被推倒的时候自动调用)protected boolean onAdvance(int phase,int registeredParties)
-
-
方法:
phaser.arriveAndAwaitAdvance(); //执行结束,开始等待; phaser.arriveAndDeregister(); //执行结束,不进入下一阶段;
读写锁
程序中的读写锁(一种排他锁、共享锁)
-
概念
- 当
A
进程在读取ABCD
的时候,B
进程也来读取ABCD
,同时发现A
进程在读取,则读取成功; - 当
A
进程在读取ABCD
的时候,B
进程来修改ABCD
,同时发现A
进程在读取,若此时更改ABCD
的内容,则A
进程读取会出问题,所以修改失败; - **总结:**两个都是读取的进程可以同时进行,当有 读 进程在进行时,无法进行 写 进程,写同理;
- 当
-
作用
- 避免 / 减少 脏数据
static ReadWriteLoak readWriteLock = new ReentrantReadWriteLock();
//在 ReentrantReadWriteLock 中 分出一个 `readLock`一个`writeLock`
static Lock readLock = readWriteLock.readLock();
static Lock writeLock = readWriteLock.writeLock();
public static void read(Lock lock){
try{
lock.lock();
Thread.sleep(1000);
Sout("read over!");
// 模拟读取过程
}catch(InterruptedException e){
e.peintStackTrace();
}finally{
lock.unlock();
}
}
public static void write(Lock lock,int a){
try{
lock.lock();
Thread.sleep(1000);
Sout("write "+ a +"over!");
// 模拟读取过程
}catch(InterruptedException e){
e.peintStackTrace();
}finally{
lock.unlock();
}
}
public static void main(String[] args){
Runnable readR = ()->read(lock);
//Runnable readR = ()->read(readLock);
Runnable write = ()->write(lock,new Random().nextInt());
for (int i=0;i<18;i++)new Thread(readR ).start();
for (int i=0;i<2 ;i++)new Thread(writeR).start();
}
}
// 如果使用 ReentrantLock
的话,以上代码在执行读
的时候也需要等待一秒;
// 解决方法:将Main
方法中的读
锁换成Runnable readR = ()-> read(readLock);
Semaphore 一个有意思的线程池
Semaphore s = new Semaphore(x);
x是几则这个 < 线程池 > 就 允许几个线程 同时执行。
public static void main(String[] args){
Semaphore s = new Semaphore(1);
//括号中数字为x时,允许x个线程同时执行
// T1 Running
new Thread(()->{
try{
s.acquire();
// 进来一个进程 1 变成 0 ,别的线程不能执行
Sout("T1 Running");
Thread.sleep(200);
Sout("T1 Running");
}catch(InterruptedException e){
e.printStackTrace();
}finally{
s.release();
// 离开一个进程 0 变成 1 ,别的线程可以执行
}
});
// T2 Running
new Thread(()->{
try{
s.acquire();
// 进来一个进程 1 变成 0 ,别的线程不能执行
Sout("T2 Running");
Thread.sleep(200);
Sout("T2 Running");
}catch(InterruptedException e){
e.printStackTrace();
}finally{
s.release();
// 离开一个进程 0 变成 1 ,别的线程可以执行
}
});
}
如果x==1
则运行结果是T1 T1 T2 T2
,否则可能是T1 T2 T1 T2
Exchanger 用于 < ! 两个 ! > 线程交换数据的方法
使用场景:双人游戏中两人交换装备!执行一次就失效,可以循环等待下一次;
public static void main(String[] args){
// T1
new Thread(()->{
String s = "T1";
try{
s = sxchanger.exchange(s);
}cathc(InterruptedException e){
e.printStackTrace();
}
Sout(Thread.currentThread().getName()+""+s);
},"t1").start();
// T2
new Thread(()->{
String s = "T2";
try{
s = sxchanger.exchange(s);
}cathc(InterruptedException e){
e.printStackTrace();
}
Sout(Thread.currentThread().getName()+""+s);
},"t2").start();
}
线程中有两个变量,分别是
s
和s
(局部变量),两个线程同时执行,最后交换T1
与T2
的值;
分布式锁
只是某个类型的锁,将来补充概念。
总结 :
- 无论何种情况,优先考虑使用
synchronized
- 读写的时候
读写锁
可以大大提升效率 lock
比synchronized
要灵活很多,但是需要自己加锁并释放锁,所以不是很方便