美文网首页
Java中的两种锁

Java中的两种锁

作者: 土豆肉丝盖浇饭 | 来源:发表于2018-03-07 23:41 被阅读251次

为了保证线程安全,使用锁是其中一种方式,所以决定写篇文章巩固一下对锁的理解

锁的种类

1. 内置锁

内置锁的使用方式是使用synchoronized关键字,可以将synchoronized关键字放在方法上面,如果需要更细粒度的控制,可以使用synchoronzied代码块

//使用方式1
synchoronized(object){
    //同步代码
    object.wait();
    object.notify();
    object.notifyAll();
}
//使用方式2
public synchronized test1(){
    //同步代码
    wait();
    notify();
    notifyAll();
}
//使用方式3
public static synchronized test1(){
    //同步代码
}

使用方式2和3,其实本质上也是使用方式1的调用形式,只不过锁监视的对象不同,方式2是监听当前的实例化对象,方式3是监听当前类对象。然后可以在代码块里面,可以使用wait和notify以及notifyAll方法对线程进行控制。

1.1 wait,notify,notifyAll的作用

wait:让当前线程进入阻塞状态,进入锁的等待队列,并且释放当前的锁

notify:随机让一个等待队列中的线程从阻塞状态变为就绪,但是此时并没有释放锁,需要运行到synchronized代码块结束,或者碰到下一个wait方法释放锁。然后和其他线程竞争这个锁(可能有多个线程变为就绪,或者新起线程)。

notifyAll:激活阻塞队列中的所有线程,但是这些线程还是需要竞争锁,因为全都变成就绪态了,如果当前线程退出同步代码块,释放锁了,其他被激活的线程会继续竞争锁。这与notify有所不同,notify只激活了一个线程,即使锁可用了,那些阻塞的线程没有被notify或者notifyAll还是会一直阻塞下去。

1.2 wait是否一定要notify或者notifyAll才能唤醒

首先,明确一点,唤醒和继续执行下去是两回事,因为需要继续执行需要重新获得锁

wait除了被notify和notifyAll唤醒,还有以下几种方式

  1. 其他线程调用该线程的interrupt方法
  2. 使用超时的wait方法
  3. 特殊情况 当使用线程对象作为锁对象时

第三种情况比较有意思,如果以运行中的线程对象当作锁对象,在同步代码块中的wait方法,会在这个线程结束后,自动将当前线程唤醒

贴上我自己发现这个问题的代码

public class WaitTest {

    static class ThreadA extends Thread {
        public ThreadA(String name){
            super(name);
        }

        @Override
        public void run() {
           synchronized (this){
               System.out.println(Thread.currentThread().getName()+" call notify()");
           }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ThreadA t1 =new ThreadA("t1");

        synchronized (t1){
            System.out.println(Thread.currentThread().getName()+" start t1");
            t1.start();

            System.out.println(Thread.currentThread().getName()+" wait");
            t1.wait();
                
            System.out.println(Thread.currentThread().getName()+" continue");
        }
    }
}

这边t1.wait()理论上来讲 应该阻塞住,但是没有,如果将 t1.start()注释掉,就能被阻塞住

经过查询资料,真相就是。。。在Thread的join源码上发现一端解释

* <p> This implementation uses a loop of {@code this.wait} calls
 * conditioned on {@code this.isAlive}. ==As a thread terminates the
 * {@code this.notifyAll} method is invoked==. It is recommended that
 * applications not use {@code wait}, {@code notify}, or
 * {@code notifyAll} on {@code Thread} instances.

意思就是线程结束,会自动调用这个线程对象的notifyall,所以在线程结束之前把它作为锁对象,会自动把wait唤醒

2. 显式锁

显式锁主要用到JUC包中的ReentrantLock,与内置锁不同,这边的锁的获取和释放需要显式使用代码

//使用方式
public void test(){
    ReentrantLock lock = new ReentrantLock();
    Condition condition =reentrantLock.newCondition();
    lock.lock();
    try{
        //同步代码
        condition.await();
        condition.signal();
        condition.signalAll();
    }finally{
        lock.unlock();
    }
}

ReentrantLock提供了Condition对象,里面的await,signal和signalAll对应隐式锁中的wait,notify和notifyAll

2.1 condition这个设计的好处

在内置锁中,使用wait方法之后,会把当前线程加入到对应锁的等待队列中,但是只有一个提交队列,如果2个线程通过这种形式

while(not conditionA){
    wait();
}

等待不同的2个条件,而第三个线程执行了notify,它的目的是唤醒conditionA下的等待,由于norify是随机选择一个,就有几率把这个唤醒丢失,导致程序出错,当然这时可以使用notifyAll来避免。但是肯定会带来一定多余的上下文切换。

而condition的设计,可以让我们在一个锁上创建多个等待队列,激活这个等待队列中的线程,不会影响其他等待队列中的线程。并且condition会继承lock的一些特性,比如公平/非公平。

2.2 公平/非公平模式

ReentrantLock支持2种模式,公平和非公平,默认实现为非公平。具体不同之处是,在公平模式下,锁的获得,是按照lock先后顺序的。而非公平模式,被激活的下一个线程会与新加入的线程产生竞争。

3.两种锁的区别

1.是否可中断
2.尝试性获取锁
3.锁的释放方式
4.公平/非公平模式
5.condition精确唤醒

我的公众号

希望大家关注下我的公众号,我保证自己每天都能更新,为了我的粉丝哈


image

相关文章

网友评论

      本文标题:Java中的两种锁

      本文链接:https://www.haomeiwen.com/subject/ovqefftx.html