售票情景解读synchronized和Lock两种锁的区别

一、并发下售票存在的Bug

首先让我们我们定义一个资源类Ticket我们通过多个线程来操作这一资源类,模拟卖票的例子:

//资源类
class Ticket{
     
    //属性总共还剩有多少张票
    private int number=50;

    //卖票的方法
    public void sale(){
     
        if (number>0){
     
            //卖掉一张票
            number--;
            try {
     
                //延迟,目的是为了更好看出在并发下程序运行的效果
                TimeUnit.MILLISECONDS.sleep(5);
                System.out.println(Thread.currentThread().getName() + "卖出了" + (50-number) + "张票,剩余"+number+"张票");
            } catch (Exception e) {
     
                e.printStackTrace();
            }
        }
    }
}

当我们在没有任何加锁的情况下进行多个线程并发操作:

public class SaleTicketDemo01 {
     
    public static void main(String[] args) {
     
        //并发:多个线程操作同一资源类,把资源类丢入线程
        Ticket ticket=new Ticket();

        //@FunctionalInterface   函数式接口,jdk8 lambda表达式
        new Thread(()->{
      for (int i = 0; i < 10; i++) ticket.sale(); },"A").start();
        new Thread(()->{
      for (int i = 0; i < 10; i++) ticket.sale(); },"B").start();
        new Thread(()->{
      for (int i = 0; i < 30; i++) ticket.sale(); },"C").start();

    }
}

这时候我们看一下运行结果:

B卖出了3张票,剩余47张票
A卖出了3张票,剩余47张票
C卖出了3张票,剩余47张票
C卖出了6张票,剩余44张票
B卖出了6张票,剩余44张票
A卖出了6张票,剩余44张票
A卖出了9张票,剩余41张票
C卖出了9张票,剩余41张票
B卖出了9张票,剩余41张票
C卖出了12张票,剩余38张票
A卖出了12张票,剩余38张票
B卖出了12张票,剩余38张票
B卖出了15张票,剩余35张票
A卖出了16张票,剩余34张票
C卖出了17张票,剩余33张票
A卖出了18张票,剩余32张票
B卖出了18张票,剩余32张票
C卖出了18张票,剩余32张票
C卖出了21张票,剩余29张票
A卖出了21张票,剩余29张票
B卖出了21张票,剩余29张票
B卖出了24张票,剩余26张票
C卖出了24张票,剩余26张票
A卖出了24张票,剩余26张票
A卖出了27张票,剩余23张票
B卖出了27张票,剩余23张票
C卖出了27张票,剩余23张票
A卖出了30张票,剩余20张票
B卖出了30张票,剩余20张票
C卖出了30张票,剩余20张票
C卖出了31张票,剩余19张票
C卖出了32张票,剩余18张票
C卖出了33张票,剩余17张票
C卖出了34张票,剩余16张票
C卖出了35张票,剩余15张票
C卖出了36张票,剩余14张票
C卖出了37张票,剩余13张票
C卖出了38张票,剩余12张票
C卖出了39张票,剩余11张票
C卖出了40张票,剩余10张票
C卖出了41张票,剩余9张票
C卖出了42张票,剩余8张票
C卖出了43张票,剩余7张票
C卖出了44张票,剩余6张票
C卖出了45张票,剩余5张票
C卖出了46张票,剩余4张票
C卖出了47张票,剩余3张票
C卖出了48张票,剩余2张票
C卖出了49张票,剩余1张票
C卖出了50张票,剩余0张票

明显能看出运行结果不对劲儿,那为什么会出现这种情况呢???

很简单:在没有锁的情况下多个线程没有排队,当访问同一资源的时候多个线程看到的资源数量相同,当抢到资源的那几个线程执行结束后,回头看见的剩余资源又是一样的。这就形成了上面图中多个线程执行,打印结果出现一样的情况。

售票情景解读synchronized和Lock两种锁的区别_第1张图片

二、synchronized锁解决售票问题

synchronized锁

package com.xiaochao;
import java.util.concurrent.TimeUnit;

public class SaleTicketDemo01 {
     
    public static void main(String[] args) {
     
        //并发:多个线程操作同一资源类,把资源类丢入线程
        Ticket ticket=new Ticket();

        //@FunctionalInterface   函数式接口,jdk8 lambda表达式
        new Thread(()->{
      for (int i = 0; i < 10; i++) ticket.sale(); },"A").start();
        new Thread(()->{
      for (int i = 0; i < 10; i++) ticket.sale(); },"B").start();
        new Thread(()->{
      for (int i = 0; i < 30; i++) ticket.sale(); },"C").start();

    }
}

//资源类 OOP
class Ticket{
     
    //属性、方法
    private int number=50;

