JUC并发、JVM相关

news2024/11/22 16:35:21

文章目录

  • JUC并发
    • synchronized
      • 锁对象
      • 底层原理
    • synchronized锁升级
    • reentrantlock
      • 公平锁和非公平锁
      • 可重入锁 / 递归锁
    • 死锁
      • 死锁产生条件
      • 如何排查死锁?如果解决死锁?
    • LockSupport与中断机制
      • 中断机制
        • 中断相关的三大API
        • 如何中断运行中的线程?
      • LockSupport
        • LockSupport原理与优势
  • JMM
    • happens-before(先行发生原则)
  • CAS
    • Unsafe
  • ThreadLocal
    • 基本使用
    • 使用规范
  • 对象内存布局

JUC并发

synchronized

锁对象

synchronized 有三种应用场景:

  • 作用于非静态实例方法:对当前实例加锁,进入同步方法前,需获得当前实例的锁,此时它是一个对象锁
  • 作用于静态实例方法:对实例所属类加锁,进入静态同步方法前,需要获得当前类对象的锁,此时它是一个类锁
  • 作用于代码块:对括号中配置的对象加锁。

底层原理

  • 1、管程(英语:monitors,也称为监视器)是一种程序结构,结构内的多个子程序(对象或模块〉形成的多个工作线程互斥访问共享资源。这些共享资源一般是硬件设备或一群变量。对共享变量能够进行的所有操作集中在一个模块中。(把信号量及其操作原语“封装”在一个对象内部) 管程实现了在一个时间点,最多只有一个线程在执行管程的某个子程序。管程提供了一种机制,管程可以看做一个软件模块,它是将共享的变量和对于这些共享变量的操作封装起来,形成一个具有一定接口的功能模块,进程可以调用管程来实现进程级别的并发控制。
  • 2、Java中每个对象都继承自Object,而每个Object 的对象都关联一个monitor,该monitor在C语言底层中对应一个ObjectMonitor结构体,有_owner属性记录持有该对象的线程,有_count属性,记录该线程获取monitor锁的次数,同一时间只有一个线程能够持有对象的monitor,这是Java中所有对象都可以成为锁对象,且可以重复获取锁的基础。
    在这里插入图片描述
  • 3、字节码标识:
    • synchronized同步代码块实现使用的是monitorentermonitorexit指令来标识获取到对象的monitor和释放对象的monitor,每次monitorenter进入,锁重入次数加1,每次monitorexit退出,锁重入次数减1。
    • synchronized同步方法底层使用 ACC_SYNCHRONIZED访问标志,标识同步方法,并使用ACC_STATIC区分该方法是否静态同步方法,即有ACC_SYNCHRONIZEDACC_STATIC标识的是静态同步方法,只有ACC_SYNCHRONIZED标识的是普通同步方法。静态同步方法会持有类对象的monitor,而普通同步方法会持有实例对象的monitor.

synchronized锁升级

reentrantlock

公平锁和非公平锁

在这里插入图片描述
reentrantlock默认是非公平锁,主要有以下两点考虑:

  1. 恢复挂起的线程到真正锁的获取还是有时间差的,从开发人员来看这个时间微乎其微,但是从CPU的角度来看,这个时间差存在的还是很明显的。所以非公平锁能让真正运行的线程先获取到锁,更充分的利用CPU的时间片,尽量减少CPU空闲状态时间。
  2. 使用多线程很重要的考量点是线程切换的开销,当采用非公平锁时,当1个线程请求锁获取同步状态,然后释放同步状态,所以刚释放锁的线程在此刻再次获取同步状态的概率就变得非常大,所以就减少了线程的开销。

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

可重入锁 / 递归锁

可重入锁的优点在于,可以在一定程度上避免死锁,一个加了锁的方法,如果递归调用自身,此时使用不可重入锁,就会自己把自己锁住。

死锁

死锁产生条件

  • 存在互斥访问的锁
  • 已经获取锁不可被强行剥离
  • 没有完全获取到所需要的锁时,会持有锁并等待。
  • 存在循环等待锁释放

举一个MySQL中,由于两个事务的间隙锁互相兼容,而插入意向锁与间隙互斥导致死锁的案例:
在这里插入图片描述

  • time1阶段:事务A加间隙锁,范围(20, 30)
  • time2阶段:事务B加间隙锁,范围(20, 30)
  • time3阶段:事务A尝试给id为25的位置加插入意向锁,但是发现事务B在(20,30)间设置了间隙锁,加锁失败,阻塞,等待事务B释放间隙锁。
  • time4阶段:事务B尝试给id为26的位置加插入意向锁,但是发现事务A在(20,30)间设置了间隙锁,加锁失败,阻塞,等待事务A释放间隙锁。

事务A和事务B相互等待对方释放锁,满足了死锁的四个条件:互斥、占有且等待、不可强占用、循环等待,因此发生了死锁。

如何排查死锁?如果解决死锁?

命令行版:

  1. jps -l
    列出所有Java进程号,找到发生死锁的进程,假如是1123
  2. jstack 1123
    查看进程栈信息,就可以定位到死锁的情况

图形工具版:
使用jconsole工具,也可以定位正在运行的Java进程的死锁情况。

LockSupport与中断机制

中断机制

  • 一个线程不应该由其他线程来强制中断或停止,而是应该山线程自己自行停止,自己来决定自己的命运。
  • 在Java中没有办法立即停止一条线程,然而停止线程却非常重要,如取消一个耗时操作。因此,Java提供了一种用于停止线程的协商机制——中断,也即中断标识协商机制。
  • 中断只是一种协作协商机制,Java没有给中断增加任何语法,中断的过程完全需要程序员自己实现。若要中断一个线程,你需要手动调用该线程的interrupt方法,该方法也仅仅是将线程对象的中断标识设成true;接着你需要自己写代码不断地检测当前线程的标识位,如果为true,表示别的线程请求这条线程中断,此时究竟该做什么需要你自己写代码实现。
  • 每个线程对象中都有一个中断标识位,用于表示线程是否被中断;该标识位为true表示中断,为false表示未中断;通过调用线程对象的interrupt方法将该线程的标识位设为true;可以在别的线程中调用,也可以在自己的线程中调用。

中断相关的三大API

在这里插入图片描述

  1. interrupt():
    • 当一个线程调用interrupt()时,如果线程处于正常活动状态,那么会将该线程的中断标志设置为true,仅此而已,被设置中断标志的线程将继续正常运行,不受影响。即interrupt()仅是设置中断标识,并不实际中断线程,需要被调用的线程进行配合。就像你让一个人闭嘴,但最终那人是否闭嘴,需要它的配合。
    • 如果线程处于被阻塞的状态(例如sleep、wait、join等状态),在别的线程中调用当前线程对象的interrupt()方法,那么线程将立即退出被阻塞状态,中断状态标志位将被清除,并抛出一个InterruptedException异常。
    • 对于不活动的线程,调用interrupt()没有任何影响。
  2. public boolean isInterrupted():检测当前实例的中断标志位是否被设置为中断。
  3. public static boolean interrupted():静态方法,会调用当前线程的isInterrupted(),并传入参数,清除中断标志位。

从源码中可以看到,实例方法默认是不清除中断状态的,而静态方法则会。
在这里插入图片描述

如何中断运行中的线程?

  1. 通过volatile关键字,进行线程间通信,线程每次都会读取volatile关键字最新的值。
public class demo1 {
    private static volatile boolean isStop;
    public static void main(String[] args) {
        new Thread( () -> {
            while(!isStop) {}
            System.out.println(Thread.currentThread().getName() + "收到停止!");
        },"A").start();
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (Exception e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "发送停止信号!");
            isStop = true;
        }, "B").start();
    }
}

