【Java难点】多线程终极

news2024/11/26 23:58:03

悲观锁和乐观锁

悲观锁

synchronized关键字和Lock的实现类都是悲观锁。

它很悲观,认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会一不做二不休的先加锁,确保数据不会被别的线程修改。

适合写操作多的场景,先加锁可以保证写操作时数据正确。

实例:

image-20240428003636339

乐观锁

它很乐观,认为自己在使用数据时不会有别的线程修改数据或资源,所以不会添加锁。

在Java中是通过使用无锁编程来实现,只是在更新数据的时候去判断,之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果这个数据己经被其它线程更新,则根据不同的实现方式执行不同的操作,比如放弃修改、重试抢锁等等。

乐观锁的实现方式:

  • 采用Version版本号机制

  • 采用CAS算法实现

    实例:

image-20240428003707495

锁案例演示

案例1
import java.util.concurrent.TimeUnit;

public class JUC04 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()->{
            phone.sendEmail();
        },"a").start();
        new Thread(()->{
            phone.sendSMS();
        },"b").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

/**
 * 资源类
 */
class Phone{
    public synchronized void sendEmail(){
        System.out.println("--------sendEmail");
    }
    public synchronized void sendSMS(){
        System.out.println("--------sendSMS");
    }
}

请问先打印邮件还是短信?

image-20240428005339903

案例2
import java.util.concurrent.TimeUnit;

public class JUC04 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()->{
            phone.sendEmail();
        },"a").start();
        new Thread(()->{
            phone.sendSMS();
        },"b").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

/**
 * 资源类
 */
class Phone{
    public synchronized void sendEmail(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("--------sendEmail");
    }
    public synchronized void sendSMS(){
        System.out.println("--------sendSMS");
    }
}

请问先打印邮件还是短信?

image-20240428005646180

案例3
import java.util.concurrent.TimeUnit;

public class JUC04 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()->{
            phone.sendEmail();
        },"a").start();
        new Thread(()->{
            phone.hello();
        },"b").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

/**
 * 资源类
 */
class Phone{
    public synchronized void sendEmail(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("--------sendEmail");
    }
    public void hello(){
        System.out.println("--------hello");
    }
}

请问先打印邮件还是hello?

image-20240428010005046

案例4
import java.util.concurrent.TimeUnit;

public class JUC04 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        Phone phone2 = new Phone();
        new Thread(()->{
            phone.sendEmail();
        },"a").start();
        new Thread(()->{
            phone2.sendSMS();
        },"b").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

/**
 * 资源类
 */
class Phone{
    public synchronized void sendEmail(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("--------sendEmail");
    }
    public synchronized void sendSMS(){
        System.out.println("--------sendSMS");
    }
}

有两部手机,请问先打印邮件还是短信?

image-20240428010211612

案例5
import java.util.concurrent.TimeUnit;

public class JUC04 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()->{
            phone.sendEmail();
        },"a").start();
        new Thread(()->{
            phone.sendSMS();
        },"b").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

/**
 * 资源类
 */
class Phone{
    public static synchronized void sendEmail(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("--------sendEmail");
    }
    public static synchronized void sendSMS(){
        System.out.println("--------sendSMS");
    }
}

有两个静态同步方法,有一部手机,请问先打印邮件还是短信?

image-20240428010526197

案例6
import java.util.concurrent.TimeUnit;

public class JUC04 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        Phone phone2 = new Phone();
        new Thread(()->{
            phone.sendEmail();
        },"a").start();
        new Thread(()->{
            phone2.sendSMS();
        },"b").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

/**
 * 资源类
 */
class Phone{
    public static synchronized void sendEmail(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("--------sendEmail");
    }
    public static synchronized void sendSMS(){
        System.out.println("--------sendSMS");
    }
}

有两个静态同步方法,有两部手机,请问先打印邮件还是短信?

image-20240428010724241

案例7
 import java.util.concurrent.TimeUnit;

public class JUC04 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()->{
            phone.sendEmail();
        },"a").start();
        new Thread(()->{
            phone.sendSMS();
        },"b").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

/**
 * 资源类
 */
class Phone{
    public static synchronized void sendEmail(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }  
        System.out.println("--------sendEmail");
    }
    public static void sendSMS(){
        System.out.println("--------sendSMS");
    }
}

有一个静态同步方法,一个普通静态方法,有一部手机,请问先打印邮件还是短信?

