Lock、Synchronized锁区别解析( 四 )


等待-通知模型在一个线程执行 synchronized 修饰的代码块时 , 其他线程并不是必须等到该线程执行完才可能得到 CPU 调度 , 对于某些业务场景 , 需要我们在一段代码中进行线程地来回切换 。 这就需要说到 "等待-通知" 模型了 , 在说这个模型前 , 要先了解 Object 的 wait() 方法
1 public final void wait() throws InterruptedException {2wait(0);3 }4 5 6 7 8 9 public final native void wait(long timeout) throws InterruptedException;可以看到 wait(long timeout) 是使用 native 修饰的 , 是一个本地方法 , 用 C、C++实现的 , 这个方法作用就是让拥有这个对象的线程释放掉这个对象 , 进入 "休眠", 并让出 CPU , 让其他线程去得到对象资源执行 。 与其对应的就是 notify() 方法 , 它也是 Object 类的方法 , 这个方法会随机让该对象对应锁的一个 "休眠" 的线程 "苏醒" , 然后参与CPU的竞争中 。 还有一个 notifyAll() 是让对象对应锁的所有 "休眠" 的线程 "苏醒" 。 下面来看一个例子
1 public class CoTest01 { 2public static void main(String[] args) { 3SynContainer container = new SynContainer(); 4new Productor(container).start(); 5new Consumer(container).start(); 6} 7 } 8 //生产者 9 class Productor extends Thread{10SynContainer container;11public Productor(SynContainer container) {12this.container = container;13}14 15public void run() {16//生产17 //synchronized (container) {18for(int i=0;i<100;i++) {19container.push(new Steamedbun(i));20}21 //}22}23 }24 //消费者25 class Consumer extends Thread{26SynContainer container;27public Consumer(SynContainer container) {28this.container = container;29}30public void run() {31 //synchronized (container) {32//消费33for(int i=0;i<100;i++) {34container.pop();35}36 //}37}38 }39 //缓冲区40 class SynContainer{41List list=new ArrayList<>(); //存储容器42//存储 生产43public synchronized void push(Steamedbun bun) {44//何时能生产容器存在空间45//不能生产 只有等待46if(list.size() == 10) {47try {48this.wait(); //线程阻塞消费者通知生产解除49} catch (InterruptedException e) {50System.out.println("push 异常");51}52}53//存在空间 可以生产54list.add(bun);55//存在数据了 , 可以通知消费了56this.notifyAll();57System.out.println("生产-->"+list.size()+"个馒头");58}59//获取 消费60public synchronized void pop() {61//何时消费 容器中是否存在数据62//没有数据 只有等待63if(list.size() == 0) {64try {65this.wait(); //线程阻塞生产者通知消费解除66} catch (InterruptedException e) {67}68}69//存在数据可以消费70list.remove(list.size()-1);71this.notifyAll(); //存在空间了 , 可以唤醒对方生产了72System.out.println("消费第" + list.size() + "个馒头");73}74 }75 //馒头76 class Steamedbun{77int id;78public Steamedbun(int id) {79this.id = id;80}8182 }上面这个例子就是典型的 "等待-通知" 模型 。 通过 wait() 和 notifyAll() 来控制线程之间的切换 。
执行结果:
Lock、Synchronized锁区别解析文章插图
Locklock 是 java 核心类库中的一个接口 , 它是 jdk 维护的实现同步的一个接口 , 常用的实现类是 RenntrantLock、ReadWriteLock 等 。 相比于 synchronized , 它更灵活 , 效率也更高(jdk6之前) 。 下面就先以 ReeentrantLock 为例 , 来说一下Lock 接口中常用的构造器和实现方法 。 在说构造器前先要了解什么是公平锁 , 非公平锁 。 它不能修饰方法 , 只能修饰代码块 。
公平锁指的是一个线程在执行到一段锁包裹的代码前 , 发现已经有其他线程到了 , 并且已经排成了一个 "等待队伍" (这个属于AQS定义的线程执行规则 , 也会在后续的多线程博客中说到) , 排队等待CPU调度 , 那么公平锁的策略是直接加入这个 "等待队伍" 的最后面 , 保证锁资源的公平性分配 。
非公平锁非公平锁指的是在遇到 "等待队列" 时 , 会先尝试获取锁资源 , 如果获取到直接 "插队" 执行 , 如果没有获取到就乖乖到最后面 "排队" 。 虽然这种锁看起来不 "文明" , 但是它的总体效率是比 "公平锁" 要高的 , 如果在 "队头" 的线程发生异常停止运行 , 那么后面的线程就需要一直等待影响效率 。 但是这样也会导致优先级低的线程在和高优先级的线程竞争时一直没有获取到CPU , 从而一直无法执行造成 "活锁" 。 所以总结下来就是非公平锁会使系统整体的效率提高但是可能会导致 "活锁" 的情况发生 。上面的 synchronized 就是非公平锁 。
构造器 /*** Creates an instance of {@code ReentrantLock}.* This is equivalent to using {@code ReentrantLock(false)}.*/public ReentrantLock() {sync = new NonfairSync();}/*** Creates an instance of {@code ReentrantLock} with the* given fairness policy.** @param fair {@code true} if this lock should use a fair ordering policy*/public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}