线程锁

Posted wxw_wang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线程锁相关的知识,希望对你有一定的参考价值。

synchronized
synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
1. 修饰一个代码块 : 被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
           当两个并发线程(thread1和thread2)访问同一个对象(syncThread)中的synchronized代码块时,在同一时刻只能有一个线程得到执行,另一个线程受阻塞,必须等待当前线程执行完这个代码块以后才能执行该代码块。 
           当一个线程访问对象的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该对象中的非synchronized(this)同步代码块。 

2. 修饰一个方法 : 被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
3. 修改一个静态的方法 : 其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
4. 修改一个类 : 其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象

synchornized与lock区别?

1、Lock 是通过java代码实现加锁,而 synchronized 是在 JVM 层面上实现的。

2、synchronized 在锁定时如果方法块抛出异常,JVM 会自动将锁释放掉,不会因为出了异常没有释放锁造成线程死锁。Lock出现异常时必须在 finally 将锁释放掉,否则将会引起死锁。

3、Lock能实现精准的通知,但是synchronized不行!用法如下:

    private Lock lock = new ReentrantLock();
    Condition consumeCondition = lock.newCondition();
    Condition produceContion = lock.newCondition();

produceContion.await(); // 只让produce进入等待状态
produceContion.signalAll();//只给produce发送信号

consumeCondition.await();
consumeCondition.signalAll();

Synchronized 和 ReenTrantLock 的对比
1、两者都是可重入锁
“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。
2、synchronized依赖于JVM而ReenTrantLock依赖于API
ReenTrantLock是JDK层面实现的(也就是API层面,需要lock()和unlock()方法配合try/finally语句块来完成)
3、ReenTrantLock比synchronized增加了一些高级功能
3.1、等待可中断;
ReenTrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。
3.2、可实现公平锁;
ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。
ReenTrantLock默认情况是非公平的,可以通过ReenTrantLoc类的ReentrantLock(boolean fair)构造方法来制定是否是公平的。
3.3、可实现选择性通知
synchronized关键字与wait()和notify()/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition()方法。

在JDK1.6之前,synchronized的性能比ReenTrantLock差很多。具体表示为:synchronized关键字吞吐量随线程数的增加,下降得非常严重。而ReenTrantLock 基本保持一个比较稳定的水平。
在JDK1.6之后JVM团队对synchronized关键字做了很多优化,性能基本能与ReenTrantLock持平。
所以JDK1.6之后,性能已经不是选择 synchronized 和ReenTrantLock的影响因素,
而且虚拟机在未来的性能改进中会更偏向于原生的synchronized,所以还是提倡在synchronized能满足你的需求的情况下,优先考虑使用synchronized关键字来进行同步!
优化后的synchronized和ReenTrantLock一样,在很多地方都是用到了CAS操作。

synchronized的问题
       如果一个代码块被synchronized修饰了,当A线程获取了锁并执行该代码块时,其他线程便只能一直等待,等待A线程释放锁,而A线程释放锁只会有两种情况:
  1)A线程执行完了该代码块,然后A线程释放对锁的占有;
  2)线程执行发生异常,此时JVM会让线程自动释放锁。
  那么如果A线程由于要等待IO或者调用sleep方法等其他原因被阻塞了,但又没有主动释放锁,其他线程便只能一直等待,想象一下这执行效率多么低下。
  因此我们就需要一种可以不让等待的线程一直无期限地等待下去的机制(例如只等待给定的时间/能中断响应)
       通过Lock我们就可以办到这些。

  例如:当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作会发生冲突现象,但是读操作和读操作不会发生冲突现象。
  但是采用synchronized关键字来实现同步的话,就会导致一个问题:
  如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程只能等待无法进行读操作。
  因此就需要一种机制来使得多个线程都只是进行读操作时,线程之间不会发生冲突,通过Lock就可以办到。
  另外,通过Lock可以知道线程有没有成功获取到锁。这个是synchronized无法办到的。
  总结:
        Lock提供了比synchronized更多的功能。
        注意:
  1)Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问;
  2)Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

Lock

在Lock接口中声明了四个方法来获取锁
1、lock()方法是使用最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待。
一般来说使用Lock必须在try{}catch{}块中进行,并且在finally块中进行释放锁的操作以保证锁一定被释放,防止死锁的发生。
2、tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程占有),则返回false,拿不到锁时不会一直等待。
3、tryLock(long time, TimeUnit unit)方法和tryLock()方法基本一样,区别在于这个方法在拿不到锁时会等待一段的时间,在时间期限之内如果还拿不到锁,就返回false。如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
4、lockInterruptibly()方法比较特殊,当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。也就使说,当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。
而用synchronized修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去。

Lock和synchronized的选择
1、Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
2、synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
3、Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
4、通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
5、Lock可以提高多个线程进行读操作的效率。
在性能上来说,如果资源竞争不激烈,两者性能相差不大,而当资源竞争非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以在具体使用时要根据适当情况选择。

 

sleep()、yield()有什么区别?
1、线程执行sleep方法后会先变成Blocked状态,等到了阻塞时间以后会变成Runable状态,yield方法后会直接变成Runable状态。

2、sleep让出CPU资源而给其他线程运行机会时不会考虑线程的优先级,而yield只会给相同优先级或更高优先级的线程以运行的机会。

3、sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常。

4、sleep()方法比yield()方法具有更好的可移植性。

sleep()、与wait()有什么区别?
1、sleep方法是Thread类的静态方法,wait是Object类的方法。

2、sleep会使线程进入阻塞状态,只有在阻塞时间到后才会重新进入就绪阶段等待cpu调度。wait会使线程进入锁定对象的等待池,直到其他线程调用同一个对象的notify方法才会重新被激活。

3、wait方法必须在同步块中,并且调用wait方法的对象必须与同步块锁定的是同一个对象。调用wait方法的线程会释放锁资源,而在同步块中调用sleep方法的线程不会释放锁资源!

以上是关于线程锁的主要内容,如果未能解决你的问题,请参考以下文章

并发技术12线程锁技术的使用

java并发线程锁技术的使用

互斥锁 & 共享锁

线程方法区别

起底多线程同步锁(iOS)

互斥锁自旋锁读写锁和条件变量