image-20240428011114157

案例8
import java.util.concurrent.TimeUnit;

public class JUC04 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        Phone phone2 = new Phone();
        new Thread(()->{
            phone.sendEmail();
        },"a").start();
        new Thread(()->{
            phone2.sendSMS();
        },"b").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

/**
 * 资源类
 */
class Phone{
    public static synchronized void sendEmail(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("--------sendEmail");
    }
    public static void sendSMS(){
        System.out.println("--------sendSMS");
    }
}

有一个静态同步方法,一个普通静态方法,有两部手机,请问先打印邮件还是短信?

image-20240428011251980

笔记总结

案例1和案例2

一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了,其它的线程都只能等待,换句话说,某一个时刻内,只能有唯一的一个线程去访问这些synchronized方法,锁的是当前对象this,被锁定后,其它的线程都不能进入到当前对象的其它的 synchronized方法。

案例3和案例4

普通方法和同步锁无关,换成两个对象后,不是同一把锁了

案例5和案例6

对于普通同步方法,锁的是当前实例对象,通常指this,具体的一部手机,所有的普通同步方法用的都是同一把锁->实例对象本身;

对于静态同步方法,锁的是当前类的class 对象,如Phone.class;

对于同步代码块,锁的是 synchronized 扩号内的对象。

案例7和案例8

当一个线程试图访问同步代码块时,它首先必须得到锁,正常退出或抛出异常时必须释放锁。

所有的普通同步方法用的都是同一把锁-实例对象本身,就是new出来的具体实例对象本身,本类this
也就是说如果一个实例对象的普通同步方法获取锁后,该实例对象的其他普通同步方法必须等待获取锁的方法释放锁后才能再次获取锁。

所有的静态同步方法用的也是同一把锁-类对象(类名.class)本身,具体实例对象this 和类对象本身,这两把锁是两个不同的对象,所以静态同步方法与普通同步方法之间是不会有竞态条件的,但是一但一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁。

synchronized字节码分析

synchronized代码块
public class JUC05 {
    Object o=new Object();
    public void m1(){
        synchronized (o){
            System.out.println("----hello synchronized code block");
        }
    }
    public static void main(String[] args) {

    }
}

运行main方法,会在src的同级目录下生成一个target目录,进入target下的classes目录,找到JUC05.class,右键->打开于->终端,在终端中输入javap -c JUC05 ,对 JUC05.class进行反编译,得到如下信息:

image-20240428230628261

synchronized代码块使用的是monitorentermonitorexit指令来持有锁和释放锁。

问: 一定是1个monitorenter对应2个monitorexit吗?

答: 一般情况下,1个monitorenter对应2个monitorexit,但是也存在极端的情况,1个monitorenter对应1个monitorexit

image-20240428230221143

synchronized同步方法

public class JUC05 {
    public synchronized void m2(){
            System.out.println("----hello synchronized m2");
    }
    public static void main(String[] args) {
    }
}

运行main方法,会在src的同级目录下生成一个target目录,进入target下的classes目录,找到JUC05.class,右键->打开于->终端,在终端中输入javap -v JUC05 ,对 JUC05.class进行反编译,得到如下信息:

image-20240428231242956

调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置。如果设置了,执行线程会先持有montor锁, 然后再执行方法,最后在方法完成(无论是正常完成还是非正常完成)时释放monitor

synchronized静态同步方法

public class JUC05 {
    public synchronized void m2(){
            System.out.println("----hello synchronized m2");
    }
    public static synchronized void m3(){
            System.out.println("----hello static synchronized m3");
    }
    public static void main(String[] args) {
    }
}

运行main方法,会在src的同级目录下生成一个target目录,进入target下的classes目录,找到JUC05.class,右键->打开于->终端,在终端中输入javap -v JUC05 ,对 JUC05.class进行反编译,得到如下信息:

image-20240428231557432

ACC_STATIC, ACC_SYNCHRONIZED访问标识来区分该方法是否静态同步方法

synchronized底层原语分析

问: 为什么任何一个对象都可以成为一个锁?

答:

公平锁和非公平锁

公平锁

image-20240428235631627

image-20240429002250063

非公平锁

image-20240428235654107

image-20240429002237433