在这里插入图片描述

  1. 通过AtomicBoolean 原子布尔型,
public class demo1 {
    private static volatile boolean isStop;
    private static AtomicBoolean flag = new AtomicBoolean(false);
    public static void main(String[] args) {
        new Thread( () -> {
//            while(!isStop) {}
            while(!flag.get()) {}
            System.out.println(Thread.currentThread().getName() + "收到停止!");
        },"A").start();
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (Exception e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "发送停止信号!");
//            isStop = true;
            flag.set(true);
        }, "B").start(
        );
    }
}
  1. 通过线程自带的中断API,线程2中使用线程1的interrupt()方法,而线程1中使用isInterrupted()方法来监听。
public class demo1 {
    private static volatile boolean isStop;
    private static AtomicBoolean flag = new AtomicBoolean(false);
    public static void main(String[] args) {
        Thread t1 = new Thread( () -> {
//            while(!isStop) {}
//            while(!flag.get()) {}
            while (!Thread.currentThread().isInterrupted()) {}
            System.out.println(Thread.currentThread().getName() + "收到停止!");
        },"A");
        t1.start();
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (Exception e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "发送停止信号!");
//            isStop = true;
//            flag.set(true);
            t1.interrupt();
        }, "B").start(
        );
    }
}

LockSupport

