第一节:使用ReentrantLock类
-
JDK1.5提供此类,不但也可以实现线程之间同步互斥,并且在扩展功能上也更加强大,使用上也比synchronized更加的灵活;
-
使用Condition实现等待/通知:
代码链接: -
对比:
-
condition实现生产者消费者模式:多对多交替打印:出现这种“有可能 B 连续”、“有可能 A 连续”,打印的情况就是因为程序中使用了一个Condition对象,再结合signalAll()方法来唤醒所有的线程,那么唤醒的线程就有可能是同类,所以就出现了这种情况;
-
公平锁与非公平锁:锁Lock分为“公平锁”和”非公平锁“;
a.公平锁:表示线程获取锁的顺序是按照线程加锁的顺序来分配的,即先来先得的FIFO先进先出顺序;
b.非公平锁:就是一种获取锁的抢占机制,是随机获得锁的;
c.可以设置当前的锁的抢占机制:
d.非公平锁的运行结果基本上是乱序的,说明先start()启动的线程不代表先获得锁; -
三个get方法:
a.int getHoldCount():查询当前线程保持此锁(锁重入可以观察)定的个数,也就是调用lock()方法的次数;
b.int getQueueLength():返回正在等待获取此锁定的线程估计数,比如说5个线程,1个线程首先执行了wait()方法,那么调用此方法后返回4,说明有4个线程同时再等待lock的释放;
c.int getWaitQueueLength(Condition condition):返回等待与此锁定相关的给定条件Condition的线程估计数,比如说有5个线程,每个线程都执行了同一个Condition对象的await()方法,则调用此方法时返回的是5; -
三个has方法:
a.boolean isFair():判断是不是公平锁;默认状态下,ReentrantLock类使用的是非公平锁;
b.boolean isHeldByCurrentThread()的作用是查询当前线程是否保持此锁定;
c.boolean isLocked()的作用是查询此锁定是否由任意线程保持; -
void lockInterruptibly():作用是如果当前线程未被中断,则获取锁定,如果已经被中断则出现异常;
如下图例:
-
boolean tryLock():仅在调用时锁定未被另外一个线程保持的情况下,才获取该锁定; 实例代码:
-
public boolean tryLock(long timeout, TimeUnit unit):如果锁定在给定等待时间内没有被另外一个线程保持,且当前线程未被中断,则获取该锁定;
-
TimeUnit 表示给定单元粒度的时间段,它提供在这些单元中进行跨单元转换和执行计时及延迟操作的实用工具方法。TimeUnit 不维护时间信息,但是有助于组织和使用可能跨各种上下文单独维护的时间表示形式。毫微秒定义为千分之一微秒,微秒为千分之一毫秒,毫秒为千分之一秒,一分钟为六十秒,一小时为六十分钟,一天为二十四小时。
如例子:线程A超时未获得锁:
-
awaitUninterruptibly():造成当前线程在接到信号之前一直处于等待状态,与此条件相关的锁以原子方式释放;之前子线程调用start方法后如果又调用了interrupt方法来中断线程,那么此时会抛出异常,所以可以调用awaitUninterruptibly()方法来解决此问题;
当condition调用awaitUninterruptibly()的时候,就不会出现异常了,而是一直等待下去:
-
awaitUntil():造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态;与此条件相关的锁以原子方式释放。
-
Calendar.getInstance():使用默认时区和语言环境获得一个日历。
-
使用Condition还可以实现指定线程唤醒用来实现顺序执行:
第二节:使用ReentrantReadWriteLock类(读写锁)
- 由于类ReentrantLock具有完全互斥排他的效果,同一时间只有一个线程在执行ReentrantLock.lock后面的方法,这样虽然保证了实例变量的线程安全性,单效率却是非常低下的。所以此时有了读写锁ReentrantReadWriteLock类,在某些不需要操作实例变量的方法中,完全可以使用读写锁来提升该方法的运行速度;
- 读写锁分为两种:
a.共享锁:与读操作相关的锁
b.排他锁:与写操作相关的锁
c.读锁与写锁互斥,写锁与写锁互斥;
d.即多个Thread可以同时进行读取操作,但是同一时刻只允许一个Thread进行写入操作; - 读读共享:
package Day25.Test;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
1. 类ReentrantReadWriteLock的使用:读读共享
2. 读锁之间不互斥
*/
class Service1{
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void read(){
try{
lock.readLock().lock();
System.out.println("获得读锁:"+Thread.currentThread().getName()
+" "+System.currentTimeMillis());
Thread.sleep(3000);
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock.readLock().unlock();
}
}
}
class ThreadA extends Thread{
private Service1 service1;
public ThreadA(Service1 service1) {
this.service1 = service1;
}
@Override
public void run() {
service1.read();
}
}
class ThreadB extends Thread{
private Service1 service1;
public ThreadB(Service1 service1) {
this.service1 = service1;
}
@Override
public void run() {
service1.read();
}
}
public class Test1 {
public static void main(String[] args) {
Service1 service1 = new Service1();
ThreadA threadA = new ThreadA(service1);
threadA.setName("A");
ThreadB threadB = new ThreadB(service1);
threadB.setName("B");
threadA.start();
threadB.start();
}
}
/*
运行结果:
获得读锁:A 1559365518307
获得读锁:B 1559365518309
从运行结果可以看出,两个线程几乎同时执行lock()方法后面的代码;
*/
- 写写互斥
将上面的代码稍作改动,lock调用writeLock.lock()方法;
运行结果如下:
- 读写互斥:
package Day25.Test;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 读写互斥;
*/
class Service3{
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void read(){
try{
lock.readLock().lock();
System.out.println("获得读锁线程:"+Thread.currentThread().getName()
+" "+System.currentTimeMillis());
Thread.sleep(3000);
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock.readLock().unlock();
}
}
public void write(){
try{
lock.writeLock().lock();
System.out.println("获得写锁线程:"+Thread.currentThread().getName()
+" "+System.currentTimeMillis());
Thread.sleep(3000);
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock.writeLock().unlock();
}
}
}
class Thread3A extends Thread{
private Service3 service3;
public Thread3A(Service3 service3) {
this.service3 = service3;
}
@Override
public void run() {
service3.read();
}
}
class Thread3B extends Thread{
private Service3 service3;
public Thread3B(Service3 service3) {
this.service3 = service3;
}
@Override
public void run() {
service3.write();
}
}
public class Test3 {
public static void main(String[] args) {
Service3 service3 = new Service3();
Thread3A thread3A = new Thread3A(service3);
thread3A.setName("A");
thread3A.start();
Thread3B thread3B = new Thread3B(service3);
thread3B.setName("B");
thread3B.start();
System.out.println("主线程结束时间:"+System.currentTimeMillis());
}
}
/*
运行结果:
主线程结束时间:1559366890979
获得读锁线程:A 1559366890980
获得写锁线程:B 1559366893981
*/
运行结果:
说明读写操作时是互斥的
- 写读互斥:将上面主线程里面的两个线程启动顺序改变一下:,先启动调用写操作的线程B: