线程安全杂谈之锁

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

锁的种类

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:激活阻塞队列中的所有线程,但是这些线程还是需要竞争锁,因为全都变成就绪态了,如果当前线程退出同步代码块,释放锁了,其他被激活的线程会继续竞争锁。这与wait有所不同,wait只激活了一个线程,即使锁可用了,那些阻塞的线程没有被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.精确唤醒

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值