Java多线程核心技术一-多线程基础其他内容

接上篇:

Java多线程核心技术一-基础篇synchronzied同步方法

Java多线程核心技术一-基础篇synchronzied同步语句块

1 String常量池特性与同步问题

        JVM具有String常量池的功能,如下示例:

public class Test01 {
    public static void main(String[] args) {
        String a = "a";
        String b = "a";
        System.out.println(a == b);
    }
}

6e186de4f2434c528d535466cef09f70.png

        在把synchronzied(string)同步块与String联合使用时,要注意常量池会带来一些意外。

public class Service {
    public static void print(String param){
        try {
            synchronized (param){
                while(true){
                    System.out.println(Thread.currentThread().getName());
                    Thread.sleep(500);
                    if(Thread.currentThread().getName().equals("B")){
                        break;
                    }
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

 

public class Run1 {
    public static void main(String[] args) {
        Service service = new Service();
        ThreadA a = new ThreadA(service);
        a.setName("A");
        a.start();
        ThreadB b = new ThreadB(service);
        b.setName("B");
        b.start();
    }
}
public class ThreadA extends Thread{
    private Service service;

    public ThreadA(Service service) {
        this.service = service;
    }
    @Override
    public void run(){
        service.print("A");
    }
}
public class ThreadB extends Thread{
    private Service service;

    public ThreadB(Service service) {
        this.service = service;
    }

    @Override
    public  void run(){
        service.print("A");
    }
}

17c030f20af94fc1855e987f2491cb1e.png

        出现这种情况就是因为String的两个值都是"AA",两个线程是持有相同的锁,造成线程B不能执行。这就是String常量池所带来的问题,所以大多数情况下,同步synchronzied都不使用String作为锁对象,而改用其他的。例如 new Object()实例化一个新的object对象时,它并不放入缓存池中,或者执行new String()创建不同的字符串对象,形成不同的锁。下面把synchronzied代码块的锁的对象改成object。

public class Service1 {
    public static void print(Object obj){
        try{
            synchronized (obj){
                while(true){
                    System.out.println(Thread.currentThread().getName());
                    Thread.sleep(500);

                }
            }

        }catch (InterruptedException e){
            e.printStackTrace();
        }


    }
}

 

public class ThreadA1 extends Thread{
    private Service1 service1;

    public ThreadA1(Service1 service1) {
        this.service1 = service1;
    }
    @Override
    public void run(){
        service1.print(new Object());
    }
}
public class ThreadB1 extends Thread{
    private Service1 service1;

    public ThreadB1(Service1 service1) {
        this.service1 = service1;
    }

    @Override
    public void run(){
        service1.print(new Object());
    }
}
public class Run2 {
    public static void main(String[] args) {
        Service1 service1 = new Service1();
        ThreadA1 a = new ThreadA1(service1);
        a.setName("A");
        a.start();
        ThreadB1 b = new ThreadB1(service1);
        b.setName("B");
        b.start();
    }
}

321cfc012d5048bda2cb2329ecd1b2da.png

A 和B 交替输出的原因是持有的锁不是同一个。

2 多线程死锁

        Java线程死锁是一个经典的多线程问题,因为不同的线程都在等待根本不可能被释放的锁,导致所有的任务都无法继续完成。在多线程技术中,死锁是必须要避免的,因为这会造成线程的“假死”。示例:

public class DealThread implements Runnable{
    public String username;
    public Object lock1 = new Object();
    public Object lock2 = new Object();
    public void setFlag(String username){
        this.username = username;
    }

    @Override
    public void run() {
        if(username.equals("a")){
            synchronized (lock1){
                try {
                    System.out.println("username = " + username);
                    Thread.sleep(3000);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                synchronized (lock2){
                    System.out.println("按lock1 -> lock2代码顺序执行了");
                }
            }
        }
        if(username.equals("b")){
            synchronized (lock2){
                try {
                    System.out.println("username = " + username);
                    Thread.sleep(3000);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                synchronized (lock1){
                    System.out.println("按lock2 -> lock1代码顺序执行");
                }
            }
        }
    }
}

 

public class Run1 {
    public static void main(String[] args) {
        try {
            DealThread t1 = new DealThread();
            Thread a = new Thread(t1);
            t1.setFlag("a");
            a.start();
            Thread.sleep(100);
            t1.setFlag("b");
            Thread b = new Thread(t1);
            b.start();
        }catch (InterruptedException e){
            e.printStackTrace();
        }

    }
}

4b42cb22783141c0be1d515638044908.png

        可以使用JDK自带的工具来检测是否有死锁现象,进入jdk/bin目录下,执行jps命令:

3b4e10bdc2254d059e755c7ac5deb595.png

         看到运行线程Run1的id值是12880,在执行jstack命令,查看结果:

d0cb584ba07f40e59e6c8e7a7053013e.png

        如上图,检测出有死锁 。

        死锁是程序设计的bug,在设计程序时就要避免双方互相持有对方的锁,只要互相等待对方释放锁,就有可能出现死锁。

3 内置类与静态内置类

        synchronzied关键字的知识点还涉及内置类的使用,先来看一个简单的内置类的测试。

public class PublicClass {
    private String username;
    private String password;

    class PrivateClass{
        private String age;
        private String address;

        public String getAge() {
            return age;
        }

        public void setAge(String age) {
            this.age = age;
        }

        public String getAddress() {
            return address;
        }

        public void setAddress(String address) {
            this.address = address;
        }
        public void printPublicProperty(){
            System.out.println(username + ";"+password);
        }
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}
public class Run1 {
    public static void main(String[] args) {
        PublicClass publicClass = new PublicClass();
        publicClass.setUsername("usernameValue");
        publicClass.setPassword("passwordValue");
        System.out.println(publicClass.getUsername() + ";" + publicClass.getPassword());
        PublicClass.PrivateClass privateClass = publicClass.new PrivateClass();
        privateClass.setAge("ageValue");
        privateClass.setAddress("addressValue");
        System.out.println(privateClass.getAge() + ";" + privateClass.getAddress());
    }
}

1f9ffda55d414362958d658c75c649ff.png

        如果PublicClass.java类和Run.java类不在同一个包中,则需要将PrivateClass内置类声明为public。想要实例化内置类,必须使用如下代码:

PublicClass.PrivateClass privateClass = publicClass.new PrivateClass();

         还有一种内置类叫静态内置类。

public class PublicClass1 {
    static private String username;
    static private String password;

    static class PrivateClass{
        private String age;
        private String address;

        public String getAge() {
            return age;
        }

        public void setAge(String age) {
            this.age = age;
        }

        public String getAddress() {
            return address;
        }

        public void setAddress(String address) {
            this.address = address;
        }

        public void printPublicProperty(){
            System.out.println(username + ";"+password);
        }
    }

    public static String getUsername() {
        return username;
    }

    public static void setUsername(String username) {
        PublicClass1.username = username;
    }

    public static String getPassword() {
        return password;
    }

    public static void setPassword(String password) {
        PublicClass1.password = password;
    }
}
public class Run2 {
    public static void main(String[] args) {
        PublicClass publicClass = new PublicClass();
        publicClass.setUsername("usernameValue");
        publicClass.setPassword("passwordValue");
        System.out.println(publicClass.getUsername() + ";" + publicClass.getPassword());

        PublicClass1.PrivateClass privateClass = new PublicClass1.PrivateClass();
        privateClass.setAge("ageValue");
        privateClass.setAddress("addressValue");
        System.out.println(privateClass.getAge() + ";" + privateClass.getAddress());
    }
}

4 内置类的同步

        本节测试的案例是内置类中有两个同步方法,但使用不同的锁,输出的结果也是异步的。

public class Inner1 {
    public void method1(){
        synchronized ("其他的锁"){
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "; i = " +(i + 1));
                try {
                    Thread.sleep(100);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        }
    }

    synchronized public void method2(){
        for (int i = 11; i < 20; i++) {
            System.out.println(Thread.currentThread().getName() + "; i = " + (i + 1));
            try {
                Thread.sleep(100);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}
public class Run1 {
    public static void main(String[] args) {
        final Inner1 inner1 = new Inner1();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                inner1.method1();
            }
        },"A");

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                inner1.method2();
            }
        }, "B");
        t1.start();
        t2.start();
    }
}

0ed42276ddfe43e3a0a422df0e8e5c41.png

由于持有不同的锁,所有输出结果是乱序的。

 5 锁对象改变导致异步执行

        在把任何数据类型作为同步锁时,需要注意是否有多个线程同时争抢锁对象,如果同时争抢相同的锁对象,则这些线程之间就是同步的;如果分别获得自己的锁,那么这些线程之间就是异步的。通常情况下,一旦持有锁后就不再对锁对象进行更改,因为一旦更改就有可能出现一些错误。

public class MyService {
    private String lock = "123";
    public void testMethod(){
        try {
            synchronized (lock){
                System.out.println(Thread.currentThread().getName() + "开始时间是:" + System.currentTimeMillis());
                lock = "456";
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName() + "结束时间时: " + System.currentTimeMillis());
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
public class ThreadA extends Thread{

    private MyService myService;

    public ThreadA(MyService myService) {
        this.myService = myService;
    }

    @Override
    public void run(){
        myService.testMethod();
    }
}
public class ThreadB extends Thread{
    private MyService myService;

    public ThreadB(MyService myService) {
        this.myService = myService;
    }

    @Override
    public void run(){
        myService.testMethod();
    }
}
public class Run1 {
    public static void main(String[] args) throws InterruptedException {

        MyService service = new MyService();
        ThreadA a = new ThreadA(service);
        ThreadB b = new ThreadB(service);
        a.setName("A");
        b.setName("B");
        a.start();
        Thread.sleep(50);
        b.start();
    }
}

4010886680b1434682b5b21f6c84f1fa.png

        因为50毫秒后,B现成取得锁时456.

        继续实现,修改Run1.java,去掉代码中的Thread.sleep(50) 

public class Run2 {
    public static void main(String[] args) throws InterruptedException {

        MyService service = new MyService();
        ThreadA a = new ThreadA(service);
        ThreadB b = new ThreadB(service);
        a.setName("A");
        b.setName("B");
        a.start();
        b.start();
    }
}

 169b77164abe4ef3b508574512c19b29.png

       需要注意的是,String类型是不可变的,都是创建新的内存空间来解决存储新的字符。

        控制台输出的信息说明A线程和B线程检测到锁对象的值为123,且存储到内存空间X的位置,虽然将锁改成了456,并存储内存空间Y的位置,但结果还是同步的,因为A线程和B线程共同争抢的锁是X空间的123,不是Y空间的456。但是,还是会有很小的概率出现一起输出两个begin的情况,因为A线程将锁的值改成456后,B现成才启动区执行run方法,不存在A和B争抢同一把锁的情况,导致B线程获取的是更改后的锁的值(456),并连续输出两个begin。

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Apifox】token的使用方式和脚本示例

前言&#xff0c;关于token的使用&#xff0c;仅做了简单的demo测试token效果。 一、手动登录获取token 顾名思义&#xff0c;因为只有登录之后才有token的信息&#xff0c;所以在调用其他接口前需要拥有token才能访问。 操作步骤 1)添加环境变量、全局参数 这里拿测试环境举…

37 - 数据库参数设置优化,失之毫厘差之千里

MySQL 是一个灵活性比较强的数据库系统&#xff0c;提供了很多可配置参数&#xff0c;便于我们根据应用和服务器硬件来做定制化数据库服务。如果现在让你回想&#xff0c;你可能觉得在开发的过程中很少去调整 MySQL 的配置参数&#xff0c;但我今天想说的是我们很有必要去深入了…

SourceInsight - Relation Windows

磨刀不误砍柴工&#xff0c;你使用的工具决定了你的下限。我平时使用较多的代码编辑工具就是SourceInsight&#xff0c;这个工具速度快&#xff0c;操作方便&#xff0c;但处理非常大的项目的性能不是很理想&#xff0c;比如你要是添加整个Linux Kernel的源代码的话。 在使用SI…

Retrofit+OkHttp打印Request 请求地址参数

在移动端开发时&#xff0c;我们常常需要像web端一样可以方便地查看我们向服务器发送请求的报文详细日志&#xff08;如请求地址&#xff0c;请求参数&#xff0c;请求类型&#xff0c;服务器响应的耗时时间&#xff0c;请求返回的结果等等&#xff09;。 使用Retrofit时&…

C语言进阶指南(14)(部分字符串库函数及其模拟实现)

欢迎来到博主的专栏——C语言进阶指南 博主id&#xff1a;reverie_ly 文章目录 1、strlen&#xff08;&#xff09;——字符串长度计算函数自定义strlen函数的实现 2、strcpy——字符串拷贝函数strcpy的模拟实现 3.strcat——字符串追加函数strcat的模拟实现 4、strcmp——字符…

vue2+element-ui npm run build打包后,在服务器打开报错

报错 页面的图标也显示不出来&#xff0c;如下 解决&#xff1a; 在build->utils.js文件里面加上publicPath: ../../&#xff0c;再打包发布一下就可以了 // Extract CSS when that option is specified// (which is the case during production build)if (options.extrac…

LabVIEW当鼠标悬停在图形曲线上时显示坐标

LabVIEW当鼠标悬停在图形曲线上时显示坐标 在波形图上显示波形数据后&#xff0c;当鼠标放在波形图的曲线上时&#xff0c;如何自动显示对应点的坐标&#xff1f; 1. 创建事件结构&#xff0c;选择“波形图”作为“事件源”&#xff0c;选择“鼠标移动”作为“事件”&a…

MySQL之redo log

聊聊REDO LOG 为什么需要redolog&#xff1f; 那redolog主要是为了保证数据的持久化&#xff0c;我们知道innodb存储引擎中数据是以页为单位进行存储&#xff0c;每一个页中有很多行记录来存储数据&#xff0c;我们的数据最终是要持久化到硬盘中&#xff0c;那如果我们每进行…

为什么 SQL 日志文件很大,我应该如何处理?

SQL Server 日志文件是记录所有数据库事务和修改的事务日志文件。在 SQL 术语中&#xff0c;此日志文件记录对数据库执行的所有 INSERT、UPDATE 和 DELETE 查询操作。 如果数据库处于联机状态或处于恢复状态时日志已满&#xff0c;则 SQL Server 通常会发出 9002 错误。在这种…

windows系统用nginx部署web应用

要在Windows系统上使用Nginx进行本地部署和运行Web应用程序&#xff0c;可以按照以下步骤进行操作&#xff1a; 1.首先下载nginx&#xff0c;需要去nginx官网&#xff1a; nginx: download 下载最新版本的&#xff1a; 2.解压缩Nginx&#xff1a;找个磁盘位置&#xff0c;新…

C++ AVL 树

AVL树的概念 当数据有序或接近有序二叉搜索树将退化为单支树&#xff0c;此时二叉搜索树的搜索效率低下 解决方法&#xff1a;AVL树&#xff08;降低树的高度&#xff0c;从而减少平均搜索长度) 一棵AVL树或者是空树&#xff0c;或者是具有以下性质的二叉搜索树&#xff1…

1.自动化运维工具Ansible的安装

1.物料准备 四台服务器&#xff0c;其中一个是主控机&#xff0c;三个为host 2.安装 在主控机上安装ansible 2.1 设置EPEL仓库 Ansible仓库默认不在yum仓库中&#xff0c;因此我们需要使用下面的命令启用epel仓库。 yum install epel-release -y2.2 执行安装命令 yum i…

temu的产品发布后在哪里显示

temu是一款备受瞩目的产品&#xff0c;其发布后引起了广泛的关注。但是&#xff0c;很多人对于temu产品发布后在哪里显示存在疑惑。本文将深入探讨temu产品的展示方式和关键特点&#xff0c;帮助读者更好地了解temu产品在发布后的展示位置。 先给大家推荐一款拼多多/temu运营工…

贝锐向日葵与华为达成合作,启动鸿蒙原生应用开发

2023年11月24日&#xff0c;贝锐与华为携手举办鸿蒙原生应用开发启动仪式。贝锐创始人之一兼首席技术官张小峰先生、贝锐事业部总经理张海英女士共同出席了此次活动&#xff0c;并达成重大合作。贝锐旗下国民级远程控制品牌贝锐向日葵将以原生方式适配鸿蒙系统&#xff0c;成为…

串口理解小结(UART)

串口作为单片机的必备外设&#xff0c;主要用于单片机与其它模块的信息通讯、程序烧录和升级作用。 UART全称为通用异步收发器。 可分为&#xff1a; 一、串行和并行 串行指数据位只能一位一位地发送 并行之多个数据位同时发送 二、同步和异步 同步和异步是相当于时钟而…

AcWing 3555:二叉树(北京大学考研机试题)→公共父结点

【题目来源】https://www.acwing.com/problem/content/description/3435/【题目描述】 如下图所示&#xff0c;由正整数 1, 2, 3, … 组成了一棵无限大的&#xff08;满&#xff09;二叉树。 1/ \2 3/ \ / \4 5 6 7 /\ /\ /\ /\ ... ... 从任意一个结点到根结点&…

25. 深度学习进阶 - 权重初始化,梯度消失和梯度爆炸

文章目录 权重初始化梯度消失与梯度爆炸 Hi&#xff0c;你好。我是茶桁。 咱们这节课会讲到权重初始化、梯度消失和梯度爆炸。咱们先来看看权重初始化的内容。 权重初始化 机器学习在我们使用的过程中的初始值非常的重要。就比如最简单的wxb&#xff0c;现在要拟合成一个yha…

TZOJ 1373 求多项式的和

答案&#xff1a; #include <stdio.h> int main() {int m 0;scanf("%d", &m); // 读取测试实例的个数 while (m--) //循环m次{int n 0, i 0;scanf("%d", &n); // 读取求和项数n double sum 0.0;for (i 1; i < n; i) //分…

JenKins快速安装与使用

一、JenKins 0.准备&#xff0c;配置好环境 1&#xff09;Git&#xff08;yum安装&#xff09; 2&#xff09;JDK&#xff08;自行下载&#xff09; 3&#xff09;Jenkins&#xff08;自行下载&#xff09; 1.下载安装包 进官网&#xff0c;点Download下方即可下载。要下…

linux之下安装 nacos

1 下载地址 也可使用在线下载wget https://github.com/alibaba/nacos/releases/download/1.4.6/nacos-server-1.4.6.tar.gzTags alibaba/nacos GitHuban easy-to-use dynamic service discovery, configuration and service management platform for building cloud nativ…