Java线程的同步机制(synchronized关键字)

news2025/2/12 20:05:31

线程的同步机制(synchronized )

1.背景
例子:创建个窗口卖票,总票数为100张.使用实现Runnable接口的方式
*

  • 1.问题:卖票过程中,出现了重票、错票 -->出现了线程的安全问题
  • 2.问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。(多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误)
  • 3.如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,其他线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。

2.Java解决方案:同步机制
在Java中,我们通过同步机制,来解决线程的安全问题。

在这里插入图片描述

方式一:同步代码块


*
*   synchronized(同步监视器){
*      //需要被同步的代码
*
*   }
*  说明:1.操作共享数据的代码,即为需要被同步的代码。  -->不能包含代码多了,也不能包含代码少了。
*       2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
*       3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
*          //要求:多个线程必须要共用同一把锁。
*
* 补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
       在继承Thread类创建多线程的方式中,慎用this充当同步监视器(因为this可能会指向多个对象,看下面.下面的代码),考虑使用当前类(类名.class)充当同步监视器。

class Window1 implements Runnable{

    private int ticket = 100;
//    Object obj = new Object();
//    Dog dog = new Dog();
    @Override
    public void run() {
//        Object obj = new Object();
        while(true){
            synchronized (this){//此时的this:唯一的Window1的对象   //方式二:synchronized (dog) {

                if (ticket > 0) {

                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);


                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}


public class WindowTest1 {
    public static void main(String[] args) {
        Window1 w = new Window1();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }

}


class Dog{

}
 * 使用同步代码块解决继承Thread类的方式的线程安全问题
class Window2 extends Thread{


    private static int ticket = 100;

    private static Object obj = new Object();

    @Override
    public void run() {

        while(true){
            //正确的
//            synchronized (obj){
            synchronized (Window2.class){
                //Class clazz = Window2.class,
                //Window2.class只会加载一次
                //错误的方式:this代表着t1,t2,t3三个对象 (下面代码main中new了三个window2的对象)
//              synchronized (this){

                if(ticket > 0){

                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(getName() + ":卖票,票号为:" + ticket);
                    ticket--;
                }else{
                    break;
                }
            }

        }

    }
}


public class WindowTest2 {
    public static void main(String[] args) {
        Window2 t1 = new Window2();
        Window2 t2 = new Window2();
        Window2 t3 = new Window2();


        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();

    }
}

方式二:同步方法

  • 如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。
    
  • 关于同步方法的总结:
    
  • 1. 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
    
  • 2. 非静态的同步方法,同步监视器是:this
    
  • 静态的同步方法,同步监视器是:当前类本身
    
 * 使用同步方法解决实现Runnable接口的线程安全问题
class Window3 implements Runnable {

    private int ticket = 100;

    @Override
    public void run() {
        while (true) {

            show();
        }
    }

    private synchronized void show(){//同步监视器:this
        //synchronized (this){

            if (ticket > 0) {

                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);

                ticket--;
            }
        //}
    }
}


public class WindowTest3 {
    public static void main(String[] args) {
        Window3 w = new Window3();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }

}
/**
 * 使用同步方法处理继承Thread类的方式中的线程安全问题
 *
 * @author shkstart
 * @create 2019-02-15 上午 11:43
 */
class Window4 extends Thread {


    private static int ticket = 100;

    @Override
    public void run() {

        while (true) {

            show();
        }

    }
    private static synchronized void show(){
        //同步监视器:Window4.class(加static)
        //private synchronized void show(){ //同步监视器:t1,t2,t3。此种解决方式是错误的。
        if (ticket > 0) {

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
            ticket--;
        }
    }
}


public class WindowTest4 {
    public static void main(String[] args) {
        Window4 t1 = new Window4();
        Window4 t2 = new Window4();
        Window4 t3 = new Window4();


        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();

    }
}
  • 同步的方式,解决了线程的安全问题。—好处
  • 操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。 —局限性

释放锁的操作

●当前线程的同步方法、同步代码块执行结束。

●当前线程在同步代码块、同步方法中遇到break、return终 止了该代码块、
该方法的继续执行。

●当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导
致异常结束。

●当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线
程暂停,并释放锁。

join底层调用的是wait(),而wait是Object的方法,wait本身是会释放锁(彻底交出CPU的执行权),所以 Thread 的join() 方法是否会释放锁?答案是会!
但是,join()只会释放Thread的锁,不会释放线程对象的锁(可能会造成死锁)。

不会释放锁的操作

●线程执行同步代码块或同步方法时,程序调用Thread.sleep()、
Thread.yield()方法暂停当前线程的执行

●线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程
挂起,该线程不会释放锁(同步监视器)。

➢应尽量避免使用suspend()和resume()来控制线程

使用同步机制将单例模式中的懒汉式改写为线程安全的。



class Bank{

    private Bank(){}

    private static Bank instance = null;

    public static Bank getInstance(){
        //方式一:效率稍差
//        synchronized (Bank.class) {
//            if(instance == null){
//
//                instance = new Bank();
//            }
//            return instance;
//        }
        //方式二:效率更高
        if(instance == null){ //后面来的线程 如果已经创建好了对象 就可以不进入同步代码块 直接return给一个单例对象。

            synchronized (Bank.class) {
                if(instance == null){

                    instance = new Bank();
                }

            }
        }
        return instance;
    }

}

面试题:写一个线程安全的单例模式。
饿汉式。
懒汉式:上面提供的。

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

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

相关文章

罗马不是一天建成的,那为什么建了那么多罗马?

这一个罗马,那一个罗马,东一个罗马,西一个罗马,世界历史的大半部分都在跟罗马打交道。更要命的是四大文明古国还没有古代罗马。 存在感这么强,还不是四大文明古国,名字还难记,公元前居然就有共…

P19[6-7]编码器接口(硬)

编码器接口自动控制定时器时基单元中的CNT计数器进行自增或自减,(初始化后CNT=0;编码器右转,产生一个脉冲,CNT++,左转,产生一个脉冲,CNT--)。相当于外部时钟,同时控制CNT计数方向和计数时钟。每隔一段时间取CNT的值,再把CNT清零,就表示编码器的速度。 编码器测速即测频法…

总结linux查看当前用户的方法

操作环境:ubuntu 18.04系统 一、查看当前用户 1、shell终端中输入:who 当前用户为:root,使用pts的终端,后面是登陆的时间 2、shell终端中输入:whoami 当前用户为:root,很精简输出结果…

SpringBoot - spring-boot-maven-plugin插件介绍

简述 这个是SpringBoot的MAVEN插件,主要用来打包的,通常用来将项目打包成JAR或者WAR文件(生成FAT 包)。 这个插件生成的包是可执行的JAR。 Spring Boot Maven Plugin Documentation:https://docs.spring.io/spring-boo…

了解和使用 Kubernetes

文章目录 前言Kubernetes 集群安装Kubernetes 功能Kubernetes 核心概念Kubernetes 部署应用发布部署脚本发布服务使用 Ingress配置自动伸缩 Kubernetes 常用命令Kubernetes 故障排查 前言 通过 《容器集群管理工具 Docker Swarm》可以知道,在部署、调度、扩展和管理…

计算机时间到底是怎么来的?程序员必看的时间知识!

参考文章&#xff1a;https://xie.infoq.cn/article/22d762b26daee8b3f404f60a6 <title>计算机时间到底是怎么来的&#xff1f;程序员必看的时间知识&#xff01;_操作系统_Kaito_InfoQ写作社区</title><meta name"description" content"大家好&…

【阿里云OSS: Java端提供签名,vue+elementUI+axios 实现直传文件到OSS 实例】

文章目录 java代码&#xff08;包含后端上传文件、删除文件、提供签名&#xff09;前端代码postman 测试截图 java代码&#xff08;包含后端上传文件、删除文件、提供签名&#xff09; {private final static String OSS_BUCKET_NAME "test";private final static …

Day06 Python入门必知必会

文章目录 第一章 Python环境搭建1.1. 计算机基础1.1.1. 什么是编程1.1.2. 什么是进制1.1.2.1. 进制的简介1.1.2.2. 进制的分类1.1.2.3. 进制的表示1.1.2.4. 进制的转换1.1.2.5. 原反补(了解)数据的转换负数的表示补码的引入 1.2. Python的介绍1.3. Python的安装与使用1.3.1. Py…

PMP证书能直接升级项目管理专业人员能力评价(CSPM)三级吗?

2021年10月&#xff0c;中共中央、国务院发布的《国家标准化发展纲要》明确提出构建多层次从业人员培养培训体系&#xff0c;开展专业人才培养培训和国家质量基础设施综合教育。建立健全人才的职业能力评价和激励机制。由中国标准化协会&#xff08;CAS&#xff09;组织开展的项…

【6.14 代随_57day】 回文子串、最长回文子序列

回文子串、最长回文子序列 回文子串1.方法图解步骤代码 最长回文子序列1.方法图解步骤代码 回文子串 力扣连接&#xff1a;647. 回文子串&#xff08;中等&#xff09; 1.方法 确定dp数组以及下标的含义 dp数组是要定义成一位二维dp数组。 布尔类型的dp[i][j]&#xff1a;表示…

Three.js教程:光源对物体表面影响

推荐&#xff1a;将 NSDT场景编辑器 加入你的3D工具链 其他系列工具&#xff1a; NSDT简石数字孪生 光源对物体表面影响 实际生活中物体表面的明暗效果是会受到光照的影响&#xff0c;threejs中同样也要模拟光照Light对网格模型Mesh表面的影响。 你可以打开课件中案例源码&am…

5.2.12 IP分组的转发(三)

5.2.12 IP分组的转发&#xff08;三&#xff09; 示例&#xff1a;例&#xff1a;某网络拓扑如图所示&#xff0c;从图中我们可以看出该网络拓扑中有两个局域网&#xff0c;由两台路由器&#xff0c;其中路由器R1有两个以太网口&#xff0c;分别是E1和E2连接了两个局域网&…

这是我见过最通俗易懂的装饰者模式讲解!

关注“Java架构栈”微信公众号&#xff0c;回复暗号【Java面试题】即可获取大厂面试题 前言 本文主要讲述装饰者模式&#xff0c;文中使用通俗易懂的案例&#xff0c;使你更好的学习本章知识点并理解原理&#xff0c;做到有道无术。 什么是装饰者模式 装饰者模式是23种设计模式…

jmeter如何进行http压力测试

目录 前言&#xff1a; 1、添加线程组&#xff1a; 2、添加采样器&#xff1a; 3、添加监视器 压力测试知识说明 前言&#xff1a; JMeter是一个基于Java的开源压力测试工具&#xff0c;可用于评估Web应用程序的性能&#xff0c;包括HTTP、HTTPS、FTP、SOAP、Restful、JD…

试试前端自动化测试(基础篇)

众所周知的原因&#xff0c;前端作为一种特殊的 GUI 软件&#xff0c;做自动化测试困难重重。在快速迭代&#xff0c;UI 变动大的业务中&#xff0c;自动化测试想要落地更是男上加男 &#x1f436;。 近期的学习过程中&#xff0c;翻阅了众多前端自动化测试相关的文章&#xf…

JAVA中的拦截器、过滤器

JAVA变成拦截器、过滤器 一、拦截器1、简介说明2、源码及方法说明3、拦截器自定义应用 二、过滤器1、简介说明2、源码及方法说明3、过滤器的自定义应用 三、Springboot中的WebMvcConfigurer1、简介2、主要方法3、添加拦截器 四、区别1、原理2、触发3、其他 一、拦截器 1、简介…

Scala学习笔记

累了&#xff0c;基础配置不想写了&#xff0c;直接抄了→Scala的环境搭建 这里需要注意的是&#xff0c;创建新项目时&#xff0c;不要用默认的Class类&#xff0c;用Object&#xff0c;原因看→scala中的object为什么可以直接运行 一、Scala简介 1.1 图解Scala和Java的关系 1…

HQChart实战教程63-自定义手机端K线tooltip显示数据

HQChart实战教程63-自定义手机端K线tooltip显示数据 手机端K线tooltip步骤1. 配置手机端tooltip2. 替换k线tooltip格式化输出函数2. 格式化输出函数说明HQChart插件源码地址完整的demo源码手机端K线tooltip hqchart手机端内置一个tooltip,显示手势所在K线的信息。默认显示 日期…

了解D-Galactopyranose pentaacetate,CAS号25878-60-8的性质和应用

​ 中文名称&#xff1a;1,2,3,4,6-D-葡萄糖五乙酸酯 英文名称&#xff1a;D-Galactopyranose pentaacetate 规格标准&#xff1a;1g、5g、10g CAS&#xff1a;25878-60-8 分子式&#xff1a;C16H22O11 分子量&#xff1a;390.34 熔点&#xff1a;113C 沸点&#xff1a;451C 密…

迷宫生成算法

迷宫生成 ① 十字分割 递归版本 ② BFS&#xff08;即广度算法&#xff09; 十字分割方法生成 要求初始时迷宫内全是通路&#xff0c;然后随机十字建墙&#xff0c;然后随机在三面墙上打洞&#xff0c;使四个子空间连通。 要求&#xff1a;十字点横纵坐标均要求为偶数&…