2021-06-05# Java 基础 (dayFourteen):锁的两种方式
当两个或两个以上的线程需要共享对同一数据的存取或修改时,会发生覆盖现象,覆盖情况取决于线程调度器先调度哪一个线程,这种情况通常称为竞态条件
锁对象
Java 提供两种机制防止并发访问代码块
Synchronized 关键字
ReentrantLock 类
ReentrantLock
使用 ReentrantLock 来进行加锁的话,都是要用在 try 语句上(即 lock 方法后面必须紧跟 try 语句),最后一定要用 finally 来释放锁

该结构确保了在任何时刻只有一个线程可以进入下面的 lock 后的语句块里面,也就是临界区
一旦一个线程锁定了锁对象,其他任何线程都无法通过 lock 语句,当其他线程调用 lock 时,则会进行暂停,直到前面的线程释放这个锁对象,也就是 unlock
举个栗子
下面是某类加锁的方法

测试的 main 方法

结果为

可以看到在 lock 前面的代码是可以让其他线程执行的,但是 lock 后面的代码必须等待前面的线程释放锁才可以去执行,期间就会发生阻塞
这里要注意的是,每个对象会有自己的 ReentrantLock,所以要争抢同一个 ReentrantLock 对象才会发生阻塞,该锁就可以保证串行化访问,如果是两个不同的对象,以上个栗子为例,就是两个不同的 TestSyncFunny 对象,那么两个线程得到的是不同的锁,是不会发生阻塞现象的
这个锁称为重入锁(ReentrantLock:重入),因为这个锁可以被线程反复获得,该锁会有一个持有计数来跟踪对 lock 方法的嵌套使用,即在加锁的 try 语句块里面调用方法 A,该方法 A 加的是同一把锁(同一个 ReentranLock),所以获得 B 的锁也会去影响 A 的运行
举个栗子
private ReentrantLock lock = new ReentrantLock();
public void doSyncTo(String name){
System.out.println("下面"+name+"也要进行加锁");
lock.lock();
try{
System.out.println(name+"已经加锁了~~~~");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void doSync(String name){
System.out.println(name+"还没进行上锁----");
lock.lock();
try{
System.out.println(name + "上锁了,dododoodid");
doSyncTo(name);
System.out.println(name+"调用完了另一个加锁方法,并且已经释放那个锁了");
Thread.sleep(2000);
System.out.println(name+"睡醒了");
//code....
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
上面是两个加锁的方法,用都是同一个 ReentrantLock,而且在 doSync 方法中,还调用了 doSyncTo 的方法

可以看到,调用 doSync 拿到的 ReentrantLock,会让其他线程也无法调用 doSyncTo 加锁后的那些方法,必须要等线程释放了最外的一层锁,即 doSync 的 lock 才可以调用,单独释放了 doSyncTo 里面的 lock 也是不够的
公平锁
我们也可以使用 ReentrantLock 来构建一个公平锁
调用下面的构造方法,传入 true 参数

公平锁是指:锁释放的时候,倾向于让等待时间最长的线程获取该锁
不过公平锁会影响性能,所以一般情况下都是非公平的,而且公平锁不一定是绝对公平的,还要取决于线程调度器,如果线程调度器忽略了一个已经为锁等待很长时间的线程,那就不能实现公平
评论