Java多线程编程六(使用Lock)
可重入锁ReentrantLock
使用ReentrantLock方法
lock()
:锁定unlock()
:解除锁定int getHoldCount()
:返回当前线程保持此锁定的个数,也就是调用lock()
方法的次数int getQueueLength()
:返回正在等待获取此锁定的线程估计数,例如:8个线程,3个线程调用了await()
方法,那么在调用此方法后返回的值是5,说明有5个线程同时在等待lock的释放int getWaitQueueLength()
:顾名思义,其作用是返回等待与此锁定相关的给定条件Condition的线程估计数。例如:8个线程,每个线程都调用了同一个Condition的await()
方法,则调用此方法返回的值是8boolean hasQueuedThread()
:查询指定线程是否正在等待获取此锁定ReentrantLock.hasQueuedThread(thread)
boolean hasWaiters()
:查询是否有线程正在等待与此锁定有关的Condition条件boolean isFair()
:判断是否是公平锁boolean isHeldByCurrentThread()
:查询当前线程是否保持此锁定boolean isLocked()
:查询此锁定是否由任意线程保持void lockInterruptibly()
:如果当前线程未被中断,则获取锁定,如果已经被中断则出现异常boolean tryLock()
:仅在调用时锁定未被另一个线程保持的情况下,才获取该锁定boolean tryLock(long timeout, TimeUnit unit)
:如果锁定在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁定
使用ReentrantLock实现同步效果
public class MyServer {
private ReentrantLock lock = new ReentrantLock();
public void printReentrantLock() {
try {
lock.lock();//获取锁
for (int i = 0; i < 5; i++) {
System.out.println("ThreadName=" + Thread.currentThread().getName() + ",i=" + i);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();//释放锁
}
}
public static void main(String[] args) {
final MyServer myServer = new MyServer();
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(new Runnable() {
public void run() {
myServer.printReentrantLock();
}
});
thread.setName(String.valueOf(i));
thread.start();
}
}
}
运行结果为:
ThreadName=0,i=0
ThreadName=0,i=1
ThreadName=0,i=2
ThreadName=0,i=3
ThreadName=0,i=4
ThreadName=1,i=0
ThreadName=1,i=1
ThreadName=1,i=2
ThreadName=1,i=3
ThreadName=1,i=4
ThreadName=2,i=0
ThreadName=2,i=1
ThreadName=2,i=2
ThreadName=2,i=3
ThreadName=2,i=4
ThreadName=3,i=0
ThreadName=3,i=1
ThreadName=3,i=2
ThreadName=3,i=3
ThreadName=3,i=4
ThreadName=4,i=0
ThreadName=4,i=1
ThreadName=4,i=2
ThreadName=4,i=3
ThreadName=4,i=4
由结果分析知:当前线程获取锁对象后其他线程呈阻塞状态,直到当前线程打印完毕释放锁,其他线程才能获取到锁进行打印。
这个主要进行单个方法同一个锁多线程调用的同步效果,下面来看一下多个线程交叉调用不同方法同一个锁的例子:
public class MyServer {
private ReentrantLock lock = new ReentrantLock();
public void methodA() {
try {
lock.lock();//获取锁
System.out.println("method A begin ThreadName=" + Thread.currentThread().getName() + " time=" + System.currentTimeMillis());
methodB();//调用methodB
Thread.sleep(5000);
System.out.println("method A end ThreadName=" + Thread.currentThread().getName() + " time=" + System.currentTimeMillis());
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void methodB() {
try {
lock.lock();//获取锁
System.out.println("method B begin ThreadName="+Thread.currentThread().getName()+" time="+System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("method B end ThreadName="+Thread.currentThread().getName()+" time="+System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
String[] names = {"A", "AA", "B", "BB"};
final MyServer myServer = new MyServer();
Thread[] threads = new Thread[4];
for (int i = 0; i < 2; i++) {
threads[i] = new Thread(new Runnable() {
public void run() {
myServer.methodA();
}
});
}
for (int i = 2; i < 4; i++) {
threads[i] = new Thread(new Runnable() {
public void run() {
myServer.methodB();
}
});
}
for (int i = 0; i < 4; i++) {
threads[i].setName(names[i]);
threads[i].start();
}
}
method A begin ThreadName=A time=1551406905681
method B begin ThreadName=A time=1551406905681
method B end ThreadName=A time=1551406910685
method A end ThreadName=A time=1551406915689
method A begin ThreadName=AA time=1551406915689
method B begin ThreadName=AA time=1551406915690
method B end ThreadName=AA time=1551406920691
method A end ThreadName=AA time=1551406925691
method B begin ThreadName=B time=1551406925691
method B end ThreadName=B time=1551406930693
method B begin ThreadName=BB time=1551406930693
method B end ThreadName=BB time=1551406935698
分析结果:在线程A获取锁时其他线程呈阻塞状态直至线程A执行完毕且释放锁,其他线程才可以获取到锁,同时在线程A获取锁执行过程中又成功调用了methodB,这些说明reentrantlock
和synchronized
关键字一样线程之间是同步顺序执行的,也都具有锁重入特性。
使用Condition实现等待/通知
关键字synchronized
与wait()
和notify()/notifyAll()
方法相结合可以实现等待/通知模式,类ReentrantLock
也可以试想同样的功能,但需要借助于Condition
对象。相对于notify
和notifyAll
,Condition
具有跟高的灵活性,在使用notify
和notifyAll
时,被通知的线程是由JVM随机选择的,而ReentrantLock
结合Condition
类是可以“选择性通知的”。
Condition中通知/等待方法
await()
:相当于Object类中的wait()
方法await(long time, TimeUtil unit)
:相当于Object类中的wait(long timeout)
方法signal()
:相当于Object类中的notify()
方法signalAll()
:相当于Object类中的notifyAll()
方法
简单使用
注意:调用condition.await()
之前需要执行lock.lock()
来获取同步监视器。
public class MyServer {
private ReentrantLock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void await() {
try {
lock.lock();
System.out.println("await 时间为 " + System.currentTimeMillis());
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void signal() {
try {
lock.lock();
System.out.println("signal 时间为 " + System.currentTimeMillis());
condition.signal();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
final MyServer myServer = new MyServer();
new Thread(new Runnable() {
public void run() {
myServer.await();
}
}).start();
Thread.sleep(3000);
myServer.signal();
}
}
运行结果为:
await 时间为 1551420692029
signal 时间为 1551420695031
使用多个Condition实现“选择性”通知线程
public class MyServer {
private ReentrantLock lock = new ReentrantLock();
private Condition conditionA = lock.newCondition();
private Condition conditionB = lock.newCondition();
@SuppressWarnings("all")
public void awaitA() {
try {
lock.lock();
System.out.println("begin await A 时间为 " + System.currentTimeMillis());
conditionA.await();
System.out.println("end await A 时间为 " + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
@SuppressWarnings("all")
public void awaitB() {
try {
lock.lock();
System.out.println("begin await B 时间为 " + System.currentTimeMillis());
conditionB.await();
System.out.println("end await B 时间为 " + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
@SuppressWarnings("all")
public void signalAll_A() {
try {
lock.lock();
System.out.println(" signalall A 时间为 " + System.currentTimeMillis());
conditionA.signalAll();
} finally {
lock.unlock();
}
}
@SuppressWarnings("all")
public void signalAll_B() {
try {
lock.lock();
System.out.println(" signalall B 时间为 " + System.currentTimeMillis());
conditionB.signalAll();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
final MyServer myServer = new MyServer();
Thread threadA = new Thread(new Runnable() {
public void run() {
myServer.awaitA();
}
});
threadA.setName("A");
threadA.start();
Thread threadB = new Thread(new Runnable() {
public void run() {
myServer.awaitB();
}
});
threadB.setName("B");
threadB.start();
Thread.sleep(3000);
myServer.signalAll_B();
}
}
在上述代码中我们定义了两个Condition,分别用于两个线程的等待和唤醒,而我们只唤醒线程B而线程A则继续等待。结果如下:
begin await A 时间为 1551424032113
begin await B 时间为 1551424032116
signalall B 时间为 1551424035121
end await B 时间为 1551424035122
公平锁与非公平锁
公平锁就是线程获取锁的顺序是按照线程加锁的顺序来分配的,即先来先得的FIFO先进先出的顺序。非公平锁就是随机获取锁的,属于一种枪占机制。先来不一定先得到,谁抢到属于谁,所以是不公平的。
通过构造方法ReetrantLock(boolean isFair)
来创建对应公平锁或不公平锁。无参默认为非公平锁。
使用ReentrantReadWriteLock类
使用ReetrantLock
具有完全互斥排他的效果,即同一时间只有一个线程能够执行Reentrantlock.lock()
方法后的代码。虽然这样能保持实例变量的线程安全性,但是效率确实非常低的。然而使用读写锁ReentrantReadWriteLock
类,可以提高运行效率,在不需要操作实例变量的情况下,可以使用读写锁ReentrantReadWriteLock
来提高任务的执行效率。
读写锁ReentrantReadWriteLock
有两个锁,一个是读操作的锁即共享锁,一个是写操作锁即排它锁。在多个读锁之间不互斥,读锁和写锁互斥,写锁和写锁互斥。在多个线程中,如果没有线程进行写操作时,可以有多个线程同时进行读操作;而其中一个线程正在写操作时,其他线程的读操作或写操作都只能等当前的写线程执行完毕。
读写或写读互斥
public class MyServer {
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void read() {
try {
lock.readLock().lock();
System.out.println("获得读锁:"+Thread.currentThread().getName()+" "+System.currentTimeMillis());
Thread.sleep(10000);
} 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(10000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.writeLock().unlock();
}
}
public static void main(String[] args) throws InterruptedException {
final MyServer myServer = new MyServer();
Thread threadA = new Thread(new Runnable() {
public void run() {
myServer.read();
}
});
threadA.setName("A");
threadA.start();
Thread threadB = new Thread(new Runnable() {
public void run() {
myServer.write();
}
});
threadB.setName("B");
threadB.start();
Thread threadC = new Thread(new Runnable() {
public void run() {
myServer.read();
}
});
threadC.setName("C");
threadC.start();
}
}
结果如下:
获得读锁:A 1551509003778
获得读锁:A 1551509013785
获得读锁:A 1551509023789
由结果可知,读写和写读是互斥的
读读共享
更改上面的main方法:
public static void main(String[] args) throws InterruptedException {
final MyServer myServer = new MyServer();
Thread threadA = new Thread(new Runnable() {
public void run() {
myServer.read();
}
});
threadA.setName("A");
threadA.start();
Thread threadB = new Thread(new Runnable() {
public void run() {
myServer.read();
}
});
threadB.setName("B");
threadB.start();
}
结果如下:
获得读锁:A 1551509377678
获得读锁:B 1551509377678
由结果可知:读和读共享
写写互斥
更改上面的main方法:
public static void main(String[] args) throws InterruptedException {
final MyServer myServer = new MyServer();
Thread threadA = new Thread(new Runnable() {
public void run() {
myServer.write();
}
});
threadA.setName("A");
threadA.start();
Thread threadB = new Thread(new Runnable() {
public void run() {
myServer.write();
}
});
threadB.setName("B");
threadB.start();
}
结果如下:
获得写锁:A 1551509613785
获得写锁:B 1551509623788
由结果知:写写互斥
版权声明:
作者:Joe.Ye
链接:https://www.appblog.cn/index.php/2023/02/13/java-multi-threag-programming-lock/
来源:APP全栈技术分享
文章版权归作者所有,未经允许请勿转载。
共有 0 条评论