关于Lock锁用法的详细讲解(案例驱动,手把手教学)

来吧,宝贝,卖票!

关于Lock锁用法的详细讲解(案例驱动,手把手教学)_第1张图片

100张票,4个窗口
——emmmmm,能不能5个窗口?
也行!嘿嘿,你开心就好!
——哎,不对,我看你上面图片上是8个窗口……
wocao,he…tui,我说4个就是4个!

关于Lock锁用法的详细讲解(案例驱动,手把手教学)_第2张图片

准备准备,开始了,让你的女朋友帮你打开咱们的开发工具:IDEA2020.2
关于Lock锁用法的详细讲解(案例驱动,手把手教学)_第3张图片

顺便提一句哈:如果你也想让自己的idea可以有这么长时间的使用权,你可以关注我的微信公众号:【佑佑有话说】,回复关键字“IDEA”即可获取和我一样的效果哦!
关于Lock锁用法的详细讲解(案例驱动,手把手教学)_第4张图片

欧克欧克,继续

新建java类,命名为woaiyouyouxiaogege.java,怎么样,没有拼音八级都看不懂我的代码吧!(我爱佑佑小哥哥.java)哈哈哈哈哈嗝

开玩笑开玩笑,正常点哈,新建java类,命名为LockDemo.java

public class LockDemo{

    public static void main(String[] args) {
        
    }

既然我们讲到了锁,那么肯定要有线程(多线程)啊,So,我这里让这个类实现Runnable接口、重写run方法

public class LockDemo implements Runnable{

    public static void main(String[] args) {
        
    }
     @Override
     public void run() {
         
      }

然后,创建线程对象,并调用线程start方法,顺便给线程起个名字,方便区分(女朋友1号,女朋友2号,女朋友3号……)
关于Lock锁用法的详细讲解(案例驱动,手把手教学)_第5张图片

public class LockDemo implements Runnable{

    public static void main(String[] args) {
        LockDemo lockDemo=new LockDemo();
        new Thread(lockDemo,"售票窗口").start();
    }
     @Override
      public void run() {
         
      }

——好像……有点……小问题?

啥?哦哦哦哦哦,我懂,多线程嘛,再来几个窗口嘛!多大点事呀!

public class LockDemo implements Runnable{

    public static void main(String[] args) {
        LockDemo lockDemo=new LockDemo();
        new Thread(lockDemo,"窗口1").start();
        new Thread(lockDemo,"窗口2").start();
        new Thread(lockDemo,"窗口3").start();
        new Thread(lockDemo,"窗口4").start();
    }
     @Override
     public void run() {
        
     }

做完以上的准备工作,我们就可以卖票了
but,票呢?售票员都在窗口准备好了,却没有票,还卖个锤子哟?
关于Lock锁用法的详细讲解(案例驱动,手把手教学)_第6张图片

public class LockDemo implements Runnable{

    public static void main(String[] args) {
        LockDemo lockDemo=new LockDemo();
        new Thread(lockDemo,"窗口1").start();
        new Thread(lockDemo,"窗口2").start();
        new Thread(lockDemo,"窗口3").start();
        new Thread(lockDemo,"窗口4").start();
    }
      private  static  int count =100;
      private Lock lock=new ReentrantLock();

       @Override
       public void run() {
           while (count>0){
               sellTickets();
           }
       }

