一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情。
1. synchronized的一些基本概念
- synchronized锁的是对象不是代码,由对象头上的两位记录是否加锁,表示加的什么锁,目前哪个线程持有的锁
- synchronized可以同时保证可见性和原子性,但不能保证有序性,可能会发生指令重排序
- 同步方法和非同步方法可以同时调用
- 对业务写加锁,读不加锁会导致脏读。也就是读到的值不是期望的值,当写进行到一半尚未完成的时候,读到的可能会是中间状态的值
- 锁方法的时候锁的是this、静态方法加锁锁的是xx.class
- 锁不能用于String常量、Interger、Long等
- synchronized获得的锁是可重入的。重入只能是同一个线程重入
- 程序执行过程中,若是发生异常,锁默认是会被释放的
- 同步代码块中的语句越少越好,也就是说将锁的粒度细化
- 避免将锁的引用变为其他的对象
2. synchronized锁升级的概念
- 第一个访问某把锁的线程,并不会持有该锁,只会在锁头记录自己的线程ID,当重复获取这个锁时只验证ID即可。此时称之为--偏向锁
- 如果此时有其他线程想征用这把锁,并不知直接获取锁。是在一个等待队列中自旋此时称之为--自旋锁
- 自旋10次之后升级为--重量级锁
2.1 锁的选择
- 持有锁的时间短,争抢锁的线程数少适合使用自旋锁。若是加锁时间段并且线程数少,在自旋几次之后即可运行并不过多占用CPU资源。
- 当持有锁的时间长,争抢锁的线程数多时适合使用重量级锁。因为在队列中等待并尝试获取锁是个消耗CPU的行为,当争抢的线程过多时,将会对CPU的施加巨大负载。
3. CAS的概念以及新型锁简介
Compare And Set称之为自旋锁,也可以称之为无锁,乐观锁
3.1 ReentrantLock
Lock lock = new ReentrantLock();
try(){
lock.lock();
} finally{
lock.unlock();
}
必须使用try catch finally包裹并将lock.unlock();放入finally代码块。否则万一unlock();没有执行,则会造成死锁。
3.1.1 tryLock();
boolean locked = lock.trylock(5,TimeUnit.SECONDS);//5是时间长短,第二个参数是时间类型
在5秒内尝试获取锁,若是成功则返回true,失败则为false。
3.1.2 lockInterruptibly();
lock.lockInterruptibly();
以对interrupt()方法做出响应,是可以被打断的加锁
3.1.3指定为公平锁
ReentrantLock lock = new ReentrantLock(true);//true表示公平锁
公平锁:
线程在变为Runnable状态之后,若没有拿到锁,则在一个队列中等待。谁在前边谁先执行,也就是遵循排队的规则,谁先来谁先执行。
3.2 CyclicBarrier
阻塞的线程数,到达一定数量会执行第二个参数的方法,然后继续执行原方法。也可以不传入,直接执行原方法。
CyclicBarrier barrier = new CyclicBarrier(20,function());
barrier.await();//阻塞,线程进来之后在此处等待,直到到达设定值20才会继续
也就是说在上边的例子中,当到达并且阻塞在await()的线程数到达20个时,将会执行function()的方法,继而执行主方法。
3.3 Phaser
分阶段执行的锁
自定义一个类从Phaser继承,重写onAdvance()方法,定义每个阶段做什么事,当每个阶段满足条件时,会自动调用onAdvance()方法。
MarragePhaser phaser = new MarragePhaser();
phaser.bulkRegister(7);//每个阶段参加的线程总数
phaser.arriveAndAwaitAdvance();//阻塞,等着进入下一个阶段
phaser.arriveAndDeregister();//不进入下一个阶段,执行完毕
在上边的例子中bulkRegister(7)表示参与每个阶段的线程数为7,当每个线程执行到arriveAndAwaitAdvance()执行完onAdvance()里对应的方法之后将阻塞,等待其他线程,当等待队列数到达7之后将进入下一阶段。若是哪个线程调用了arriveAndDeregister()方法,则此线程执行完毕,不进入下个阶段,之后的阻塞等待队列数量对应减少。
3.4 ReadWriteLock
读写锁是相对常用的锁,读锁是共享锁,也就是说当添加了读锁时,读操作可以并发执行。写锁是排他锁,也就是说,当添加了写锁时,只能等到获得锁的写操作完成,释放锁之后,其他线程才能拿到锁。
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
Lock readLock = readWriteLock.readLock();//定义读锁
Lock writeLock = readWriteLock.writeLock();//定义写锁
3.5 Semaphore
限流锁,可以控制最多允许多少个线程同时运行。
Semaphore s = new Semaphore(2);//允许2个线程同时执行
s.acquire();//拿到这个2,并使它变为1,再有线程进入则将1变为0.变为0时阻塞其他线程。
s.release();//线程结束,将1变为2或者将0变为1,其他线程可以进入方法
可通过传入第二个参数使其变为公平锁
Semaphore s = new Semaphore(2,true);
3.6 Exchanger
交换器,用于两个线程之间交换数据
Exchanger<String> exchanger = new Exchanger<>;
s = exchanger.exchange(s);//交换两个线程的s值
当第一个线程执行到s = exchanger.exchange(s)时将阻塞等待,直到第二个线程到达此处,交换数据并继续执行。
3.7 LockSupport
LockSupport.park();//当前线程停止
LockSupport.unpark(t);//解封,继续运行停止的线程t
unpark若是在park前执行,则park不会起作用