Java多线程 ReentrantLock与Condition

Java多线程 ReentrantLock与Condition

文章目录

1、ReentrantLock

1.1 关系图

先来了解一下其关系图:
Java多线程 ReentrantLock与Condition
(1)首先ReentrantLock继承了AbstractQueuedSynchronizer;
(2)ReentrantLock实现的这个Lock接口;
(3)Sync实现了AbstractQueuedSynchronizer的tryRelease方法。NonfairSync和FairSync两个类继承自Sync,实现了lock方法,然后分别公平抢占和非公平抢占针对tryAcquire有不同的实现。

1.2 概念

ReentrantLock是一个可重入的互斥锁,又被称为“独占锁”。ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,只是代码写法上有点区别,ReentrantLock是一个表现在API层面的互斥锁(lock() 和 unlock() 方法配合try/finally语句来完成),synchronized表现为原生语法层面的互斥锁。不过,相比synchronized , ReentrantLock增加了一些高级功能,主要有以下3项:等待可中断,可实现公平锁,以及锁可以绑定多个条件

(1)等待可中断是指当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情。可中断特性对处理执行时间非常长的同步块很有帮助
(2)公平锁是指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁;而非公平锁则不保证这一点,在锁被释放时,任何一个等待锁的线程都有机会获得锁。synchronized中的锁是非公平的,ReentrantLock默认情况下也是非公平的,但可以通过带布尔值的构造函数要求使用公平锁。

(3)锁绑定多个条件是指一个ReentrantLock对象可以同时绑定多个Condition对象,而在synchronized中,锁对象的wait()和notify()或notifyAll()方法可以实现一个隐含的条件,如果要和多于一个的条件关联的时候,就不得不额外地添加一个锁,而ReentrantLock则无须这样做,只需要多次调用newCondition()方法即可。

常见的两个构造器

方法名称 描述
ReentrantLock() 创建一个 ReentrantLock的实例。
ReentrantLock(boolean fair) 创建一个特定锁类型(公平锁/非公平锁)的ReentrantLock的实例

一些常用的方法介绍

void lock()获得锁 如果锁不可用,则当前线程将被禁用以进行线程调度,并处于休眠状态,直到获取锁
void lockInterruptibly() 获取锁 如果可用并立即返回。如果锁不可用,那么当前线程将被禁用以进行线程调度,并且处于休眠状态,和lock()方法不同的是在锁的获取中可以中断当前线程(相应中断)。
Condition newCondition() 获取等待通知组件,该组件和当前的锁绑定,当前线程只有获得了锁,才能调用该组件的wait()方法,而调用后,当前线程将释放锁。
boolean tryLock() 只有在调用时才可以获得锁。如果可用,则获取锁定,并立即返回值为true;如果锁不可用,则此方法将立即返回值为false 。
boolean tryLock(long time, TimeUnit unit) 超时获取锁,当前线程在一下三种情况下会返回: 1. 当前线程在超时时间内获得了锁;2.当前线程在超时时间内被中断;3.超时时间结束,返回false.
void unlock() 释放锁。

1.3 可重入锁

实例代码:

import java.util.Calendar;
import java.util.concurrent.locks.ReentrantLock;

public class TestLock {

    private ReentrantLock lock = null;

    public TestLock() {
        // 创建*竞争的可重入锁
        lock = new ReentrantLock();
    }

    public static void main(String[] args) {

        TestLock tester = new TestLock();

        try{
            // 测试可重入,方法testReentry() 在同一线程中,可重复获取锁,执行获取锁后,显示信息的功能
            tester.testReentry();
            // 能执行到这里而不阻塞,表示锁可重入
            tester.testReentry();
            // 再次重入
            tester.testReentry();
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            // 释放重入测试的锁,要按重入的数量解锁,否则其他线程无法获取该锁。
            tester.getLock().unlock();
            tester.getLock().unlock();
            tester.getLock().unlock();
        }
    }

    public ReentrantLock getLock() {
        return lock;
    }
    public void testReentry() {
        lock.lock();

        Calendar now = Calendar.getInstance();

        System.out.println(now.getTime() + " " + Thread.currentThread().getName()
                + " get lock.");
    }
}

结果:

Sat Feb 29 11:15:42 CST 2020 main get lock.
Sat Feb 29 11:15:43 CST 2020 main get lock.
Sat Feb 29 11:15:43 CST 2020 main get lock.