		private void sellTickets() {
		
		}

这里我们提供100张票,并准备了一把锁,在run方法中我们调用卖票的方法,前提是有票的情况下才可以卖票!

lock是一个接口,里面只定义了lock、trylock、unlock等方法,所以实现原理我们直接从ReentrentLock来看。ReentrantLock把所有Lock接口的操作都委派到一个Sync类上,该类继承了AbstractQueuedSynchronizer(简称AQS),线程使用ReentrantLock获取锁分为两个阶段,第一个阶段是初次竞争(ReentrantLock默认使用非公平锁,当我们调用ReentrantLock的lock方法的时候,实际上它调用的是非公平锁的lock(),这个方法先用CAS操作,去尝试抢占该锁。如果成功,就把当前线程设置在这个锁上,表示抢占成功,如果失败,就调用LockSupport.park将当前线程阻塞,将其加入CLH队列中,等待抢占),第二个阶段是基于CLH队列的竞争。(然后进入CLH队列的抢占模式,当持有锁的那个线程调用unlock的时候,会将CLH队列的头结点的下一个节点线程唤醒,调用的是LockSupport.unpark()方法。)在初次竞争的时候是否考虑队列节点直接区分出了公平锁和非公平锁。在基于CLH队列的锁竞争中,依靠CAS操作来抢占锁,依靠LockSupport来做线程的挂起和唤醒,使用队列来保证并发执行变成了串行执行,从而消除了并发所带来的问题。总体来说,ReentrantLock是一个比较轻量级的锁,而且使用面向对象的思想去实现了锁的功能,比原来的synchronized关键字更加好理解。

对于卖票的方法,我们可以这样写,模拟卖票,打印出线程名,加上卖的票的编号

 private void sellTickets() {
                if(count >0){
                    System.out.println(Thread.currentThread().getName()+",正在出售:"+(100-count+1));
                    count--;
}

来看一下我们的运行结果

再多试几次(多线程的运行结果具有随机性),情况似乎出现了一些转机,但是,结果还是扎堆出现啊……而且,一直说的锁呢?锁呢?呢?
关于Lock锁用法的详细讲解(案例驱动,手把手教学)_第7张图片


这个时候就可以有请我们的闪亮出场了!
关于Lock锁用法的详细讲解(案例驱动,手把手教学)_第8张图片

 private void sellTickets() {
           
            try{
                lock.lock();//上锁
                if(count >0){
                    System.out.println(Thread.currentThread().getName()+",正在出售:"+(100-count+1));
                    count--;
                }
            }catch (Exception e){
                e.getCause();
            }finally {
                lock.unlock();//释放锁
            }
}

为了保证锁一定会被释放,我把释放锁的代码写在了finally块里(这里面的代码一定会被执行,所以,我的锁一定会被释放)!为了程序的效果,我又做了一些修改,给程序加了一段休眠的代码:

 private void sellTickets() {
            try{
                Thread.sleep(30);
            }catch (Exception e){
                e.printStackTrace();
            }
            try{
                lock.lock();//上锁
                if(count >0){
                    System.out.println(Thread.currentThread().getName()+",正在出售:"+(100-count+1));
                    count--;
                }
            }catch (Exception e){
                e.getCause();
            }finally {
                lock.unlock();//释放锁
            }
        }

请看最终运行效果:

为了让大家看清运行过程,我把线程休眠时间改成10秒……
请注意观察!
关于Lock锁用法的详细讲解(案例驱动,手把手教学)_第9张图片
关于Lock锁用法的详细讲解(案例驱动,手把手教学)_第10张图片
关于Lock锁用法的详细讲解(案例驱动,手把手教学)_第11张图片

完整源码如下:

package cn.uu710;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @version 1.0
 * @author: 张佑
 * @date: 2020-10-09 13:46
 */

public class LockDemo implements Runnable{

    public static void main(String[] args) {
        study.study03.LockDemo lockDemo=new study.study03.LockDemo();
        new Thread(lockDemo,"窗口1").start();
        new Thread(lockDemo,"窗口2").start();
        new Thread(lockDemo,"窗口3").start();
        new Thread(lockDemo,"窗口4").start();
    }
    private  static  int count =100;
    private Lock lock=new ReentrantLock();

    @Override
    public void run() {
        while (count>0){
            sellTickets();
        }
    }

    private void sellTickets() {
        try{
            Thread.sleep(30);
        }catch (Exception e){
            e.printStackTrace();
        }
        try{
            lock.lock();//上锁
            if(count >0){
                System.out.println(Thread.currentThread().getName()+",正在出售:"+(100-count+1));
                count--;
            }
        }catch (Exception e){
            e.getCause();
        }finally {
            lock.unlock();//释放锁
        }
    }
}

关于Lock锁用法的详细讲解(案例驱动,手把手教学)_第12张图片

你可能感兴趣的:(多线程,并发编程)