IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Java多线程显式锁与隐式锁的不一样的详细解读 -> 正文阅读

[Java知识库]Java多线程显式锁与隐式锁的不一样的详细解读

前言

那我在搜索多线程有关于锁的知识的时候发现,基本上所有的博客内容都是出自同一篇文章,啊,知识就是这样传播开来的。那我就觉得我既然也学习了这个知识点那就做点不一样的,当然我也很希望大家都能转载我这篇博客,留言说一声就行,点个赞再走也不错,能收藏那就是极好不过了,不转载学习讨论留言评论区大家一起探讨也是极好的。
好,开始我们的学习,那在开始本次的学习中我希望大家能够了解,什么是锁,锁的定义概念是什么,锁它要什么用,锁它会在哪里去使用呢?
其实我们生活中到处都有锁的身影,自行车锁,车锁,门锁,还有你们那难以打开的心锁。。。等等。对吧,那我们都知道锁的存在,那谁能说一下锁它为什么要存在呢?好,没人说,那我说啊,那不管是上面的哪个锁是不是都是在防止盗窃,防止我们的财产不被他人恶意侵占,防止不好的事情发生。那锁的存在就给了我们一定程度上的安全感没错吧。那这也是Java中锁的意义所在。那我们的显式锁与隐式锁的意义也就是在Java多线程的任务执行中保护我们线程能够好好的,不被阻断的进行下去。

那谈到了锁就必然会存在安全问题。我们也经常听到别人说线程安全不安全的,注意,面试官也经常问线程安全的奥。 那既然涉及到了这个知识点,那我们先来聊一聊什么是线程安全。

线程安全

什么是线程安全?

多个线程同一时刻对同一个全局变量(同一份资源)做写操作(读操作不会涉及线程安全)时,如果跟我们预期的结果一样,我们就称之为线程安全,反之,线程不安全。
不知道大家打过架没有?反正我肯定是没有打过架了,好好学生一枚的说!(我都是往死里打。),好继续说回来。那假设你们家你有个姐姐,没错,总是欺负你的那个女人,她每次都会在你们吃饭的时候和你抢饭吃,不是穷,没饭吃,而是总抢你的饭吃。你没有听错,她就是总抢你的饭吃。(那时候我还小,打不过她,到了10+了,更打不过了,女孩发育早的说。别给我说,什么姐姐最好了,那我小的时候最怕的就是她,很烦,打也打不过,你放弃,她还穷追不舍,拿话讽刺你。啊,那种感觉我现在回想起来还是有点气。)。好,回归到打架的阶段,那她总是要和你吃同一口,抢一个食,那是不是你们就筷子会打架,最后你被打。对吧,那这就是不安全。

源码案例解释

public class Demo7 {
    public static void main(String[] args) {
        //线程不安全
        Runnable run = new Ticket();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
    }

    static class Ticket implements Runnable{
        //票数
        private int count=10;
        @Override
        public void run() {
            while (count>0){
                //卖票
                System.out.println("正在准备卖票");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count--;
                System.out.println("出票成功,余票:"+count);
            }
        }
    }
}

没错,就是市面上的例子,我就不浪费心思了,反正知识点都是一样的。
好,我们来瞧一瞧在线程不安全下的运行结果。
在这里插入图片描述
通过上面的测试结果,三个线程,同时抢票,有时候会抢到同一张票?那在这里我先说一个情况,我这次的线程运行中没有打印出余票为负数的情况的,但是这种情况也是存在的,因为在第一个线程出票为0之后,那第二个第三个线程还在继续执行,也就出现了余票为-1,-2的现象。那我们为了能够更好地理解这个卖票的过程,我们用画图来讲一下。

请添加图片描述
注意,重点来了
那假设我们的票count卖着卖着卖的就剩一张了,那A继续到while判断,count=1,是大于0的,那继续往下,当走到我们的try-catch的时候就会耗费一些时间,当然,sleep方法会消耗时间(sleep方法是放大了犯错的几率),但是即使没有,也有可能因为时间偏的丢失,导致B抢到了,那这个时候B也进行判断,往下执行,之后C也执行了,也就是说,我们的三个线程,同时处在任务中,那A走完打印是不是余票0,之后B再打印那就是-1,C就是-2了,这也会死为什么有可能出现-2的原因。
好,这里引入一个知识点:

时间偏指的是CPU分出来的一个个的时间。即抢到的时间的概率越大。

这里就又涉及到我们的线程调度问题,那这里就不再多说了,想知道的可以自主查找或者留言评论。那我们其中的抢占调度就是我们Java使用的调度机制,当CPU空闲以后,CPU会主动抛出时间偏,由各个线程争抢,抢到了就去执行。
那通过以上的案例也让我们更好的理解到线程安全,那怎么解决我们的线程安全问题就是这次博客的真正重点了。

解决线程安全问题方法

1、隐式锁:同步代码块

被标记的锁对象,Java中任何对象都可以传进来,任何对象都可以打标记

public class Demo8 {
    public static void main(String[] args) {
        //线程不安全
        //解决方案1. 同步代码块
        //格式: synchronized(锁对象){
        //
        // }
        Runnable run = new Ticket();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
    }

    static class Ticket implements Runnable{
        //票数
        private int count=10;
        private Object  o= new Object();
        @Override
        public void run() {
            //Object o= new Object();
            while (count>0){
                synchronized (o){
                    if (count>0){
                        //卖票
                        System.out.println("正在准备卖票");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        count--;
                        System.out.println(Thread.currentThread().getName()+"出票成功,余票:"+count+"张"+",时间:"+System.currentTimeMillis());
                    }else {
                        break;
                    }
                }
            }
        }
    }
}

