多线程八 Lock (一)

简介: 多线程八 Lock (一)

前面我们可以使用synchronized关键字来实现线程之间的同步互斥,lock接口同样也是在JDK1.5中提出,同样是解决线程安全性问题的另一种解决方案,而且它更强大,更灵活本片博客介绍对其展开介绍;


Lock接口有如下几个实现类:#


  • ReentrantLock--JDK实现的锁
  • ReentrantReadWritrLock.ReadLock
  • ReentrantReadWriteLock.WriteLock


打个例子#


public class Demo01 {
    private int i=0;
 Lock Mylock = new ReentrantLock();
  public int add() throws InterruptedException {
      try{
          Mylock.lock();
          i++;
          return i;
      } finally {
          Mylock.unlock();
      }
      }


如上代码 i++ 被 Mylock.lock()和 Mylock.unlock()围住,显示的获取和释放锁,具有同步性...


其中,ReentrantLock一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。

Lock 与 Synchronized 相比较,显而易见的就是,Lock需要显示的去获取锁,释放锁,比较繁琐,但是繁琐带来了更大好处,让代码更灵活...Lock是对Synchronized的封装...


比如:

  1. 在控制锁的获取和释放,以及何时何地获取释放
  2. 使用Lock,可以很方便的实现锁的公平性ReentrantLock(boolean fair)
  3. 强大的API
  • 非阻塞获取锁:tryLock()
  • 可中断式获取线程acquireSharedInterruptibly(int arg)
  • 超时获取线程tryAcquireSharedNanos(int arg , long nanosTimeout)


一 . Conditon&Reentrantlock#


1. Condition实现正确的通知/等待#


  • 注意点,一定要在condition.wait()方法调用之前,使用lock.lock()方法获取对象同步锁,否则抛出异常


public void waitMethod(){
        try{
            lock.lock();
            System.out.println(Thread.currentThread().getName()+"等待了..."+System.currentTimeMillis());
            try {
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }finally {
            lock.unlock();
        }
    }
    public void signalMethod(){
        try{
            lock.lock();
            System.out.println(Thread.currentThread().getName()+"被唤醒了..."+System.currentTimeMillis());
            condition.signal();
        }finally {
            lock.unlock();
        }
    }
    public static void main(String[] args) {
        demo1 demo1 = new demo1();
        new Thread(()->{
            demo1.waitMethod();
        }).start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        demo1.signalMethod();
    }
}



运行结果:

Thread-0等待了...1549450097987
main被唤醒了...1549450099988


2 使用多个Condition实现,通知部分线程#


接下来就是重头戏了Condition控制通知指定的线程醒来


public void waitMethod(){
    try{
        lock.lock();
        System.out.println(Thread.currentThread().getName()+"等待了..."+System.currentTimeMillis());
        try {
            condition.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }finally {
        lock.unlock();
    }
}
public void signalMethod(){
    try{
        lock.lock();
        System.out.println(Thread.currentThread().getName()+"被唤醒了..."+System.currentTimeMillis());
        condition.signal();
    }finally {
        lock.unlock();
    }
}
public static void main(String[] args) {
    demo1 demo1 = new demo1();
    new Thread(()->{
        demo1.waitMethod();
    }).start();
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    demo1.signalMethod();
}
}


运行结果:


Thread-0等待了...1549450097987
main被唤醒了...1549450099988


接下来就是重头戏了Condition控制通知指定的线程醒来


/*
* 使用多个Condition, 唤醒指定的线程
* */
public class demo2 {
private Lock lock = new ReentrantLock();
private Condition conditionA = lock.newCondition();
private Condition conditionB = lock.newCondition();
public void waitA(){
try{
 lock.lock();
 System.out.println(Thread.currentThread().getName()+"等待了..."+System.currentTimeMillis());
 try {
     conditionA.await();
     System.out.println(Thread.currentThread().getName()+"await之后的代码..."+System.currentTimeMillis());
 } catch (InterruptedException e) {
     e.printStackTrace();
 }
}finally {
 lock.unlock();
}
}
public void waitB(){
try{
    lock.lock();
    System.out.println(Thread.currentThread().getName()+"等待了..."+System.currentTimeMillis());
    try {
        conditionB.await();
        System.out.println(Thread.currentThread().getName()+"await之后的代码..."+System.currentTimeMillis());
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}finally {
    lock.unlock();
}
}
public void signalAALL(){
try{
 lock.lock();
 conditionA.signalAll();
 System.out.println(Thread.currentThread().getName()+" 执行唤醒操作. ."+System.currentTimeMillis());
}finally {
 lock.unlock();
}
}
public void signalBBLL(){
try{
    lock.lock();
    System.out.println(Thread.currentThread().getName()+"  被唤醒了.."+System.currentTimeMillis());
    conditionB.signalAll();
}finally {
    lock.unlock();
}
}
public static void main(String[] args) {
demo2 demo2 = new demo2();
ExecutorService executorService = Executors.newCachedThreadPool();
  executorService.execute(new Runnable() {
        @Override
        public void run() {
         demo2.waitA();
        // demo2.waitB();
        }
    });
try {
    Thread.sleep(2000);
} catch (InterruptedException e) {
    e.printStackTrace();
}
demo2.signalAALL();
System.out.println("mian 线程结束...");
}
}


结果:


pool-1-thread-1等待了...1549459953814
main 执行唤醒操作. .1549459955805
mian 线程结束...
pool-1-thread-1await之后的代码...1549459955805