    //卖票的方式
    //synchronized 本质:队列,排队,锁
    public synchronized void sale(){
     
        if (number>0){
     
            number--;
            try {
     
                 //延迟,目的是为了更好看出在并发下程序运行的效果
                TimeUnit.MILLISECONDS.sleep(5);
                System.out.println(Thread.currentThread().getName() + "卖出了" + (50-number) + "张票,剩余"+number+"张票");
            } catch (Exception e) {
     
                e.printStackTrace();
            }
        }
    }


}

在上面的代码中我们能看到,我们将Ticket资源类的sale()方法添加了一个synchronized关键字,

这时候我们来看一下运行的结果是什么样的:

A卖出了1张票,剩余49张票
A卖出了2张票,剩余48张票
C卖出了3张票,剩余47张票
C卖出了4张票,剩余46张票
C卖出了5张票,剩余45张票
C卖出了6张票,剩余44张票
C卖出了7张票,剩余43张票
C卖出了8张票,剩余42张票
C卖出了9张票,剩余41张票
C卖出了10张票,剩余40张票
C卖出了11张票,剩余39张票
C卖出了12张票,剩余38张票
C卖出了13张票,剩余37张票
C卖出了14张票,剩余36张票
C卖出了15张票,剩余35张票
B卖出了16张票,剩余34张票
B卖出了17张票,剩余33张票
B卖出了18张票,剩余32张票
B卖出了19张票,剩余31张票
B卖出了20张票,剩余30张票
B卖出了21张票,剩余29张票
B卖出了22张票,剩余28张票
B卖出了23张票,剩余27张票
B卖出了24张票,剩余26张票
B卖出了25张票,剩余25张票
C卖出了26张票,剩余24张票
C卖出了27张票,剩余23张票
C卖出了28张票,剩余22张票
C卖出了29张票,剩余21张票
C卖出了30张票,剩余20张票
C卖出了31张票,剩余19张票
C卖出了32张票,剩余18张票
C卖出了33张票,剩余17张票
C卖出了34张票,剩余16张票
C卖出了35张票,剩余15张票
C卖出了36张票,剩余14张票
C卖出了37张票,剩余13张票
A卖出了38张票,剩余12张票
A卖出了39张票,剩余11张票
A卖出了40张票,剩余10张票
A卖出了41张票,剩余9张票
A卖出了42张票,剩余8张票
A卖出了43张票,剩余7张票
A卖出了44张票,剩余6张票
A卖出了45张票,剩余5张票
C卖出了46张票,剩余4张票
C卖出了47张票,剩余3张票
C卖出了48张票,剩余2张票
C卖出了49张票,剩余1张票
C卖出了50张票,剩余0张票

下面我们将这个情节画图展示出来:

售票情景解读synchronized和Lock两种锁的区别_第2张图片

三、Lock锁解决售票问题

Lock锁


package com.xiaochao;

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

/**
 * @program: demoCode
 * @description:
 * @author: 小超
 * @create: 2020-12-29 15:15
 **/
public class SaleTicketDemo02 {
     

    public static void main(String[] args) {
     
        //并发:多个线程操作同一资源类,把资源类丢入线程
        Ticket2 ticket=new Ticket2();

        //@FunctionalInterface   函数式接口,jdk8 lambda表达式
        new Thread(()->{
      for (int i = 0; i < 10; i++) ticket.sale(); },"A").start();
        new Thread(()->{
      for (int i = 0; i < 10; i++) ticket.sale(); },"B").start();
        new Thread(()->{
      for (int i = 0; i < 60; i++) ticket.sale(); },"C").start();

    }

}

//资源类 OOP
//lock三部曲:1.new锁2.加锁,3.解锁
class Ticket2{
     
    //属性、方法
    private int number=50;
    //new 锁
    Lock lock=new ReentrantLock();
    
    public void sale(){
     
        //加锁
        lock.lock();
        if (number>0){
     
            number--;
            try {
     
                //延迟
                TimeUnit.MILLISECONDS.sleep(5);
                System.out.println(Thread.currentThread().getName() + "卖出了" + (50-number) + "张票,剩余"+number+"张票");
            } catch (Exception e) {
     
                e.printStackTrace();
            }finally {
     
                //解锁,必须执行的步骤,不然会造成死锁
                lock.unlock();
            }
        }
    }

}

四、synchronized和Lock的区别

synchronized与Lock的区别

  1. synchronized是关键字,Lock是一个接口
  2. synchronize无法判断获取锁的状态,Lock可以判断是否获取到了锁
  3. synchronized会自动释放锁,Lock必须要手动释放锁,如果不释放锁就会造成死锁
  4. synchronized 线程1(获得锁)、线程2(会等待);Lock锁就不一定会等下去了,
  5. synchronized 可重入锁,不可以中断,非公平;Lock:可重入锁,可以判断,非公平(可以自己设置);
  6. synchrnoized 适合锁少量的代码同步问题,Lock锁适合锁大量的同步代码块!

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