注意:这里必须要手动地释放锁,确保完全释放该线程所获得的锁,否则会造成死锁。

1.4 公平锁与非公平锁

公平锁保证一个阻塞的线程最终能够获得锁,因为是有序的,所以总是可以按照请求的顺序获得锁。非公平锁意味着后请求锁的线程可能在其前面排列的休眠线程恢复前拿到锁,这样就有可能提高并发的性能。这是因为通常情况下挂起的线程重新开始与它真正开始运行,二者之间会产生严重的延时。因此非公平锁就可以利用这段时间完成操作。这是非公平锁在某些时候比公平锁性能要好的原因之一。

锁Lock分为“公平锁”和“非公平锁”,公平锁表示线程获取锁的顺序是按照线程加锁的顺序来分配的,即先来先得的FIFO先进先出顺序。而非公平锁就是一种获取锁的抢占机制,是随机获得锁的,和公平锁不一样的就是先来的不一定先得到锁,这个方式可能造成某些线程一直拿不到锁,结果也就是不公平的了。

实例代码:

package chat6;

import java.util.concurrent.locks.ReentrantLock;
public class Service {

    private ReentrantLock lock ;

    public Service(boolean isFair) {
        lock = new ReentrantLock(isFair);
    }

    public void serviceMethod() {
        try {
            lock.lock();
            System.out.println("ThreadName=" + Thread.currentThread().getName()
                    + " 获得锁定");
        } finally {
            lock.unlock();
        }
    }
}
package chat6;


public class TestFairLock {

    public static void main(String[] args) throws InterruptedException {
        final Service service = new Service(true);  //改为false就为非公平锁了
        Runnable runnable = new Runnable() {

            public void run() {
                System.out.println("**线程: " + Thread.currentThread().getName()
                        +  " 运行了 " );
                service.serviceMethod();
            }
        };

        Thread[] threadArray = new Thread[10];

        for (int i=0; i<5; i++) {
            threadArray[i] = new Thread(runnable);
        }
        for (int i=0; i<5; i++) {
            threadArray[i].start();
        }
    }
}

公平锁结果:可以看出,打印是有顺序的

**线程: Thread-0 运行了 
ThreadName=Thread-0 获得锁定
**线程: Thread-1 运行了 
ThreadName=Thread-1 获得锁定
**线程: Thread-2 运行了 
ThreadName=Thread-2 获得锁定
**线程: Thread-3 运行了 
ThreadName=Thread-3 获得锁定
**线程: Thread-4 运行了 
ThreadName=Thread-4 获得锁定

非公平锁的结果:顺序杂乱无章

**线程: Thread-1 运行了 
ThreadName=Thread-1 获得锁定
**线程: Thread-0 运行了 
ThreadName=Thread-0 获得锁定
**线程: Thread-4 运行了 
ThreadName=Thread-4 获得锁定
**线程: Thread-3 运行了 
ThreadName=Thread-3 获得锁定
**线程: Thread-2 运行了 
ThreadName=Thread-2 获得锁定

1.5 小节

ReentrantLock 不好与需要注意的地方:
(1)lock 必须在 finally 块中释放。否则,如果受保护的代码将抛出异常,锁就有可能永远得不到释放!
(2)当 JVM 用 synchronized 管理锁定请求和释放时,JVM 在生成线程转储时能够包括锁定信息。这些对调试非常有价值,因为它们能标识死锁或者其他异常行为的来源。 Lock 类只是普通的类,JVM 不知道具体哪个线程拥有 Lock 对象。

2、Condition接口

2.1 Condition 简介

(1)synchronized关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例(即对象监视器),线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活

(2)而synchronized关键字就相当于整个Lock对象中只有一个Condition实例,所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题,而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程

Condtion常用方法:

方法名称 描述
void await() 相当于Object类的wait方法
boolean await(long time, TimeUnit unit) 相当于Object类的wait(long timeout)方法
signal() 相当于Object类的notify方法
signalAll() 相当于Object类的notifyAll方法

注意:
(1)调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用
(2)Conditon中的await()对应Object的wait(),Condition中的signal()对应Object的notify(),Condition中的signalAll()对应Object的notifyAll()

2.2 实例一:使用ReentrantLock来实现生产者消费者问题

