java线程锁

https://blog.csdn.net/A1342772/article/details/89853412

1.悲观锁和乐观锁:

    悲观锁和乐锁是一种广义的概念提现的是看待线程同步的不同角度

    悲观锁认为自己在使用数据的时候一定会有别的线程来修改数据,在获取数据的时候会先加锁,确保不会被别的线程修改

    锁的实现:关键字synchronize和lock的类实现

    使用场景:在写操作比较多,先加锁可以确保写操作是数据正确

    乐观锁是认为自己在使用数据的时候不会有线程来修改数据,所以不加锁,只是在更新数据的时候采取判断别的线程有没有更新了这个数据

    锁的实现:CAS算法

    使用场景:读操作比较多,不加锁的特点是能够使读操作的性能提高

2.阻塞和自旋锁

    是指当一个线程获取锁饥饿的时候如果锁已经被其他线程获取,那么该线程循环等待,然后不断判断是否能够获取成功,自旋直到获取到锁才退出循环,自旋是通过CAS算法进行的(CAS算法在另一个笔记上,这里不详细介绍)

    读写锁:读写锁是一种特殊的自旋锁,一次只有一个 线程可以占有写模式的读写锁, 但是可以有多个线程同时占有读模式的读写锁. 正是因为这个特性,

    当读写锁是写加锁状态时, 在这个锁被解锁之前, 所有试图对这个锁加锁的线程都会被阻塞.

    当读写锁在读加锁状态时, 所有试图以读模式对它进行加锁的 线程都可以得到访问权, 但是如果线程希望以写模式对此锁进行加锁, 它必须直到知道所有的线程释放锁.

    通常, 当读写锁处于读模式锁住状态时, 如果有另外线程试图以写模式加锁, 读写锁通常会阻塞随后的读模式锁请求, 这样可以避免读模式锁长期占用, 而等待的写模式锁请求长期阻塞.

         适用性:

        读写锁适合于对 数据结构的读次数比写次数多得多的情况. 因为, 读模式锁定时可以共享, 以写模式锁住时意味着独占, 所以读写锁又叫共享-独占锁.

3.无锁,偏向锁,轻量级锁和重量级锁:

    无锁        :顾名思义就是不锁住资源

    偏向锁     :可就是只有一个线程进入临界区,适用于只有一个线程访问同步块的场景

    轻量级锁  :多线程未竞争,或者竞争不激烈的情况下,适用于追求响应时间,同步执行速度非常快

    重量级锁   :多线程竞争激烈,同步速度快

4.可重入锁和不可重入锁

    可重入锁:一个线程可以多次获得同一把锁并通过state记录加了多少把锁    

    不可重入锁为什么会导致死锁:synchronizated的同步是要绑定一个对象的,不写的话就是Synchronized(this,既绑定当前对象),,这个this对象,就是锁,当LogginWidget执行dosomething的时候就获得了这把锁,那么他调用父类的dosomething的时候,,父类的dosomething也获得了这把锁,但子类的没运行完,父类要继续等,子类不释放锁,还让父类执行,所以就死锁了

5.公平锁和非公平锁:

    公平锁非公平锁主要是等待线程排队的问题,这个排队要利用AQS(AbstractQueueedSynchronize),这里不具体介绍

    ReentrantLock的公平锁和非公平锁都委托了 AbstractQueuedSynchronizer#acquire去请求获取

