尚硅谷JUC高并发编程学习笔记多线程锁

Posted exodus3

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了尚硅谷JUC高并发编程学习笔记多线程锁相关的知识,希望对你有一定的参考价值。

一、多线程锁

某一个时刻内,只能有唯一一个线程去访问这些synchronized 方法。
所有的静态同步方法用的也是同一把锁——类对象本身,这两把锁是两个不同的对象,所以静态同步方法与非静态同步方法之间是不会有竞态条件的。但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,而不管是同一个实例对象的静态同步方法之间,还是不同的实例对象的静态同步方法之间,只要它们同一个类的实例对象。

synchronized锁的是方法,则是对象锁
同个对象锁的机制要等待,不同对象锁的机制调用同一个不用等待
加了static则为class锁而不是对象锁

通过具体的实例进行分析
两个线程安全方法一个普通方法

class Phone 

    public synchronized void sendSMS() throws Exception 
        //停留4秒
        TimeUnit.SECONDS.sleep(4);
        System.out.println("------sendSMS");
    

    public synchronized void sendEmail() throws Exception 
        System.out.println("------sendEmail");
    

    public void getHello() 
        System.out.println("------getHello");
    


public class Lock_8 
    public static void main(String[] args) throws Exception 

        Phone phone = new Phone();
        Phone phone2 = new Phone();

        new Thread(() -> 
            try 
                phone.sendSMS();
             catch (Exception e) 
                e.printStackTrace();
            
        , "AA").start();

        Thread.sleep(100);

        new Thread(() -> 
            try 
               // phone.sendEmail();
               // phone.getHello();
                phone2.sendEmail();
             catch (Exception e) 
                e.printStackTrace();
            
        , "BB").start();
    

具体八种情况为

1 标准访问,先打印短信还是邮件
------sendSMS
------sendEmail
24秒在短信方法内,先打印短信还是邮件
------sendSMS
------sendEmail
3 新增普通的hello方法,是先打短信还是hello
------getHello
------sendSMS
4 现在有两部手机,先打印短信还是邮件
------sendEmail
------sendSMS
5 两个静态同步方法,1部手机,先打印短信还是邮件
------sendSMS
------sendEmail
6 两个静态同步方法,2部手机,先打印短信还是邮件
------sendSMS
------sendEmail
7 1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件
------sendEmail
------sendSMS
8 1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件
------sendEmail
------sendSMS

总结:

1.- 同样的对象访问不同的同步锁,是按照顺序执行
同样的对象访问同步锁与不同步锁,是先不同步锁执行
不同对象访问不同同步锁,按照顺序执行

2.- 同一对象访问不同静态同步锁,按照顺序执行
不同对象访问不同静态同步锁,按照顺序执行

3.- 同一对象访问一个静态同步锁,一个同步锁,先执行同步锁
不同对象访问一个静态同步锁,一个同步锁,先执行同步锁
即先出同步锁在出静态同步锁

二、公平锁和非公平锁

公平锁:效率相对低
非公平锁:效率高,但是线程容易饿死

通过查看源码,带有参数的ReentrantLock(true)为公平锁,ReentrantLock(false)为非公平锁
主要是调用NonfairSync()与FairSync()

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();

具体其非公平锁与公平锁的源码
查看公平锁的源码

static final class FairSync extends Sync 
   private static final long serialVersionUID = -3000897897090466540L;

  /**
  * Acquires only if reentrant or queue is empty.
   */
  final boolean initialTryLock() 
   Thread current = Thread.currentThread();
   int c = getState();
   if (c == 0) 
   if (!hasQueuedThreads() && compareAndSetState(0, 1)) 
     setExclusiveOwnerThread(current);
      return true;
    
     else if (getExclusiveOwnerThread() == current) 
      if (++c < 0) // overflow
          throw new Error("Maximum lock count exceeded");
         setState(c);
         return true;
       
    return false;

通过代码实例具体操作

在最前面售票代码中,继续引用前面的代码

//第一步  创建资源类,定义属性和和操作方法
class LTicket 
    //票数量
    private int number = 30;

    //创建可重入锁
    private final ReentrantLock lock = new ReentrantLock();
    //卖票方法
    public void sale() 
        //上锁
        lock.lock();
        try 
            //判断是否有票
            if(number > 0) 
                System.out.println(Thread.currentThread().getName()+" :卖出"+(number--)+" 剩余:"+number);
            
         finally 
            //解锁
            lock.unlock();
        
    


public class LSaleTicket 
    //第二步 创建多个线程,调用资源类的操作方法
    //创建三个线程
    public static void main(String[] args) 

        LTicket ticket = new LTicket();

new Thread(()-> 
    for (int i = 0; i < 40; i++) 
        ticket.sale();
    
,"AA").start();

        new Thread(()-> 
            for (int i = 0; i < 40; i++) 
                ticket.sale();
            
        ,"BB").start();

        new Thread(()-> 
            for (int i = 0; i < 40; i++) 
                ticket.sale();
            
        ,"CC").start();
    

输出结果

AA :卖出30 剩余:29
AA :卖出29 剩余:28
AA :卖出28 剩余:27
AA :卖出27 剩余:26
AA :卖出26 剩余:25
AA :卖出25 剩余:24
AA :卖出24 剩余:23
AA :卖出23 剩余:22
AA :卖出22 剩余:21
AA :卖出21 剩余:20
AA :卖出20 剩余:19
AA :卖出19 剩余:18
AA :卖出18 剩余:17
AA :卖出17 剩余:16
AA :卖出16 剩余:15
AA :卖出15 剩余:14
AA :卖出14 剩余:13
AA :卖出13 剩余:12
AA :卖出12 剩余:11
AA :卖出11 剩余:10
AA :卖出10 剩余:9
AA :卖出9 剩余:8
AA :卖出8 剩余:7
AA :卖出7 剩余:6
AA :卖出6 剩余:5
AA :卖出5 剩余:4
AA :卖出4 剩余:3
AA :卖出3 剩余:2
AA :卖出2 剩余:1
AA :卖出1 剩余:0