  • 通过结果可以看到,ReentrantLock对象,可以唤醒指定种类的线程,使用个Condition让线程等待,就用哪个Condition去把它唤醒


公平锁与非公平锁#


Lock锁分为公平锁和非公平锁,所谓公平锁,就是表示线程获取锁的顺序,是按照线程加锁的顺序来实现的,也就是FIFO的顺序先进先出的顺序,而非公平锁描述的则是一种锁的随机抢占机制,还可能会导致一些线程根本抢不着锁而被饿死,结果就是不公平了


  • ReentrantLock支持公平锁


ReentrantLock()

创建一个 ReentrantLock 的实例。


构造方法名 简介
ReentrantLock(boolean fair) 创建一个具有给定公平策略的 ReentrantLock。


ReentrantLock几组常用API#


第一组:


方法名 作用
int getHoldCount() 查询当前线程保存此锁的个数,(执行lock.lock()的次数),即重入的次数
int getQueueLength() 返回**正在等待获取此锁(调用该方法的锁)的线程的个数,比如有五条线程已经准备就绪了,其中一条线程首先执行lock.await()方法,紧接着调用该方法,获取到的返回值为4,说明有四条线程正在等待此lock
int getWaitQueueLength(Condition condition) 返回正在等待和此锁相关的condition的线程数


第二组:


方法名 作用
boolean hasQueuedThread(Thread thread) 判断当前线程是否正在等到获取到此锁
boolean hasQueuedThreads() 在所有的线程中查询,是否有线程正在等待此所的锁定
boolean hasWaiters(Condition condition) 判断是否有线程正在等待和此condition相关的条件


第三组


方法名 作用
boolean isFair() 判断此锁是不是公平的
boolean isHeldByCurrentThread() 判断当前线程是否拿到了锁
boolean isLocked() 判断此锁是否由任意线程保持


第四组


lock()与lockInterruptibly()#


假如出现下面几步,我们这两种加锁方法,会有什么反应?,第一: 线程A启动,使用lock()加锁,第二步: CPU的执行权被线程B抢到,且线程B使用interrupted()方法给线程A打上中断的标记..第三步: 线程A继续执行

  • 使用lock()加锁,假如我们没有使用isInterrupted()判断的话,代码会按照原来的顺序依次全部执行,没有异常,线程AB正常结束
  • 使用lockInterruptibly()加锁,线程A被中断后,会调用lockInterruptibly()报异常,进入catch代码块


tryLock()与Lock()#


boolean tryLock()与void lock() 前者是有返回值的,两者都能去获取锁,而tryLock()直意尝试获取锁,有就拿,没有算了,它多了一步判断,它在调用时,会去判断此锁是否被其他线程锁定,如果没有,则获取,并返回ture


if(lock.tryLock()){
    //do something 1 ...
}else{
    //do something 2...
}


可以看到,使用tryLock(),不至于当前线程被阻塞住


方法名 作用
boolean tryLock(Long timeout,TimeUnit unit) 如果在给定的等待时间内,锁没有被其他线程拿到,并且当前线程也没有被中断,获取锁


await()和awaitUninterrupted()#


当线程A await() 线程B给线程A打上中断的标记

  • 1.中断 2,抛出中断异常 3. 进入catch块

当线程A awaitUninterrupted() 线程B给线程A打上中断的标记

  • 1.中断


boolean awaitUtil(Date deadLine);#


出现一下几种情况,线程被唤醒

  • 等待的时间结束
  • 在等待.被中断的过程被提前唤醒
  • 被中断


读写锁(排它锁,共享锁)#


在一个不需要操作实例变量的方法中,完全可以使用读写锁来提升该方法的代码运行速度


  • 读操作相关的锁是共享锁,写操作相关的锁为排他锁

ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