在这里插入图片描述
synchronized和reentrantlock的线程等待和线程唤醒有如下局限性:

  1. 对象的wait()和notify()方法必须要在同步代码块或者同步方法里边运行,且成对出现必须先wait后notify才ok。
  2. reentrantlock构建的condition的await()方法和signal()方法必须先获取到reentrantlock锁,才可以使用,且成对出现,必须先await()后signal();

基于synchronized和reentrantlock实现三个线程交替打印A、B、C:

package Interrupt;

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

public class demo1 {
    public static void main(String[] args) {
        Data d = new Data();
        for (int i = 0; i < 3; ++i) {
            final int temp = i;
            new Thread( () -> {
                for (int j = 0; j < 10; ++j) {
                    d.print2(temp, (char) ('A' + temp));
                }
            }).start();
        }
    }
}
class Data {
    private int flag = 0;
    private final Object lock = new Object();
    private final Lock lk = new ReentrantLock();
    private final Condition cd = lk.newCondition();
    void print(int i, char letter)  {
        synchronized (lock) {
            try {
                while (i != flag) lock.wait();
                System.out.println(letter);
                flag = (flag + 1) % 3;
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.notifyAll();
            }
        }
    }
    void print2(int i, char letter) {
        lk.lock();
        new Object();
        try {
            while (i != flag) cd.await();
            System.out.println(letter);
            flag = (flag + 1) % 3;
            cd.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lk.unlock();
        }
    }
}

LockSupport原理与优势

  • LockSupport是concurrent包中一个工具类,不支持构造,提供了一堆static方法,比如park(),unpark()等。
  • LockSupport提供park()和unpark()方法实现阻塞线程和解除线程阻塞的过程LockSupport和每个使用它的线程都有一个许可(permit)关联。每个线程都有一个相关的permit, permit最多只有一个,重复调用unpark也不会积累凭证。
  • 当调用park():
    • 如果有凭证,直接消耗掉这个凭证,然后正常退出。
    • 如果无凭证,就必须阻塞等待凭证可用。
  • 当调用unpark():
    • 给指定线程增加一个凭证,但凭证最多只能有一个,不可累加。

LockSupport优势在于灵活性:

  • LockSupport的park()和unpark()方法不需要在同步代码块内,或者持有锁。
  • unpark()可以先于park()方法调用,不用担心线程间的执行的先后顺序,因为如果先执行unpark(),为指定线程提前赋予了凭证,那么该线程在调用park()时,直接低效已有的凭证。

下边是一个主线程等待子线程任务执行完毕,再唤醒主线程的例子:
注意在使用时,LockSupport的unpark()方法需要指定线程,因此第一步,我们需要获取到主线程md

