多线程七 Lock体系

在Lock接口出现之前,java程序主要是靠synchronized关键字实现锁功能的,而JDK5之后,并发包中增加了lock接口,它提供了与synchronized一样的锁功能。虽然它失去了像synchronize隐式加锁解锁的便捷性,(Lock锁为显式),但是却拥有了锁获取和释放的可操作性,可中断的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步特性(synchronized锁是互斥的)。

1. Lock

Lock–JDK 1.5基于Java语言实现的线程"锁"

可重入锁: 将持有锁的线程可以再次对锁的计数器 + 1

synchronized有可重入锁

// 在线程1中调用线程2
public synchronized void testA() {
    //线程1
    testB();    
}
public synchronized void testB() {
    //线程2
}

下面来看看Lock接口中定义了哪些方法

  1. void lock(); // 获取锁
  2. void lockInterruptibly(); // 获取锁的过程能够响应中断
  3. boolean tryLock(); // 非阻塞式响应中断能立即返回,获取锁返回true反之为false
  4. boolean tryLock(long time,TimeUnit unit);// 超时获取锁,在超时内或未中断的情况下能获取锁
  5. Condition newCondition(); // 获取与lock绑定的等待通知组件,当前线程必须先获得了锁才能等待,等待会释放锁,再次获取到锁才能从等待中返回

2. Lock体系使用

Demo:

try {
    //加锁
    lock.lock();
}finally {
    //解锁
    lock.unlock();
}

独占锁:在任意时刻,只有一个线程拥有此锁

共享锁:在同一时刻,可以有多个线程拥有锁(读写锁是共享锁的一种,读锁共享,写锁独占)

java中实现线程“锁”的方式:

扫描二维码关注公众号,回复: 8898207 查看本文章
  • synchronized and Lock

  • synchronized 与 Lock的关系:

    相同点:synchronized 与 Lock 都是对象锁,都支持可重入锁

    不同点:

    1. Lock 可以实现 synchronized 不具备的特性:响应中断,支持超时,非阻塞的获取锁
    2. 实现公平锁 private Lock lock = new ReentrantLock(true);
    3. 读写锁:读锁共享,写锁读占 ReentrantReadWriteLock
    4. Lock体系的 Condition 队列可以有多个(区别于 synchronized 只有一个等待队列)

Lock的使用

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

/**
 * @Author: Mr.Q
 * @Date: 2019-08-04 13:08
 * @Description:
 */
class MyLock implements Runnable {
    private Integer tickets = 50;
    private Lock ticketsLock = new ReentrantLock(); //实现Lock的接口
    @Override
    public void run() {
        while(tickets > 0) {
            //需要对程序上锁
            try {
                //等同于 synchronized(this)
                ticketsLock.lock();
                if(this.tickets > 0) {
                    System.out.println(Thread.currentThread().getName() +
                            "还有" + this.tickets-- + "张票");
                }
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                ticketsLock.unlock();
            }
        }
    }
}

public class LockTest {
    public static void main(String[] args) {
        MyLock myLock = new MyLock();
        new Thread(myLock,"黄牛A").start();
        new Thread(myLock,"黄牛B").start();
        Thread thread3 = new Thread(myLock,"黄牛C");
        thread3.start();
    }
}

可以看出 Lock 可以实现和 synchronized 同样的功能

死锁 产生条件:

  • 互斥
  • 占有且等待
  • 不可抢占
  • 循环等待

Lock解决死锁问题

破坏不可抢占(思路):

  1. 响应中断lockInterruptibly
  2. 支持超时boolean tryLock(long time, TimeUnit unit)
  3. 非阻塞式获取锁,线程若获取不到锁,线程直接退出boolean tryLock()

响应中断 lockInterruptibly

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

/**
 * @Author: Mr.Q
 * @Date: 2019-08-04 13:18
 * @Description: 在获取锁时能够响应中断
 */
class TestLockInterrupt implements Runnable {
    private Lock lock = new ReentrantLock();
    @Override
    public void run() {
        try {
            while (true) {
                lock.lockInterruptibly();
            }
        } catch (InterruptedException e) {
            System.err.println("线程"+Thread.currentThread().getName()+" 响应中断....");
            return;
        }finally {
            lock.unlock();
        }
    }
}

public class LockInterrupt {
    public static void main(String[] args) throws InterruptedException {
        TestLockInterrupt testLock = new TestLockInterrupt();
        Thread thread = new Thread(testLock,"Thread Q");
        thread.start();
        Thread.sleep(2000);
        thread.interrupt();
    }
}

while(true) { }中响应了thread.interrupt();
在这里插入图片描述
支持超时 boolean tryLock(long time, TimeUnit unit)

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Lock;

/**
 * @Author: Mr.Q
 * @Date: 2019-08-04 13:45
 * @Description:
 */
class ThreadLockTime implements Runnable {
    private Lock lock = new ReentrantLock(); //实现 Lock的接口
    @Override
    public void run() {
        fun();
    }

    private void fun() {
        try {
            if(lock.tryLock(1,TimeUnit.SECONDS)) {
                System.out.println(Thread.currentThread().getName() + "获取锁成功!");
                // sleep 2000ms,线程B获取不到锁
                Thread.sleep(2000);
            }else {
                System.err.println(Thread.currentThread().getName() + "获取锁失败...");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

public class LockTime {
    public static void main(String[] args) {
        ThreadLockTime threadLockTime = new ThreadLockTime();
        new Thread(threadLockTime,"ThreadA").start();
        new Thread(threadLockTime,"ThreadB").start();
    }
}

代码分析:
boolean tryLock(long timeout, TimeUnit unit)
如果在给定的等待时间内没有被另一个线程占用 ,并且当前线程尚未被保留,则获取该锁interrupted

A,B线程获取锁成功是随机的,看操作系统的调度. 我们以A线程获取锁成功为例

在run( )中调用fun( ),给定的等待时间为 if(lock.tryLock(1,TimeUnit.SECONDS)) 1s,线程A先拿到锁之后,线程B想要获取锁时,必须得先sleep 2s;

休眠的时间大于给定的等待时间,所以线程B获取锁失败了!
在这里插入图片描述

3. Condition

Condition:Lock体系的线程通信方式,类比Object类的 wait,notify;可以进一部提高效率,减少线程阻塞与唤醒带来的开销

await(): 释放Lock锁,将线程置入等待队列阻塞

signal(): 唤醒一个处于等待状态的线程

signalAll(): 唤醒所有线程

类比 wait()notify( )

获取一个Lock锁的Condition队列:

Lock.newCondition : 每当调用一次,就产生一个新的Condition

4. 到底使用 synchronized 还是 Lock?

  1. 若无特殊的应用场景,推荐使用 synchronized,其使用方便(隐式的加减锁),并且由于 synchronized是 JVM层面的实现,在之后的 JDK还有优化
  2. 若要使用 公平锁,读写锁,超时锁等特殊场景,才会考虑使用 Lock
发布了77 篇原创文章 · 获赞 118 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_43232955/article/details/98491287