为了让大家能够直观的感受到打印的时间间隔,我们测试运行结果如下:
在这里插入图片描述
通过图片我们可以发现,同一时间,抢票的间隔差不多都是1000ms,为什么,不是说多线程吗?
因为在抢票的方法上,增加了synchronized,导致同一时候,只能有一个线程运行,需要等这个线程运行完后,下一个线程才能运行。
大家可以认为我们去优衣库买衣服,那优衣库是不是都有试衣间对吧。那假设试衣间有两个,但是只有一个门,也就是张三进去试衣服了,把门关上了,那是不是两个试衣间都不能被使用了,只有张三出来才能供一个人使用。

2、隐式锁:同步方法

public class Demo9 {
    public static void main(String[] args) {
        //解决方案2. 同步方法
        //线程不安全
        Runnable run = new Ticket();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();

    }

    static class Ticket implements Runnable{
        //票数
        private int count=10;
        @Override
        public void run() {
            while (true){
                boolean flag = sale();
                if (!flag){
                    break;
                }
            }
        }

        public synchronized boolean sale(){
            //this
            //Ticket.class
            /**
             * 以方法为单位进行加锁。在方法名称前面加上修饰符synchronized,
             * 它的锁是this,调用该方法的对象就是锁,
             * 但当方法被静态修饰的时候锁对象就是类名.class。
             * 而创建(new)了多个对象以后,每个线程执行的对象互不相同,就不会由排队的情况,
             * 因为它们的锁不一样,给判断各自的锁
             * */
            if (count>0) {
                //卖票
                System.out.println("正在准备卖票");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count--;
                System.out.println(Thread.currentThread().getName()+"出票成功,余票:"+count+"张"+",时间:"+System.currentTimeMillis());
                return true;
            }
            return false;
        }
    }
}

方法为单位进行加锁。在方法名称前面加上修饰符synchronized,它的锁是this,调用该方法的对象就是锁,但当方法被静态修饰的时候锁对象就是类名.class。而创建(new)了多个对象以后,每个线程执行的对象互不相同,就不会由排队的情况,因为它们的锁不一样,给判断各自的锁。
测试结果是一样的,就不再多发图了,这里只是向大家展示两种不同方式。

3、显式锁:Lock

API的链接:最新版API JDK11版本中文释义
Lock:是JDK5以后才出现的具体的类。使用lock是调用对应的API。是API层面的锁
首先我们来看一下Lock的API:
在这里插入图片描述
看完就一个感受,灵活啊,这也太活了,想锁就锁,想开就开,这不是任意妄为?但是CSDN也不是法外之地,所以不要太活。
那在我们进行源码操作之前,我们先来看一下它的构造方法。
在这里插入图片描述
那我们其实主要就是进行上锁与解锁。,剩下的呢就由大家自行探索了。
上锁的介绍:
请添加图片描述解锁的介绍:
在这里插入图片描述
好,上源码:

/**
 * 同步代码块和同步方法都属于隐式锁
 * 线程同步:Lock
 */
public class Demo10 {
    public static void main(String[] args) {
        //线程不安全
        //解决方案3. 显示锁Lock 子类 ReentrantLock
        Runnable run = new Ticket();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();

    }

    static class Ticket implements Runnable{
        //票数
        private int count=10;
        //显示锁
        private Lock l = new ReentrantLock();//1、 创建锁

        //private Lock l = new ReentrantLock(true);//fair参数为true就表示公平锁
        @Override
        public void run() {
            while (true){
                l.lock();//2、 上锁
                if (count>0){
                    //卖票
                    System.out.println("正在准备卖票");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    count--;
                    System.out.println("出票成功,余票:"+count);
                }else {
                    break;
                }
                l.unlock();//3、解锁
            }
        }
    }
}

测试结果是一样的:
在这里插入图片描述

总结

那其实从开始到现在了,想必大家对二者的区别也是有了一定的了解。

  1. 所谓的显示和隐式就是在使用的时候,使用者要不要手动写代码去获取锁和释放锁的操作。
    那其实对于手动操作的lock锁我更偏爱,因为它可以随意指定位置,我们在之前的API中也可以看出,我们甚至可以在执行的进程中去上锁中断它,就是这么无敌。

  2. 这也是为什么,在各博客说的等待是否可中断?
    1、synchronized是不可中断的。除非抛出异常或者正常运行完成 。
    2、 Lock可以中断的。中断方式:

    1. :调用设置超时方法tryLock(long timeout ,timeUnit unit)
    2. :调用lockInterruptibly()放到代码块中,然后调用interrupt()方法可以中断
    
  3. 加锁的时候是否可以公平?
    synchronized:非公平锁
    lock:两者都可以的。默认是非公平锁。在其构造方法的时候可以传入Boolean值。
    在这里插入图片描述true:公平锁
    false:非公平锁

  4. 从性能分析
    synchronized是托管给JVM执行的,而lock是java写的控制锁的代码。在Java1.5中,synchronize是性能低效的。因为这是一个重量级操作,需要调用操作接口,导致有可能加锁消耗的系统时间比加锁以外的操作还多。相比之下使用Java提供的Lock对象,性能更高一些。但是到了Java1.6,发生了变化。synchronize在语义上很清晰,可以进行很多优化,有适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致在Java1.6上synchronize的性能并不比Lock差。官方也表示,他们也更支持synchronize,在未来的版本中还有优化余地。
    但是我现在还是建议使用lock,未来是未来,现在那就使最优的啊,但是我们也要对synchronized有所了解就行了,到了用的时候咱也不怵。

好了,在观看了大量的精品复制博客后,我也摘抄了不少,但是学习一定要有自己的思考,这是我想说的。

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-07-26 11:56:40  更:2021-07-26 11:59:07 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年4日历 -2025/4/12 3:09:49-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码