public class demo1 {
    public static void main(String[] args) {
        Thread mt = Thread.currentThread();
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println("子线程执行完毕。去唤醒主线程");
                LockSupport.unpark(mt);
            }
        }).start();
        System.out.println("等待子线程执行完毕");
        LockSupport.park();
        System.out.println("主线程被唤醒");
    }
}

JMM

JMM(Java内存模型Java Memory Model,简称JMM)本身是一种抽象的概念并不真实存在它仅仅描述的是一组约定或规范,通过这组规范定义了程序中(尤其是多线程)各个变量的读写访问方式并决定一个线程对共享变量的写入何时以及如何变成对另一个线程可见,关键技术点都是围绕多线程的原子性、可见性和有序性展开的。

happens-before(先行发生原则)

CAS

CAS是一条CPU的原子指令(cmpxchg指令),不会造成所谓的数据不一致问题,Unsafe提供的CAS方法(如compareAndSwapXXX)底层实现即为CPU指令cmpxchg

执行cmpxchg指令的时候,会判断当前系统是否为多核系统,如果是就对总线加锁,只有一个线程能加速成功,加锁成功之后会执行cas操作,也就是说CAS的原子性实际上是CPU实现独占的,比起用synchronized重量级锁,这里的排他时间要短很多,所以在多线程情况下性能会比较好。

Unsafe

Unsafe是CAS核心类,由于Java无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe类相当于一个后门,其内部方法都是native修饰的,可以像C的指针一样直接操作内存。

Unsafe类的compareAndSwapObject,相对于Java中调用的函数,多个两个与内存操作相关的属性,var1和var2,var1表示要操作的对象,而var2表示操作对象属性地址的偏移量。
在这里插入图片描述
以unsafe类实现的原子类Int为例:
当compareAndSwap()执行失败时,会陷入循环,即自旋,仅当执行成功时,跳出循环。
在这里插入图片描述

ThreadLocal

基本使用

ThreadLocal提供线程局部变量。这些变量与正常的变量不同,因为每一个线程在访问ThreadLocal实例的时候(通过其get或set方法)都有自己的、独立初始化的变量副本。ThreadLocal实例通常是类中的私有静态字段,使用它的目的是希望将状态(例如,用户ID或事务ID)与线程关联起来。

常用方法:
在这里插入图片描述
设置初始化方法:
protected T initialValue()
每个线程在第一次访问这个值的get()方法,会返回这个初始化方法的结果。
在这里插入图片描述

JDK8,可以用withInitial() 简化上边的写法:
在这里插入图片描述
案例:多线程模拟买票统计

public class demo1 {
    public static void main(String[] args) {
        SaleData sd = new SaleData();
        for (int i = 0; i < 5; ++i) {
            new Thread(() -> {
                int j = Math.abs(new Random().nextInt()) % 20;
                while (j-- != 0) {
                    sd.sale();
                }
                System.out.println(Thread.currentThread().getName() + "卖出:" + sd.saleNum.get());
            }).start();
        }
    }
}
class SaleData {
    ThreadLocal<Integer> saleNum = ThreadLocal.withInitial(() -> 0);
    public void sale() {
        saleNum.set(saleNum.get() + 1);
    }
}

使用规范

每个线程都有自己的线程栈,栈空间大小是有效的,因此必须回收自定义的ThreadLocal变量,尤其在线程池场景下,线程经常会被复用,如果不清理自定义的ThreadLocal变量,造成ThreadLocal变量累积,可能会影响后续业务逻辑和造成内存泄露问题。

尽量在try - finally 块进行回收。

对象内存布局

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

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

相关文章

【C++】C++11--- 线程库及详解lock_guard与unique_lock

