这篇博客记录了 Lock,ReentrantLock,ReentrantReadWriteLock 类的使用以及其一些 API:
码字不易~~ 另外《java 多线程编程核心技术》这本书读着很爽
前言说明: 之前为了解决多线程时的非线程安全问题, 使用的是 synchronized. 接下来记录的是他的升级版本 ReentrantLock, 更加灵活, 可控性更高, 而 ReentrantReadWriteLock 类是对 ReentrantLock 类的补充, 能够在某些条件之间之下提交效率
下面先来看下都有哪些 API, 以及和 synchronized 之间是怎样对应的吧.
以前使用锁完成同步是将同步代码块写在 synchronized 之内, 现在我们使用
Lock lock = new ReentrantLock();
来声明一个锁, 他有这两个方法
lock.lock(); 和 lock.unlock(); 这两个是配套的, 在其之间的代码就是同步代码块.
和之前一样, lock() 方法会让当前线程持有对象监听器, 具体规则之类的和 synchronized 也一样,
比如下面的例子, MyService 有一段代码上锁, 自定义线程类调用它
MyService.java
package 第四章;
- import java.util.concurrent.locks.Lock;
- import java.util.concurrent.locks.ReentrantLock;
- public class MyService {
- private Lock lock = new ReentrantLock();
- public void testMethod(){
- lock.lock();
- for(int i=0;i<5;i++){
- System.out.println(i+"线程:"+Thread.currentThread().getName());
- }
- lock.unlock();
- }
- }
- View Code
MyThread.java
package 第四章;
- public class MyThread extends Thread {
- private MyService myService;
- public MyThread(MyService myService) {
- super();
- this.myService = myService;
- }
- public void run(){
- myService.testMethod();
- }
- }
- View Code
test.java
package 第四章;
- public class test {
- public static void main(String[] args){
- MyThread[] threads = new MyThread[5];
- MyService myService = new MyService();
- for(int i=0;i<5;i++){
- threads[i] = new MyThread(myService);
- threads[i].start();
- }
- }
- }
- View Code
运行结果:
可以看到线程之间是同步执行的, 当然前提是同一个 MyService 对象.
之前的 wait/notify, 用 Condition 对象来替换:
效率提高的地方以及原因:
Condition 对象可以对同一个锁声明多个, 相当于每当让线程等待时, 他都有自己的唤醒 condition, 换句话说, 每一个线程都可以注册一个 Condition, 这样当我们唤醒线程的时候, 就可以唤醒指定的线程, 比如之前的生产者消费者模型之中的假死现象, 我们使用过 notifyAll() 来解决的, 但是这种方法唤醒了所有的线程, 让所有线程都去争抢 CPU, 但是我们事实上指向唤醒异类线程, 并不想唤醒同类, 全部唤醒的话, 效率是一个问题. 那么现在, 给每一个线程都注册
一个 Condition, 这样子唤醒时候, 我们就可以唤醒指定的线程, 提高了效率, 也更加灵活.
下面的是一个简单的 await/signal 例子, 展示了基本的使用: await 类似之前的 wait,signal 类似于 notify:signalAll() 唤醒全部
更改之前的 MyService.java
condition.await() 让线程阻塞, condition.signal() 随机唤醒一个由当前 condition 注册的线程
package 第四章;
- import java.util.concurrent.locks.Lock;
- import java.util.concurrent.locks.ReentrantLock;
- import java.util.concurrent.locks.Condition;
- public class MyService {
- private Lock lock = new ReentrantLock();
- public Condition condition = lock.newCondition();
- public void testMethod(){
- try{
- lock.lock();
- System.out.println("即将开始循环");
- condition.await();
- for(int i=0;i<2;i++){
- System.out.println(i+"线程:"+Thread.currentThread().getName());
- }
- }catch (InterruptedException e){
- e.printStackTrace();
- }finally {
- lock.unlock();
- }
- }
- public void signal(){
- try{
- lock.lock();
- this.condition.signal();
- System.out.println("唤醒了一个线程");
- }finally {
- lock.unlock();
- }
- }
- }
- View Code
MyThread.java 不变
test.java: 先让线程全部阻塞, 然后调用自定义的 signal 方法唤醒线程,
package 第四章;
- public class test {
- public static void main(String[] args){
- MyThread[] threads = new MyThread[5];
- MyService myService = new MyService();
- for(int i=0;i<5;i++){
- threads[i] = new MyThread(myService);
- threads[i].start();
- }
- try{
- Thread.sleep(1000);
- myService.signal();
- }catch (InterruptedException e){
- e.printStackTrace();
- }
- }
- }
- View Code
运行结果如下:
可以看到, 我们成功唤醒了一个线程.
下面的例子唤醒了一个指定的线程
MyService.java: 根据当前线程的名字让指定的 Condition 对象等待, 并书写两个唤醒不同的 Condition 对象注册的线程
package 第四章;
- import java.util.concurrent.locks.Lock;
- import java.util.concurrent.locks.ReentrantLock;
- import java.util.concurrent.locks.Condition;
- public class MyService {
- private Lock lock = new ReentrantLock();
- public Condition conditionA = lock.newCondition();
- public Condition conditionB = lock.newCondition();
- public void testMethod(){
- try{
- lock.lock();
- System.out.println("线程"+Thread.currentThread().getName()+"等待中...");
- if(Thread.currentThread().getName().equals("A"))
- conditionA.await();
- else
- conditionB.await();
- for(int i=0;i<2;i++){
- System.out.println(i+"线程:"+Thread.currentThread().getName());
- }
- }catch (InterruptedException e){
- e.printStackTrace();
- }finally {
- lock.unlock();
- }
- }
- public void signalA(){
- try{
- lock.lock();
- this.conditionA.signal();
- System.out.println("唤醒了 A 线程");
- }finally {
- lock.unlock();
- }
- }
- public void signalB(){
- try{
- lock.lock();
- this.conditionB.signal();
- System.out.println("唤醒了 B 线程");
- }finally {
- lock.unlock();
- }
- }
- }
- View Code
test.java, 启动 A,B 两个线程, 只唤醒 A 线程
package 第四章;
- public class test {
- public static void main(String[] args){
- MyService myService = new MyService();
- MyThread myThreadA = new MyThread(myService);
- myThreadA.setName("A");
- MyThread myThreadB = new MyThread(myService);
- myThreadB.setName("B");
- myThreadA.start();
- myThreadB.start();
- try{
- Thread.sleep(1000);
- myService.signalA();
- }catch (InterruptedException e){
- e.printStackTrace();
- }
- }
- }
- View Code
运行结果:
根据代码, 我们可以看到可以通过不同 Condition 对象来唤醒指定的线程.
用处:
1. 可以想到, 如果用 Lock 来解决之前的多消费多生产者时的假死问题, 我们可以将生产者统一注册一个 Condition, 消费者统一注册一个 Condition, 每一次唤醒对方的 Condition, 这样子就不会出现连续唤醒同类导致假死的情况了, 并且可以避免唤醒所有线程, 导致效率低下.
2. 我们也可以按照我们想要的顺序进行唤醒, 只要你注册了正确的 Condition 对象
公平锁和非公平锁:
比较好理解, 公平锁相当于一个队列, 先进先出, 先运行的线程先拿到锁, 后运行的后拿到锁, 按照顺序来, 非公平锁就是锁的抢占是随机的, 没有顺序.
默认是非公平锁, 创建 Lock 时加上 true 参数即为公平锁:
Lock lock =new ReentrantLock(true);
下面介绍一些 ReentrantLock 的 API,
一般在一些定制化的情况可能会用到, emmm, 这块先了解一下, 知道有这些就行, emmm, 说实话目前我感觉这个没啥用, 有个印象, 不过注意使用这些 API 使, 必须以下面这种方式 new 对象
ReentrantLock lock = new ReentrantLock();
(lock.)GetHoldCount(): 查询当前线程有保持着几个 lock 锁, 简单来讲就是当前线程调用了几次 lock() 方法
GetQueueLength(): 有多少个线程在等待获取当前锁, 可以理解为有多少个没有拿到当前锁,
getWaitQueueLength(Condition condition): 有多少个线程处于阻塞状态, 并且是执行了参数 Condition 对象所对应的 await() 方法导致阻塞的.
hasQueuedThread(Thread thread): 查询指定的线程是否正在等待获取当前锁
hasQueuedThreads(): 查询是否有线程正在等待获取当前锁
hasWaiters(Condition): 查询是否有线程是由于调用了参数 Condition.await() 导致阻塞的.
isHeldByCurrentThread(): 查询当前线程是否持有当前锁
isLocked(): 当前锁是否被某个线程持有
awaitUninterruptibly(): 这也是一种让当前线程阻塞的方法, 不过 await 调用之后如果再使用 Interrupt 等代码阻塞当前进程会报异常, 但是这个不会, 相当于让当前线程变成可以阻塞的线程,,,, 不懂有撒用
awaitUntil(Date): 阻塞当前线程, 如果在指定时间之前还没有被唤醒, 则唤醒他. 参数也可以传 Calendar.getTime(),Calendar 类用于处理时间
ReentrantReadWriteLock 类
之前的 ReentrantLock 相当于同一时间只有一个线程在执行代码. 但是在不涉及更改实例变量的代码之中, 我们可以允许异步运行来加快效率, 而一些涉及到更改实例变量的代码, 这时候同步执行 (这时候异步可能出现非线程安全), 这样可以在一定程度上加快效率, 这就是这个类的作用.
简单来说, 我们一般有读写两个操作, 如果多个线程执行读操作, ok, 异步执行, 如果多个线程有的执行读, 有的写, ok, 同步执行, 这个类就是自动完成这个事情, 你只需要在锁时使用不同类型的锁就行.
下面是一个例子, 读读异步 (其他全部同步):
ReadAndWrite.java 代表具体的操作, 读, 写, 输出当前操作以及时间, sleep() 模拟操作耗费的时间
package 第四章;
- import java.util.concurrent.locks.ReentrantReadWriteLock;
- public class ReadAndWrite {
- private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
- public void read(){
- try{
- lock.readLock().lock();
- System.out.println("读操作"+System.currentTimeMillis());
- Thread.sleep(1000);
- }catch (InterruptedException e){
- e.printStackTrace();
- }finally {
- lock.readLock().unlock();
- }
- }
- public void write(){
- try{
- lock.writeLock().lock();
- System.out.println("写操作"+System.currentTimeMillis());
- Thread.sleep(1000);
- }catch (InterruptedException e){
- e.printStackTrace();
- }finally {
- lock.writeLock().unlock();
- }
- }
- }
- View Code
MyThread2.java: 里面有两个 java 类, 一个执行读操作, 一个写操作
package 第四章;
- class MyThreadRead extends Thread{
- private ReadAndWrite readAndWrite;
- public MyThreadRead(ReadAndWrite readAndWrite) {
- this.readAndWrite = readAndWrite;
- }
- public void run(){
- this.readAndWrite.read();
- }
- }
- class MyThreadWrite extends Thread{
- private ReadAndWrite readAndWrite;
- public MyThreadWrite(ReadAndWrite readAndWrite) {
- this.readAndWrite = readAndWrite;
- }
- public void run(){
- this.readAndWrite.write();
- }
- }
- View Code
test.java: 创建三个读线程
package 第四章;
- public class test {
- public static void main(String[] args){
- ReadAndWrite readAndWrite = new ReadAndWrite();
- MyThreadRead reads[] = new MyThreadRead[3];
- for(int i=0;i<3;i++) {
- reads[i] = new MyThreadRead(readAndWrite);
- reads[i].start();
- }
- }
- }
- View Code
运行结果:
可以看到, 三个读操作时同时执行的.
下面更改 test.java, 创建三个读线程, 三个写线程:
test.java
package 第四章;
- public class test {
- public static void main(String[] args){
- ReadAndWrite readAndWrite = new ReadAndWrite();
- MyThreadWrite writes[] = new MyThreadWrite[3];
- for(int i=0;i<3;i++) {
- writes[i] = new MyThreadWrite(readAndWrite);
- writes[i].start();
- }
- MyThreadRead reads[] = new MyThreadRead[3];
- for(int i=0;i<3;i++) {
- reads[i] = new MyThreadRead(readAndWrite);
- reads[i].start();
- }
- }
- }
- View Code
运行:
可以看到, 写操作之间是互斥的, 相当于同步, 一个一个执行的, 读的时候就是异步的,
,, 好嘞, 就演示这几个, 其他的都同理, 只有读读是异步的, 读写同步, 你可以交替着 start 看一下, 如下:
好滴, 第四章就这些暂时..
来源: https://www.cnblogs.com/eenio/p/11390950.html