: 为什么会有公平锁和非公平锁的设计?为什么默认非公平?

  1. 恢复挂起的线程到真正锁的获取还是有时间差的,从开发人员来看这个时间微乎其微,但是从CPU的角度来看,这个时间差存在的还是很明显的。所以非公平锁能更充分的利用CPU 的时间片,尽量减少 CPU 空闲状态时间。

  2. 使用多线程很重要的考量点是线程切换的开销,当采用非公平锁时,当1个线程请求锁获取同步状态,然后释放同步状态,所以刚释放锁的线程在此刻再次获取同步状态的概率就变得非常大,所以就减少了线程的开销。

**问:**什么时候用非公平锁,什么时候用公平锁?

:如果为了更高的吞吐量,很显然非公平锁是比较合适的,因为节省很多线程切换时间,吞吐量自然就上去了;否则那就用公平锁,大家公平使用。

可重入锁(递归锁)

定义

可:可以;重:再次;入:进入;锁:同步锁。

image-20240429001200863

重入锁的种类

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1631982.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

船用组装式中央空调案例

船用组装式空调的整体介绍 1.1 装置的主要技术数据及配套设备规格 该轮采用的是船用组装式空调装置。 1.1.1 空调装置 1)型号:CJKR-116船用组装式空调装置;2)制冷型式:直接蒸发式;3)制冷量&…

【汇编】#6 80x86指令系统其二(串处理与控制转移与子函数)

