显式锁与隐式锁(Java)

引入

了解显式锁与隐式锁之前,先来看一个日常生活中卖票的例子:

  • 首先新建类Ticket 实现接口Runnable,新建run卖票任务
    /**
     * 任务创建一个,但是交给三个线程去执行,则会出现线程不安全问题
     */
    static class Ticket implements Runnable{
        //总票数
        private int count = 10;
        @Override
        public void run() {//每次被触发就进卖买票操作
            while(count > 0){
                //卖票
                System.out.println("正在准备卖票");
                //try-catch使得卖票的时间更长
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count --;
                System.out.println("出票成功!余票:" + count);
            }
        }//end
    }
  • 在main中创建并启动三个线程
    public static void main(String[] args) {
        //线程不安全
        Runnable runnable = new Ticket();
        //启动三个线程
        new Thread(runnable).start();
        new Thread(runnable).start();
        new Thread(runnable).start();
    }

输出结果:

正在准备卖票,请稍等...
正在准备卖票,请稍等...
正在准备卖票,请稍等...
出票成功!余票:8
正在准备卖票,请稍等...
出票成功!余票:9
正在准备卖票,请稍等...
出票成功!余票:7
正在准备卖票,请稍等...
出票成功!余票:5
正在准备卖票,请稍等...
出票成功!余票:6
正在准备卖票,请稍等...
出票成功!余票:4
正在准备卖票,请稍等...
出票成功!余票:3
正在准备卖票,请稍等...
出票成功!余票:2
正在准备卖票,请稍等...
出票成功!余票:1
正在准备卖票,请稍等...
出票成功!余票:0
出票成功!余票:-2
出票成功!余票:-1

这是一个的卖票栗子,总共有10张票,有3个线程分别进行卖票。通过输出结果观察可知,余票出现了负数,但是代码逻辑上余票count=0时便不再执行了

出现问题原因:假设三段线程为ABC,ABC可能同时进行到while,假设A先进入,此时count = 1,当A进入休眠未进行到count–时,B检测到count = 1,进入while,当B进入休眠未进行到count–时,C检测到count = 1,进入while,此时A运行count–,count = 0,B接着运行count- -,count =-1,C接着运行count- -,count = -2,同时由于线程阻塞以及线程调度,输出的顺序可能不同

这就是多线程完成统一任务(一个任务交给三个线程去执行)时出现的线程不安全问题

为了解决线程不安全问题,我们在写代码的时候常常用到锁来保证线程的安全


显式锁与隐式锁

所谓的显式和隐式,就是在使用的时候使用者是否需要手动写代码去获取锁和释放锁

显式锁隐式锁
需要手动写代码去获取锁和释放锁不需要手动写代码去获取锁和释放锁
显式锁使用Lock关键字隐式锁使用synchronized修饰符

一、 隐式锁

隐式锁使用synchronized修饰符

在使用sync关键字的时候,当sync代码块执行完成之后程序能够自动获取锁和释放锁,不需要手动写代码去获取锁和释放锁

1.1 同步代码块

线程同步,使线程排队执行

每个线程在执行时看同一把锁,谁抢到了锁,谁就执行

线程同步实现:synchronized