目录 一、thread类的介绍二、线程函数参数三、 原子性操作库四、lock_guard与unique_lock4.1、mutex的种类4.2 lock_guard4.3 unique_lock 一、thread类的介绍 在C11之前&#xff0c;涉及到多线程问题&#xff0c;都是和平台相关的&#xff0c;比如**windows和linux下各有自己…

【css】属性选择器

有些场景中需要在相同元素中获取具有特定属性的元素&#xff0c;比如同为input&#xff0c;type属性有text、button&#xff0c;可以通过属性选择器设置text和button的不同样式。 代码&#xff1a; <style> input[typetext] {width: 150px;display: block;margin-bottom…

自动配置要点解读

目录 要点1&#xff1a;什么是自动配置&#xff1f; 要点2&#xff1a;配置文件与默认配置 要点3&#xff1a;自动配置设置思想来源 要点4&#xff1a;spring.factories文件作用 要点5&#xff1a;自动配置的核心 本文只对自动配置的思想进行基本的解读&#xff0c;不涉…

21、p6spy输出执行SQL日志

文章目录 1、背景2、简介3、接入3.1、 引入依赖3.2、修改database参数&#xff1a;3.3、 创建P6SpyLogger类&#xff0c;自定义日志格式3.4、添加spy.properties3.5、 输出样例 4、补充4.1、参数说明 1、背景 在开发的过程中&#xff0c;总希望方法执行完了可以看到完整是sql语…

通用人工智能操作系统

随着科技的飞速发展&#xff0c;人工智能已经成为了当今世界最热门的技术领域之一。从智能手机、自动驾驶汽车到智能家居系统&#xff0c;人工智能技术已经渗透到了我们生活的方方面面。然而&#xff0c;尽管人工智能在很多领域取得了显著的成果&#xff0c;但它仍然存在一些局…

matplotlib+tkinter实现一个简单的绘图系统

文章目录 封装成类布局实现绘图功能 绘图系统系列&#xff1a;将matplotlib嵌入到tkinter 封装成类 在理解matplotlib嵌入到tkinter中的原理之后&#xff0c;就已经具备了打造绘图系统的技术基础&#xff0c;接下来要做的&#xff0c;就是做一个较有可读性的绘图类&#xff0…

Java异常体系总结(下篇)

目录 1. 异常处理的三种方法 1.1 JVM 默认处理异常 1.2 通过 try...catch...自己处理异常 1.3 使用 throws和throw 抛出异常 1.3.1 使用 throws 抛出异常 1.3.2 使用 throw 抛出异常 2. try...catch.. 捕获到异常之后代码的执行顺序&#xff1f; 3. try...catch... 相关…

Mysql进阶(中) -- 索引

索引上部分 -> Mysql进阶(上) -- 存储引擎&#xff0c;索引_千帐灯无此声的博客-CSDN博客 &#x1f442; 爸爸妈妈 - 王蓉 - 单曲 - 网易云音乐 &#x1f448;目录看左栏 目录 &#x1f33c;索引 &#x1f43b;性能分析 - show profiles &#x1f43b;性能分析 - exp…

Cocos 适配 HarmonyOS NEXT,亮相 HDC2023,携手华为共筑鸿蒙生态!

HDC 2023 8月4-6日&#xff0c;作为华为合作伙伴&#xff0c;Cocos 引擎应邀参加了华为开发者大会 2023 - HDC 2023 暨 HarmonyOS 4 发布会&#xff0c;并获得了【鸿蒙生态能力共创奖】。 8月5日&#xff0c;在华为开发者大会&#xff08;HDC.Together&#xff09;游戏服务论坛…

SpringBoot系列---【使用jasypt把配置文件密码加密】

使用jasypt把配置文件密码加密 1.引入pom坐标 <dependency><groupId>com.github.ulisesbocchio</groupId><artifactId>jasypt-spring-boot-starter</artifactId><version>3.0.5</version> </dependency> 2.新增jasypt配置 2.1…

HCIP-linux知识