public final void acquire(int arg) {

2 if (!tryAcquire(arg) &&

3     acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

4     selfInterrupt();

5 }

 

tryACquire();是一个抽象方法,是公平与非公平的实现原理所在

addWaiter();是将当前线程节点加入等待队列之中,公平锁释放后会严格按照等待队列去取后续值,而非公平锁对于新线程有很大优势

acquireQueued();在多次循环中尝试获取到锁或者将当前线程阻塞

selfInterrupt();如果线程在阻塞期间发生了中断,调用Thread.currentThread().interrupt() 中断当前线程

公平锁和非公平锁在说的获取上都用到了volatile关键字修饰state字段,这是保证多线程环境下锁获取与否的核心

公平锁FairSync

        公平锁的实现机制在于每次有线程来抢占锁的时候,都会去检查一遍有没有队列等待,如果有,当前线程会执行如下步骤

 if (!hasQueuedPredecessors() &&

     compareAndSetState(0, acquires)) {

     setExclusiveOwnerThread(current);

     return true;

 }

        其中 hasQueuedPredecessors是用于检查是否有等待队列的

public final boolean hasQueuedPredecessors() {

     Node t = tail; // Read fields in reverse initialization order 3     Node h = head;

     Node s;

     return h != t && ((s = h.next) == null || s.thread !=     

     Thread.currentThread());

 }

非公平锁NonfairSync

        非公平锁在实现的时候强调多次随机抢占

if (c == 0) {

     if (compareAndSetState(0, acquires)) {

         setExclusiveOwnerThread(current);

         return true;

     }

 }

与公平锁的区别在于新晋获取锁的进程会有多次机会去抢占锁,如果被加入了等待队列后跟公平锁没有区别

ReentrantLock锁的释放是逐级释放的,也就是说,在可重入场景中,必须要等到场景内所有的加锁的方法都释放了锁,当前线程持有的锁才会被释放,释放的方法很简单,state-1

 protected final boolean tryRelease(int releases) {

 //  releases = 1 int c = getState() - releases;  3 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException();

     boolean free = false;

        if (c == 0) {

        free = true;

        setExclusiveOwnerThread(null);

   }

     setState(c);

     return free;

 }

6.互斥锁和共享锁:

    互斥锁:在共享资源资源之前进行加锁,在访问完成之后进行解锁,加锁后,任何其他试图再次加锁的线程都会被阻塞,直到当前线程解锁

    共享锁:共享锁从字面上来看就是允许多个线程共同访问资源

    共享锁的典型例子就是读写者模式,一个人写,多个人可以共享资源

7.死锁:(https://www.cnblogs.com/hadoop-dev/p/6899171.html

    (1)死锁的定义:线程的死锁是指两个或者多个线程相互访问对方所需要的资源,导致这些线程处于等待状态,无法往前执行。当线程进入对象的Synchronized代码快时,便占有了资源,直到它退出该代码块或者调用wait方法才释放资源,在此期间,其他线程将不能进入该代码块。当线程相互持有对方所需要的资源时,会互相等待对方释放资源,如果线程都不主动释放所占有的资源,将产生死锁

    (2)死锁产生的条件:

                        1.互斥条件:进程对于所分配到的资源具有排他性,即一个资源只能被一个资源占用,直到被该进程释放

                        2.请求保持条件:一个进程因请求被占用资源而发生阻塞时,对已获得的资源保持不放

                        3.不剥夺条件:任何一个资源没被该进程释放之前,任何其他进程都无法对他剥夺占用

                        4.循环等待条件:当发生死锁时,所等待的进程必须形成一个环路,造成永久阻塞

    (3)死锁的例子:

package deadlock;

/*

* 线程thread1率先占有了resource1,继续运行时需要resource2

* 但此时resource2却被线程thread2占有了,

* 因此只能等待thread2释放resource2才能继续运行,因此,thread2

* 也需要等待resource1,它只能等待thread1释放resource1才能继续运行,因此,发生thread1和thread2

* 都处于等待状态,因此发生死锁

* */

public class DeadLock1 {

    public static void main(String[] args) {

         deadLock();

    }

    

    private static void deadLock(){

         //两个资源

         final Object resource1 = "resource1";

         final Object resource2 = "resource2";

         //第一个线程,想先占有resource1,再尝试resource2

         Thread t1 = new Thread(){

         //线程1,先占有资源1,在占有资源一的前提下请求资源2

         public void run() {

             synchronized (resource1) {

                 System.out.println("线程1成功占用资源1");

                 try {

                     sleep(50);

                 } catch (InterruptedException e)  {

                    

                     e.printStackTrace();

                 }

                 synchronized (resource2) {

                     System.out.println("线程1成功占用资源2");

                 }

             }

         }   

         };

            //线程2,先占有资源2,在占有资源一的前提下请求资源1

         Thread t2 = new Thread(){

             

             public void run() {

                 synchronized (resource2) {

                     System.out.println("线程2成功占用资源2");

                     try {

                          sleep(50);

                     } catch (InterruptedException  e) {

                          // TODO Auto-generated  catch block

                          e.printStackTrace();

                     }

                     synchronized (resource1) {

                          System.out.println("线程2成功占用资源1");

                     }

                 }

             }   

             };

             t1.start();

             t2.start();

    }

}

(4)死锁的避免:

               1.调整加锁顺序

               2.设定加锁时限

               3.死锁检测

               4.银行家算法

1.调整加锁的顺序

    当一个线程需要相同的一些锁,但是按照不同的顺序加锁,死锁就很容易发生

如果保证所有线程都是按照相同的顺序获得锁,那么死锁就不会发生:

Thread1:

    lock A

    lock B

Thread2:

    wait for A

    lockC (when A locked)

Thread 3:

    wait for A

    wait for B

    wait for C

如果一个线程(比如线程3)许哟啊一些锁,那么它必须按照确定的顺序获得锁,它只有获得了从顺序上排在前面的锁之后才能获取后面的锁

例如线程2和线程3只有获取了锁A之后才能继续尝试获取C(获取锁A是获取锁C的必要条件)。因为线程1已经拥有了锁A,所以线程2和3需要等到锁A被释放,然后它们尝试对B或C加锁之前必须成功的对A加锁。按照顺序加锁是一种有效的防死锁的预防机制,但是,这种方式需要你事先知道所有可能会用到的锁(并且排序),但总有一些时候是无法预防的

2.加锁时限

    另外一个可以避免死锁的方法是在尝试获取锁的时候加一个超时时间,这也就意味着在尝试获取锁的过程中超过了这个时限该线程则放弃了对锁的请求,若一个线程没有在给定的时间内成功获得所需要的锁,则会回退并释放所有已经获得的锁,然后等待一段时间后再次尝试获取锁,这段时间让其他线程有机会尝试获取这些相同的锁,并且让该应用在没有获得锁的时候可以继续运行(加锁超时后可以尝试做其他的任务,回头再重复获取锁)例:

Thread 1 locks A

Thread 2 locks B

 

 

Thread 1 attempts to lock B but is blocked

Thread 2 attempts to lock A but is blocked

 

 

Thread 1's lock attempt on B times out

Thread 1 backs up and releases A as well

Thread 1 waits randomly (e.g. 257 millis) before retrying.

 

 

Thread 2's lock attempt on A times out

Thread 2 backs up and releases B as well

Thread 2 waits randomly (e.g. 43 millis) before retrying.

在上面的例子当中线程2比线程1早200毫秒进行重试加锁,因此它可以先成功的获取到两个锁,这时,线程1尝试获取锁A并且处于等待状态。当线程2结束时,线程1也可以顺利的获得这两个锁(除非线程1成功获取两个锁之前有获得其中的一些锁)

需要注意的是,由于存在锁的超时,所以我们不能认为这种场景就一定是出现了死锁也可能是因为获取了锁的线程(导致其他线程超时),需要很长时间去完成任务

此外如果有很多线程同一时间去访问统一批资源,就算有超时和回退机制,还是有可能导致这些线程重复的尝试,但是始终的不到锁,如果只是两个线程,并且重试的时间设置在5-500ms之内,这种现象可能不会发生,但如果1

个或者20个线程情况就不同了,因为这些线程等待的重试时间相同的概率就高得多(非常接近以至于会出现问题)

3.死锁检查

        死锁检查是一个更好的死锁预防机制,它主要针对那些不可实现按顺序加锁和超时加锁的场景

每当一个线程获得了锁,会在线程和锁相关的数据相关的数据结构中(mao,graph等等)将其记下,除此之外,每当线程请求锁,也需要记录在这个数据结构当中

每当一个一个线程请求锁失败时,这个线程可以遍历锁的关系图看看是否有死锁发生,例如线程A请求锁7,但是锁7这个时候被线程B持有,这时线程A就可以检查一下线程B是否已经请求了线程A当前所持有的锁,如果线程B确实有这样的请求,那么就算发生了锁

当然,死锁一般要比两个线程相互持有对方的锁这种情况复杂的多,线程A等待线程B,线程B等待线程C,线程C等待线程D,线程D又等待线程A,线程A为了检查死锁,它需要递进地检测所有被B请求的锁。从线程B所请求的锁开始,从线程B所请求的锁的开始,线程A找到了线程C,然后又找到了D,发现线程D请求的锁被线程A自己持有着,则会就知道发生了死锁

那么检测出死锁时这些 线程应该做什么呢?

一个可行性的做法是释放所有的锁,回退,并且等待一阶段随机时间后重试,这个和简单的加锁超时类似,这个和简单的超时加锁类似,不一样的是只有死锁已经发生了才回退,而不是因为加锁的请求超时了,虽然有回退和等待,但是如果有大量的线程竞争同一批锁,他们还是会重复的锁,

一个更好的方案是给这些线程设置优先级,让一个(几个)线程回退,剩下的下次你就像没发生死锁一样继续保持着他们的锁。如果赋予这些线程优先级是固定不变的,同一批线程总是会拥有更高的优先级,为避免这个问题,可以在发生死锁的时候设置随机的优先级

 

 

 

 

 

 

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值