synchronized以及新型CAS锁

169 阅读5分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 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(2true);

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不会起作用