package chat6;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ProAndCom {
    private static Integer count = 0;
    private final Integer FULL = 10;
    final Lock lock = new ReentrantLock();
    final Condition NotFull = lock.newCondition();
    final Condition NotEmpty = lock.newCondition();

    class Producer implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(3000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                lock.lock();
                try {
                    while (count == FULL) {
                        try {
                            NotFull.await();
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                    }
                    count++;
                    System.out.println(Thread.currentThread().getName()
                            + "生产者生产,目前总共有" + count);
                    NotEmpty.signal();
                } finally {
                    lock.unlock();
                }

            }
        }
    }

    class Consumer implements Runnable {

        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                }
                lock.lock();
                try {
                    while (count == 0) {
                        try {
                            NotEmpty.await();
                        } catch (Exception e) {
                            // TODO: handle exception
                            e.printStackTrace();
                        }
                    }
                    count--;
                    System.out.println(Thread.currentThread().getName()
                            + "消费者消费,目前总共有" + count);
                    NotFull.signal();
                } finally {
                    lock.unlock();
                }

            }

        }

    }

    public static void main(String[] args) throws Exception {
        ProAndCom hosee = new ProAndCom();
        new Thread(hosee.new Producer()).start();
        new Thread(hosee.new Consumer()).start();
        new Thread(hosee.new Producer()).start();
        new Thread(hosee.new Consumer()).start();

        new Thread(hosee.new Producer()).start();
        new Thread(hosee.new Consumer()).start();
        new Thread(hosee.new Producer()).start();
        new Thread(hosee.new Consumer()).start();
    }

}

运行结果:
Java多线程 ReentrantLock与Condition

2.3 实例二:实现顺序执行线程

package com.demo.test;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class ConditionService {

    // 通过nextThread控制下一个执行的线程
    private static int nextThread = 1;
    private ReentrantLock lock = new ReentrantLock();
    // 有三个线程,所以注册三个Condition
    Condition conditionA = lock.newCondition();
    Condition conditionB = lock.newCondition();
    Condition conditionC = lock.newCondition();

    public void excuteA() {
        try {
            lock.lock();
            while (nextThread != 1) {
                conditionA.await();
            }
            System.out.println(Thread.currentThread().getName() + " 工作");
            nextThread = 2;
            conditionB.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void excuteB() {
        try {
            lock.lock();
            while (nextThread != 2) {
                conditionB.await();
            }
            System.out.println(Thread.currentThread().getName() + " 工作");
            nextThread = 3;
            conditionC.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void excuteC() {
        try {
            lock.lock();
            while (nextThread != 3) {
                conditionC.await();
            }
            System.out.println(Thread.currentThread().getName() + " 工作");
            nextThread = 1;
            conditionA.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

启动类代码:

package com.demo.test;

/**
 * 线程按顺序执行
 * @author lixiaoxi
 *
 */
public class ConditionApplication {

    private static Runnable getThreadA(final ConditionService service) {
        return new Runnable() {
            @Override
            public void run() {
                for (int i=0;i<10;i++) {
                    service.excuteA();
                }
            }
        };
    }

    private static Runnable getThreadB(final ConditionService service) {
        return new Runnable() {
            @Override
            public void run() {
                for (int i=0;i<10;i++) {
                    service.excuteB();
                }
            }
        };
    }

    private static Runnable getThreadC(final ConditionService service) {
        return new Runnable() {
            @Override
            public void run() {
                for (int i=0;i<10;i++) {
                    service.excuteC();
                }
            }
        };
    }

    public static void main(String[] args) throws InterruptedException{
        ConditionService service = new ConditionService();
        Runnable A = getThreadA(service);
        Runnable B = getThreadB(service);
        Runnable C = getThreadC(service);

        new Thread(A, "A").start();
        new Thread(B, "B").start();
        new Thread(C, "C").start();
    }

}

运行结果:
Java多线程 ReentrantLock与Condition

Java多线程 ReentrantLock与ConditionJava多线程 ReentrantLock与Condition 仙辉 发布了25 篇原创文章 · 获赞 15 · 访问量 2074 私信 关注
上一篇:MySQL配置文件mysql.ini参数详解


下一篇:MySql数据库列表数据分页查询、全文检索API在crudapi系统中零代码实现