格式:
synchronized(锁对象){
// 同步代码块
}
`
锁对象: java中任何对象都可以作为锁存在,即任何对象都可以打上锁的标记,作为锁对象


代码示例:

对原有的线程不安全的卖票示例进行修改

在while循环中加锁
同步代码块为:当余票大于0时,进行卖票操作
因此当一个线程正在执行同步代码块时,另外的线程不会执行该代码块,在后面排队等待执行

 /**
     * 任务创建一个,但是交给三个线程去执行,则会出现线程不安全问题
     *
     *  解决线程不安全问题:排队执行
     */
    static class Ticket implements Runnable{
        //总票数
        private int count = 10;
        private Object o = new Object();//创建对象
        @Override
        public void run() {//每次被触发就进卖买票操作
            while(true){
                synchronized (o){//加锁
                    if(count > 0){
                        //卖票
                        System.out.println("正在准备卖票,请稍等...");
                        //try-catch使得卖票的时间更长
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        count --;
                        System.out.println(Thread.currentThread().getName() + "出票成功!余票:" + count);
                    }else{
                        break;
                    }
                }
            }//end while
        }//end run
    }

由于只创建了一个任务,因此Object对象只创建了一个,即创建了一把锁

而后面启动的三个线程由于只有一个任务,因此三个线程在执行的时候看同一把锁,谁抢到锁谁就执行,排队执行

        Runnable runnable = new Ticket();//只有一个任务,因此下面的object对象只创建了一个
        //启动三个线程,o是同一个,只有一个任务,因此在执行的时候只看一个o
        new Thread(runnable).start();
        new Thread(runnable).start();
        new Thread(runnable).start();

		//如果上述写法写成如下,则依旧为不安全线程
		//此时创建了三个任务(new Ticket()),分别创建三个object对象(即锁),此时相当于3个人卖票,每个人卖10张 
		//错误写法!!!!注意
		//new Thread(new Ticket()).start();
		//new Thread(new Ticket()).start();
		//new Thread(new Ticket()).start();
		
		

加了锁之后的输出结果:

正在准备卖票,请稍等...
Thread-0出票成功!余票:9
正在准备卖票,请稍等...
Thread-0出票成功!余票:8
正在准备卖票,请稍等...
Thread-0出票成功!余票:7
正在准备卖票,请稍等...
Thread-0出票成功!余票:6
正在准备卖票,请稍等...
Thread-0出票成功!余票:5
正在准备卖票,请稍等...
Thread-0出票成功!余票:4
正在准备卖票,请稍等...
Thread-0出票成功!余票:3
正在准备卖票,请稍等...
Thread-0出票成功!余票:2
正在准备卖票,请稍等...
Thread-0出票成功!余票:1
正在准备卖票,请稍等...
Thread-0出票成功!余票:0


如果将创建锁的对象写在任务的代码块中,如下所示

此时,每个线程启动时都会创建o对象,因此每个线程都有自己锁o,每个线程在执行时都看自己的锁,这时不能排队,要格外注意!!!!

错误写法:

public void run() {//每次被触发就进卖买票操作
            Object o = new Object();//!!!!!!!三个线程启动时都会创建o对象,即每个线程都有自己的锁o,每个人都看自己的不同的锁,此时不能排队
            while(true){
                synchronized (o){//加锁
                    if(count > 0){
                        //卖票
                        System.out.println("正在准备卖票,请稍等...");
                        //try-catch使得卖票的时间更长
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        count --;
                        System.out.println(Thread.currentThread().getName() + "出票成功!余票:" + count);
                    }else{
                        break;
                    }
                }
            }//end while
        }//end run

1.2 同步方法

与同步代码块相似,不同的是,同步方法以方法为单位进行加锁,给方法添加synchronized修饰符

同步方法的锁为this
同步方法有可能被静态修饰,如果被静态修饰,则同步方法的锁为类.class

代码示例:

	/**
     * 创建一个任务,但是交给三个线程去执行,则会出现线程不安全问题
     * 解决线程不安全问题:排队执行
     */
    static class Ticket implements Runnable{
        //总票数
        private int count = 10;
        @Override
        public void run() {//每次被触发就进卖买票操作
            while(true){
                boolean flag = sale();//sale()为加了锁的方法
                if(!flag){
                    break;
                }
            }//end while
        }//end run

        //添加synchronized修饰符,给方法加锁
        public synchronized boolean sale(){
            //this,同步的方法的锁
            //Ticket.class,如果方法为静态方法,则同步方法的锁为类.class
            if(count > 0){
                //卖票
                System.out.println("正在准备卖票,请稍等...");
                //try-catch使得卖票的时间更长
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count --;
                System.out.println(Thread.currentThread().getName() + "出票成功!余票:" + count);
                return true;
            }
            return false;
        }
    }

如果同步代码块锁了一段代码,同步方法锁了另一端代码,锁的对象都是this,那么这当一段代码正在执行时,另一段加锁的代码不能执行

如下面的代码所示,在循环前加了一把锁,则当一个线程执行这段代码块时,同步方法sale不能执行

  public void run() {
            synchronized (this){//再加一把锁
                
            }
            while(true){
                boolean flag = sale();
                if(!flag){
                    break;
                }
            }//end while
        }//end run

如果有多个同步的方法,且多个方法都是this这把锁,则其中一个方法执行、其他方法无法执行


二、 显式锁

显式锁使用Lock关键字

在使用Lock的时候,使用者需要手动[获取lock()]和[释放unlock()]锁,如果没有释放锁,就有可能导致出现死锁的现象

显式锁比隐式锁更好,更能体现锁的概念,体现了面向对象的机制

显式锁Lock的子类:ReentrantLock

代码示例:

  • 创建隐式锁l
	Lock l = new ReentrantLock();
  • 在进行代码块前锁住
	l.lock();
  • 在代码块结束后开锁
	l.unlock();//代码执行完毕,开锁

完整代码如下:

    public static void main(String[] args) {
        //线程不安全
        //解决方案3:显式锁Lock
        //java中任何对象都可以作为锁存在,即任何对象都可以打上锁的标记,作为锁对象
        Runnable runnable = new Ticket();//只有一个任务
        //启动三个线程,但使用的都是runnable对象,因此用的都是同一把锁l
        new Thread(runnable).start();
        new Thread(runnable).start();
        new Thread(runnable).start();
    }

   static class Ticket implements Runnable{
        //总票数
        private int count = 10;
        //创建显式锁l
        private Lock l = new ReentrantLock();
        @Override
        public void run() {//每次被触发就进卖买票操作
            while(true){
                l.lock();//进入if之前,锁住
                if(count > 0){
                    //卖票
                    System.out.println("正在准备卖票,请稍等...");
                    //try-catch使得卖票的时间更长
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    count --;
                    System.out.println(Thread.currentThread().getName() + "出票成功!余票:" + count);
                }else{
                    break;
                }
                l.unlock();//代码执行完毕,开锁
            }//end while
        }//end run
    }

不论是显式锁还是隐式锁,都可以有效地控制多线程获取资源、解决所出现的线程不安全问题


公平锁与非公平锁

  • 公平锁:排队,先来先到,在Lock构造方法传入Boolean值True,则为公平锁
  • 非公平锁:抢,隐式锁Sync属于非公平锁,Lock默认为非公平锁

实现公平锁:

显式锁Lock的构造方法中,参数为True则表示公平锁

Lock l = new ReentrantLock(true);
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Selcouther

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值