  • 读读共享 lock.readLock().lock();
  • 写写互斥 lock.writeLock().lock();
  • 读写互斥


获取读写锁


public void read() {
        lock.readLock().lock();
        System.out.println("获取到了读锁" + Thread.currentThread().getName() + "  " + System.currentTimeMillis());
        try {
            Thread.sleep(2000);
        } 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(2000);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.writeLock().unlock();
        }
    }


创建四条线程测试


获取到了读锁Thread-0  1548851348805
获取到了读锁Thread-1  1548851348805
Thread-2  1548851350806
Thread-3  1548851352806





相关文章
|
3月前
|
Java 开发者 C++
Java多线程同步大揭秘:synchronized与Lock的终极对决!
Java多线程同步大揭秘:synchronized与Lock的终极对决!
70 5
|
1天前
|
Java 开发者
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
12 4
|
5月前
|
Java 开发者 C++
Java多线程同步大揭秘:synchronized与Lock的终极对决!
【6月更文挑战第20天】在Java多线程编程中,`synchronized`和`Lock`是两种关键的同步机制。`synchronized`作为内置关键字提供基础同步,简单但可能不够灵活;而`Lock`接口自Java 5引入,提供更复杂的控制和优化性能的选项。在低竞争场景下,`synchronized`性能可能更好,但在高并发或需要精细控制时,`Lock`(如`ReentrantLock`)更具优势。选择哪种取决于具体需求和场景,理解两者机制至关重要。
51 1
|
25天前
|
Java 开发者
在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选
【10月更文挑战第6天】在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选。相比 `synchronized`,Lock 提供了更灵活强大的线程同步机制,包括可中断等待、超时等待、重入锁及读写锁等高级特性,极大提升了多线程应用的性能和可靠性。通过示例对比,可以看出 Lock 接口通过 `lock()` 和 `unlock()` 明确管理锁的获取和释放,避免死锁风险,并支持公平锁选择和条件变量,使其在高并发场景下更具优势。掌握 Lock 接口将助力开发者构建更高效、可靠的多线程应用。
22 2
|
5月前
|
分布式计算 并行计算 安全
在Python Web开发中,Python的全局解释器锁(Global Interpreter Lock,简称GIL)是一个核心概念,它直接影响了Python程序在多线程环境下的执行效率和性能表现
【6月更文挑战第30天】Python的GIL是CPython中的全局锁,限制了多线程并行执行,尤其是在多核CPU上。GIL确保同一时间仅有一个线程执行Python字节码,导致CPU密集型任务时多线程无法充分利用多核,反而可能因上下文切换降低性能。然而,I/O密集型任务仍能受益于线程交替执行。为利用多核,开发者常选择多进程、异步IO或使用不受GIL限制的Python实现。在Web开发中,理解GIL对于优化并发性能至关重要。
60 0
|
5月前
|
Java 测试技术
Java多线程同步实战:从synchronized到Lock的进化之路!
【6月更文挑战第20天】Java多线程同步始于`synchronized`关键字,保证单线程访问共享资源,但为应对复杂场景,`Lock`接口(如`ReentrantLock`)提供了更细粒度控制,包括可重入、公平性及中断等待。通过实战比较两者在高并发下的性能,了解其应用场景。不断学习如`Semaphore`等工具并实践,能提升多线程编程能力。从同步起点到专家之路,每次实战都是进步的阶梯。
53 0
|
2月前
|
Java
领略Lock接口的风采,通过实战演练,让你迅速掌握这门高深武艺,成为Java多线程领域的武林盟主
领略Lock接口的风采,通过实战演练,让你迅速掌握这门高深武艺,成为Java多线程领域的武林盟主
33 7
|
3月前
|
算法 Java
JUC(1)线程和进程、并发和并行、线程的状态、lock锁、生产者和消费者问题
该博客文章综合介绍了Java并发编程的基础知识,包括线程与进程的区别、并发与并行的概念、线程的生命周期状态、`sleep`与`wait`方法的差异、`Lock`接口及其实现类与`synchronized`关键字的对比,以及生产者和消费者问题的解决方案和使用`Condition`对象替代`synchronized`关键字的方法。
JUC(1)线程和进程、并发和并行、线程的状态、lock锁、生产者和消费者问题
|
3月前
|
安全 Java 开发者
Java多线程同步:synchronized与Lock的“爱恨情仇”!
Java多线程同步:synchronized与Lock的“爱恨情仇”!
83 5
|
3月前
|
Java 开发者
揭秘!为什么大神都爱用Lock接口处理线程同步?
揭秘!为什么大神都爱用Lock接口处理线程同步?
71 5