文章目录 一、串处理指令1. 与 REP 协作的 MOVS / STOS / LODS的指令1.1 重复前缀指令REP1.2 字符串传送指令(Move String Instruction)1.2 存串指令(Store String Instruction)1.3 取字符串指令(Load String Instruct…

VULHUB复现fastjson1.2.24反序列化漏洞

蚌埠住了,遇到了很多奇奇怪怪的问题。 如果你问我为啥不用kali,我会告诉你,我电脑上的kali装不成docker-compose!我急用了ubuntu如果你问我为啥用ubuntu克隆,我会告诉你,我电脑上的kali不能安装成功java8这个版本如果你…

一二三应用开发平台使用手册——系统管理-权限项模块-使用说明

权限项 概述 在RBAC模型中,资源、角色、用户三个关键元素,构成权限体系。资源是权限控制的对象,因此常称之为权限项。 平台中所有的权限项进行集中管理,菜单、按钮、请求、分组等通过类型进行区分,实体与库表公用&a…

读天才与算法:人脑与AI的数学思维笔记13_Coq证明助手

1. 计算机 1.1. 对于计算机来说,它就很擅长处理这种重复而机械且计算量庞大的任务 1.1.1. 在速度与准确性等方面,计算机是远超过手工计算的 1.2. 计算机只能执行指令,并无自主创造力 1.2.1. 想…

第七天 dfs剪枝优化

第七天 dfs剪枝&优化 1可行性剪枝 2最优性剪枝 3重复性剪枝 题 1 输入 5 5 6 …S. XX.X. …X… …D.X …X… 输出 YES —————————————— 题解 #include<iostream> #include<cstdio> using namespace std; const int N 10; int n,m,T; char …

分治策略 --- 快排归并

目录 分治-快排 一、颜色分类 二、排序数组 三、数组中的第K个最大元素 四、库存管理 分治-归并 一、排序数组 二、交易逆序对的总数 三、计算右侧小于当前元素的个数 四、翻转对 分治是一种思想&#xff0c;也就是将大问题分解成小问题&#xff0c;一直分到小问题可…

【bug已解决】发生错误,导致虚拟 CPU 进入关闭状态。如果虚拟机外部发生此错误,则可能已导致物理计算机重新启动......

本bug报错已找到原因,并成功解决。 项目场景: vmware安装ubuntu报错。 如下: 发生错误,导致虚拟 CPU 进入关闭状态。如果虚拟机外部发生此错误,则可能已导致物理计算机重新启动。错误配置虚拟机、客户机操作系统中的错误或 VMware Workstation 中的问题都可以导致关闭状…

关于google search console工具提交sitemap.xml无法抓取的问题解决办法

其实这个问题很好解决。 第一种情况&#xff1a;利用工具为我们的网站自动生成静态的sitemap.xml文件。这种可以检查下是否完整&#xff0c;然后上传到根目录下去&#xff0c;再去google search console提交我们的网站地图。 第二种情况&#xff1a;同样利用工具自动生成动态s…

idea中使用GlassFish服务器启动项目

idea中使用GlassFish服务器进行测试 1.项目背景 当前在研究openMDM项目, 不过该项目不是springboot项目, 并且是使用GlassFish进行war部署的, 但是需要在idea中进行项目的二次开发,故需要进行idea启动项目并且进行开发和调试 2.GlassFish是什么 GlassFish是一个web服务器, …

用来传输文件的协议-FTP

一.FTP协议--文件传输协议 1.了解FTP协议 &#xff08;1&#xff09;FTP服务是用来传输文件的协议 FTP&#xff08;File Transfer Protocol&#xff0c;文件传输协议&#xff09;是TCP/IP协议组中的协议之一&#xff0c;用于互联网上的控制文件的双向传输。是传输文件到Linu…

图像置乱加密-Arnold加密算法

置乱加密是另一种较常用的加密方法&#xff0c;现也被许多文献选用&#xff0c;置乱加密可以是以像素为单位进行全局置乱&#xff0c;该方式打乱了图像像素值的位置&#xff0c;使其图像内容失去相关性&#xff0c;达到保护的目的。也可以是以块为单位进行置乱&#xff0c;该方…

软件开发技巧---TODO特殊事项标注

软件开发技巧—TODO特殊事项标注 文章目录 软件开发技巧---TODO特殊事项标注1、前言2、环境3、TODO注释规范4、Qt中使用TODO5、VS中使用TODO6、总结 更多精彩内容&#x1f449;个人内容分类汇总 &#x1f448;&#x1f449;开发工具 &#x1f448; 1、前言 &#x1f9d8;&…

头歌:Spark的安装与使用

第1关&#xff1a;Scala语言开发环境的部署 相关知识 Scala是一种函数式面向对象语言&#xff0c;它融汇了许多前所未有的特性&#xff0c;而同时又运行于JVM之上。随着开发者对Scala的兴趣日增&#xff0c;以及越来越多的工具支持&#xff0c;无疑Scala语言将成为你手上一件…

电脑已经有了一个Windows10,再多装一个Windows10组成双系统

前言 前段时间已经讲过一次双Windows系统的安装教程&#xff0c;但是小白重新去看了一下&#xff0c;发现写的内容太多&#xff0c;怕小伙伴看了之后一脸萌。 所以今天咱们就重新再来讲讲&#xff1a;在同一台机器上安装Windows10双系统的教程。 注意哦&#xff01;这里的Wi…

paddlehub的简单应用

1、下载安装 pip install paddlehub -i https://pypi.tuna.tsinghua.edu.cn/simple 报错&#xff1a; Collecting onnx<1.9.0 (from paddle2onnx>0.5.1->paddlehub)Using cached https://pypi.tuna.tsinghua.edu.cn/packages/73/e9/5b953497c0e36df589fc60cc6c6b35…

语音识别的基本概念

语音识别的基本概念​​​​​​​ ​​​​​​​ 言语是一种复杂的现象。人们很少了解它是如何产生和感知的。天真的想法常常是语音是由单词构成的&#xff0c;而每个单词又由音素组成。不幸的是&#xff0c;现实却大不相同。语音是一个动态过程&#xff0c;没有明确区分的…

【Unity动画系统】详解Root Motion动画在Unity中的应用(二)

Root Motion遇到Blend Tree 如果Root Motion动画片段的速度是1.8&#xff0c;那么阈值就要设置为1.8&#xff0c;那么在代码中的参数就可以直接反映出Root Motion的最终移动速度。 Compute Thresholds&#xff1a;根据Root Motion中某些数值自动计算这里的阈值。 Velocity X/…

公共代理IP与独享代理IP的区别是什么?

IP地址&#xff0c;是网络世界中设备互相识别的重要线索&#xff0c;当我们谈论相关话题的时候&#xff0c;总会听说“公共IP”和“独享IP”这两个词。作为用户&#xff0c;我们该如何选择更适合自己的IP资源呢&#xff0c;两者又有何区别&#xff1f; 承载用户量&#xff1a;公…

ArcGIS小技巧—基于DEM的河网提取

1、使用DEM数据提取河流水系网络 原始DEM数据中存在误差&#xff0c;或喀斯特地貌等真实地形情况&#xff0c;将引起DEM数据中存在凹陷区域。 在进行水流方向的计算上&#xff0c;如果有洼地会造成错误&#xff0c;因此我们需要进行填洼处理&#xff0c;获得相对准确的DEM数据…