Process finished with exit code 0

都是A线程执行,而BC线程都没执行到(或者某个线程出现的概率很大,其余线程出现的概率很小),出现了非公平锁。
具体改变其设置可以通过可重入锁中的一个有参构造方法

修改代码为private final ReentrantLock lock = new ReentrantLock(true);
这样就是公平锁,可以让每个线程都有出现的机会。
结果如下:

AA :卖出30 剩余:29
AA :卖出29 剩余:28
AA :卖出28 剩余:27
AA :卖出27 剩余:26
AA :卖出26 剩余:25
AA :卖出25 剩余:24
AA :卖出24 剩余:23
AA :卖出23 剩余:22
AA :卖出22 剩余:21
AA :卖出21 剩余:20
AA :卖出20 剩余:19
AA :卖出19 剩余:18
AA :卖出18 剩余:17
AA :卖出17 剩余:16
BB :卖出16 剩余:15
AA :卖出15 剩余:14
CC :卖出14 剩余:13
BB :卖出13 剩余:12
AA :卖出12 剩余:11
CC :卖出11 剩余:10
BB :卖出10 剩余:9
AA :卖出9 剩余:8
CC :卖出8 剩余:7
BB :卖出7 剩余:6
AA :卖出6 剩余:5
CC :卖出5 剩余:4
BB :卖出4 剩余:3
AA :卖出3 剩余:2
CC :卖出2 剩余:1
BB :卖出1 剩余:0

Process finished with exit code 0

三、可重入锁

synchronized和lock都是可重入锁

sychronized是隐式锁,不用手工上锁与解锁,而lock为显示锁,需要手工上锁与解锁。
可重入锁也叫递归锁
而且有了可重入锁之后,破解第一把之后就可以一直进入到内层结构。

Object o = new Object();
new Thread(()->
    synchronized(o) 
        System.out.println(Thread.currentThread().getName()+" 外层");

        synchronized (o) 
            System.out.println(Thread.currentThread().getName()+" 中层");

            synchronized (o) 
                System.out.println(Thread.currentThread().getName()+" 内层");
            
        
    

,"t1").start();

结果

t1 外层
t1 中层
t1 内层

Process finished with exit code 0

synchronized (o)代表锁住当前 内的代码块

以上都是synchronized锁机制
下面讲解lock锁机制

public class SyncLockDemo 

    public synchronized void add() 
        add();
    

    public static void main(String[] args) 
        //Lock演示可重入锁
        Lock lock = new ReentrantLock();
        //创建线程
        new Thread(()->
            try 
                //上锁
                lock.lock();
                System.out.println(Thread.currentThread().getName()+" 外层");

                try 
                    //上锁
                    lock.lock();
                    System.out.println(Thread.currentThread().getName()+" 内层");
                finally 
                    //释放锁
                    lock.unlock();
                
            finally 
                //释放做
                lock.unlock();
            
        ,"t1").start();

        //创建新线程
        new Thread(()->
            lock.lock();
            System.out.println("aaaa");
            lock.unlock();
        ,"aa").start();
        
 

结果

t1 外层
t1 内层
aaaa

Process finished with exit code 0

在同一把锁中的嵌套锁,内部嵌套锁没解锁还是可以输出,但是如果跳出该线程,执行另外一个线程就会造成死锁。
要把握上锁与解锁的概念,都要写上。

四、死锁

两个或以上的进程因为争夺资源而造成互相等待资源的现象称为死锁。

产生死锁的原因:

系统资源不足
系统资源分配不当
进程运行顺序不当

验证是否是死锁

jps 类似于linux中的ps -ef查看进程号
jstack 自带的堆栈跟踪工具

死锁验证过程:
1、死锁代码:

public class DeadLock 

    //创建两个对象
    static Object a = new Object();
    static Object b = new Object();

    public static void main(String[] args) 
        new Thread(()->
            synchronized (a) 
                System.out.println(Thread.currentThread().getName()+" 持有锁a,试图获取锁b");
                try 
                    TimeUnit.SECONDS.sleep(1);
                 catch (InterruptedException e) 
                    e.printStackTrace();
                
                synchronized (b) 
                    System.out.println(Thread.currentThread().getName()+" 获取锁b");
                
            
        ,"A").start();

        new Thread(()->
            synchronized (b) 
                System.out.println(Thread.currentThread().getName()+" 持有锁b,试图获取锁a");
                try 
                    TimeUnit.SECONDS.sleep(1);
                 catch (InterruptedException e) 
                    e.printStackTrace();
                
                synchronized (a) 
                    System.out.println(Thread.currentThread().getName()+" 获取锁a");
                
            
        ,"B").start();
    

程序没有停止

通过用idea自带的命令行输入 jps -l
查看其编译代码的进程号后jstack 进程号

以上是关于尚硅谷JUC高并发编程学习笔记多线程锁的主要内容,如果未能解决你的问题,请参考以下文章

尚硅谷JUC高并发编程学习笔记多线程锁

尚硅谷JUC高并发编程学习笔记

尚硅谷JUC高并发编程学习笔记

尚硅谷JUC高并发编程学习笔记

尚硅谷JUC高并发编程学习笔记JUC简介与Lock接口

尚硅谷JUC高并发编程学习笔记JUC简介与Lock接口