linux安装教程参考&#xff0c;https://blog.51cto.com/cloudcs/5245337 yum源配置 本地yum源配置&#xff1a; 8版本配置&#xff1a;将光盘iso挂载到某个目录&#xff0c;/dev/cdrom是/dev/sr0软链接&#xff0c;# mount /dev/cdrom /mnt&#xff0c;# ls /mnt AppStream B…

Elastic:linux设置elasticsearch、kibana开机自启

0. 引言 每次启动服务器都要手动启动es服务&#xff0c;相当之不方便&#xff0c;为此&#xff0c;书写一个脚本&#xff0c;实现es、kibana的开机自启 1. 原理 首先任何服务要实现开机自启&#xff0c;都可分为如下三步&#xff1a; 1、在/etc/init.d目录下创建启动、关闭服…

跳表与Redis

跳表原理 跳表是Redis有序集合ZSet底层的数据结构 首先有一个头结点 这个头结点里面的数据是null 就是他就是这个链表的最小值 就算是Math.Min也比它大 然后我们新建一个节点的时候是怎么操作的呢 先根据参数(假如说是5)创建一个节点 然后把它放在对应位置 就是找到小于他的最…

(JS逆向专栏十一)某融平台网站登入RSA

声明: 本文章中所有内容仅供学习交流&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff0c;若有侵权&#xff0c;请联系我立即删除&#xff01; 名称:点融 目标:登入参数 加密类型:RSA 目标网址:https://www.dianrong.com/accoun…

java: 无法访问org.springframework.web.bind.annotation.GetMapping(springboot构建时出现问题)

spring boot构建完成后出现以下问题 报错原因&#xff1a;SpringBoot 3.0以上版本要求JDK 17以上&#xff0c;jdk版本1.8 与 spring boot 3.0.1 版本不匹配 解决方法&#xff1a;

ORA-48913: Writing into trace file failed, file size limit [50000000] reached

检查某环境的alert_orcl1.log时&#xff0c;发现有很多的ORA-48913报错&#xff0c;细节如下 Sat Jul 22 19:34:04 2023 Non critical error ORA-48913 caught while writing to trace file "/u01/app/oracle/diag/rdbms/orcl/orcl1/trace/orcl1_dw00_138010.trc" E…

Python 中的机器学习简介:多项式回归

一、说明 多项式回归可以识别自变量和因变量之间的非线性关系。本文是关于回归、梯度下降和 MSE 系列文章的第三篇。前面的文章介绍了简单线性回归、回归的正态方程和多元线性回归。 二、多项式回归 多项式回归用于最适合曲线拟合的复杂数据。它可以被视为多元线性回归的子集。…

BenchmarkSQL 支持 TiDB 驱动以及 tidb-loadbalance

作者&#xff1a; GangShen 原文来源&#xff1a; https://tidb.net/blog/3c274180 使用 BenchmarkSQL 对 TiDB 进行 TPC-C 测试 众所周知 TiDB 是一个兼容 MySQL 协议的分布式关系型数据库&#xff0c;用户可以使用 MySQL 的驱动以及连接方式连接 TiDB 进行使用&#xff0…

Butterfly安装文档(三)主题配置-1

语言 修改站点配置文件 _config.yml 默认语言是 en 主题支持三种语言 default(en)zh-CN (简体中文)zh-TW (繁体中文) 网站资料 修改网站各种资料&#xff0c;例如标题、副标题和邮箱等个人资料&#xff0c;请修改博客根目录的_config.yml 导航栏设置 (Navigation bar set…

Data analysis|Tableau基本介绍及可实现功能

一、基础知识介绍 &#xff08;一&#xff09;什么是tableau tableau 成立于 2003 年&#xff0c;是斯坦福大学一个计算机科学项目的成果&#xff0c;该项目旨在改善分析流程并让人们能够通过可视化更轻松地使用数据。Tableau可以帮助用户更好地理解和发现数据中的价值&#x…