[001-02-001]. 第07-03节:理解线程的安全问题

news2024/11/13 1:39:35

我的后端学习大纲

我的Java学习大纲


当多个线程共享一份数据的时候,不同的线程对数据进行操作,就可能会导致线程安全问题,比如卖票过程中出现了错票和重复票的问题:

1、卖票问题分析:

1.1.理想状态:

在这里插入图片描述

1.2.极端状态:

  • 问题原因:当多条语句在操作同一个线程共享的数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误
private int tick = 100;
public void run(){
	while(true){
		if(tick>0){
			try{
				//这里可能就多个线程进入,进行了各种操作。。。。导致线程安全
				Thread.sleep(10);
			}catch(InterruptedException e){ 
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+“售出车票,tick号为: "+tick--);
		} 
	}
 }

1.3.改善后状态:

  • 对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行
    在这里插入图片描述

2、线程安全问题的解决

2.1.解决线程安全办法:

  • 1.对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行
  • 2.在java中,我们通过同步机制,来解决线程安全问题

2.2.实现方式:

a.方式一:同步代码块:

  • 1.操作共享数据的代码,就是需要被同步的代码;所谓的共享数据就是多个线程共同操作的变量。比如卖票中的ticket数量就是变量
  • 2.同步监视器就是的意思;任何一个类对象都可以充当锁,一定要保证多个线程要共同一把锁,即用的是同一个对象;
    • 实现Runnable接口创建的多线程的方式中,可以考虑使用this充当同步监视器;
    • 继承Thread类创建多继承的方式中,要慎用this,看看this代表的是不是一样的意义。可以考虑当前类
synchronized(同步监视器){
	//需要被同步的代码(操作共享数据的那部分代码就是需要被同步的代码)
}
  • 3.继承方式中问题的改进:
    在这里插入图片描述
  • 5.接口实现方式中程序的改进 :
    在这里插入图片描述
  • 6.同步监视器的要求:即为锁的要求:多个线程必须要用同一把锁
  • 7.同步方式解决了线程的安全问题,但是降低了效率,在操作同步代码的时候,相当于一个单线程

b.方式二:同步方法

  • 1.如果操作共享数据的代码完整的声明在了一个完整的方法中,我们不妨就考虑此方法声明式同步的
  • 2.解决实现方式的线程问题:
package com.atguigu.java;
/**
 * @author: jianqun
 * @email: 1033586391@qq.com
 * @creat: 2022-04-09-13:50
 * @Description:
 */
public class ThreadTest007  {
    public static void main(String[] args) {
        ImporeTicket2 impporeTicket = new ImporeTicket2();
        Thread thread1 = new Thread(impporeTicket);
        Thread thread2 = new Thread(impporeTicket);
        Thread thread3 = new Thread(impporeTicket);

        thread1.setName("窗口1");
        thread2.setName("窗口2");
        thread3.setName("窗口3");

        thread1.start();
        thread2.start();
        thread3.start();
    }
}



class ImporeTicket2 implements Runnable{
    private int ticket = 10;//每个对象都有100张票,属性不共享
    @Override
    public void run() {
        while(true){
            show();
        }
    }

    //卖票的完整方法
    //在同步方法中,锁就是this
    private synchronized void show(){
        if (ticket >0){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":卖票,票号是::" + ticket);
            ticket--;
        }
    }
}
  • 3.解决继承方式的线程问题:这里的锁是当前类,即Bwindow2.class
package com.atguigu.java;

/**
 * @author: jianqun
 * @email: 1033586391@qq.com
 * @creat: 2022-04-09-9:09
 * @Description: 卖票问题
 */

class Bwindow2 extends Thread{

    private static int ticket = 10;
    private static Object obj = new Object();
    @Override
    public void run() {
        while(true){
            show2();
        }
    }

	//private  synchronized void show2(){}这个更改是错误的,代表的是不同对象的不同方法,要定义成静态的方法才可以
    private static synchronized void show2(){//这里的锁是当前类
        if (ticket >0){
            System.out.println(Thread.currentThread().getName() + ":卖票,票号是::" + ticket);
            ticket--;
        }
    }
}

public class ThreadTest008 {
    public static void main(String[] args) {
        Bwindow bwindow1 = new Bwindow();
        Bwindow bwindow2 = new Bwindow();
        Bwindow bwindow3 = new Bwindow();

        bwindow1.setName("窗口1");
        bwindow2.setName("窗口2");
        bwindow3.setName("窗口3");

        bwindow1.start();
        bwindow2.start();
        bwindow3.start();

    }
}
  • 3.同步方法的总结:
    • 同步方法仍然是涉及到了同步监视器,但是不需要我们自己手动显示声明
    • 非静态同步方法:锁是:this
    • 静态的同步方法:锁是当前类的本身

c.方式三:线程同步方式-Lock锁方式 来解决线程安全问题

1.Lock(锁)

  • 从JDK 5开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步同步锁使用Lock对象充当
  • java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
  • ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁

2.语法:

  • 注意:如果同步代码有异常,要将unlock()写入finally语句块
 //Lock(锁)
class A{
	private final ReentrantLock lock = new ReenTrantLock();
	public void m(){
		lock.lock();
		try{
			//保证线程安全的代码; 
		}finally{
			lock.unlock(); 
		} 
	} 
}

3.卖票问题代码演示

package com.atguigu.java;

/**
 * @author: jianqun
 * @email: 1033586391@qq.com
 * @creat: 2022-04-09-15:57
 * @Description:
 */
import java.util.concurrent.locks.ReentrantLock;

class Window implements Runnable{
    private int ticket = 10;
    //1.实例化ReentrantLock
    private ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        while(true){
            try{
                //2.调用锁定方法lock()
                lock.lock();
                if(ticket > 0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + ticket);
                    ticket--;
                }else{
                    break;
                }
            }finally {
                //3.调用解锁方法:unlock()
                lock.unlock();
            }
        }
    }
}

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

        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();
    }
}

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

在这里插入图片描述

package com.atguigu.java;

public class ThreadTest009 {

}
class Bank{
    private Bank(){}
    private static Bank instance = null;
    public static Bank getInstance(){
        //方式一:效率稍差
       // 当有多个线程的时候,因为一上来就是锁,多个线程都在排队,然后当拿到锁之后,进入判断instance是否是null,
       // 当不是null的时候,就直接返回了。这样一堆线程都在排队,但是拿到锁的时候,就又啥都不干,这样就浪费了时间,降低了效率
       
		//synchronized (Bank.class) {
        //  if(instance == null){
        //      instance = new Bank();
        //  }
        //  return instance;
        // }

        //方式二:效率更高
        if(instance == null){
            synchronized (Bank.class) {
                if(instance == null){
                    instance = new Bank();
                }
            }
        }
        return instance;
    }
}

3、面试题目:

3.1.synchronized 与 Lock的异同

a.相同:

  • 二者都可以解决线程安全问题

b.不同:

  • synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器
  • Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())

3.2.synchronized 与 Lock 的对比:

  • Lock是显式锁(手动开启和关闭锁,别忘记关闭锁)synchronized是隐式锁,出了作用域自动释放
  • Lock只有代码块锁,synchronized有代码块锁和方法锁
  • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

3.3.线程安全问题解决的优先顺序:

  • Lock --> 同步代码块(已经进入了方法体,分配了相应资源)--> 同步方法(在方法体之外)

4、线程的安全练习:

银行有一个账户。有两个储户分别向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额
分析:是一个多线程问题(两个储户),通过判断是否出现共享数据来判断是否出现线程安全问题。当有共享数据(账户余额是共享数据)的时候,就使用三种方式来实现线程同步机制

package com.atguigu.java;
public class ThreadTest12   {
    public static void main(String[] args) {
        //对Account中double类型的balance属性进行初始化
        Account account = new Account(0);

        //对Customer中的Account类型的属性acc进行实例化,通过实例化出来两个用户
        Customer customer1 = new Customer(account);
        Customer customer2 = new Customer(account);
        customer1.setName("张三");
        customer2.setName("李四");

        customer1.start();
        customer2.start();
    }
}



//账户类
class Account{
    private double balance;
    public Account(double balance) {
        this.balance = balance;
    }

    //存钱:
    // 在未控制多线程同步的时候分析:
    //     当乙用户存钱的时候,执行了 balance+=amt,然后未及时打印输出余额,
    //     然后呢甲用户又存了钱,然后甲又进入了睡眠状态
    //     过了一会乙和甲都睡醒了,但是都是存了1000元,可以打印的是存了2000元,这样就出现了线程安全的问题。

    // 以下进行了线程安全控制:
    public synchronized void  deposits(double amt){
        if (amt > 0){
            balance+=amt;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "存储成功,余额:"+ balance);
        }
    }
}





//储户类存钱,可以看成是两个线程
class Customer extends Thread{
    private Account acct;//定义一个Account类型的属性

    //提供一个有参构造器,用于对属性的实例化
    public Customer(Account acct){
        this.acct = acct;
    }

    //run方法中重写存钱的逻辑
    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            //存钱的方法
             acct.deposits(1000);
        }
    }
}

5、线程同步中的死锁问题:

5.1.死锁定义:

  • 1.不同的线程分别占用对方需要的同步资源不放弃都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
  • 2.出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续

5.2.举例分析死锁

在这里插入图片描述

package com.atguigu.java;

/**
 * @author: jianqun
 * @email: 1033586391@qq.com
 * @creat: 2022-04-09-14:39
 * @Description:
 */
public class ThreadTest010 {
    public static void main(String[] args) {
        StringBuffer stringBuffer1 = new StringBuffer();
        StringBuffer stringBuffer2 = new StringBuffer();


        //继承方式创建匿名方式实现多线程的同步
        new Thread(){
            @Override
            public void run() {
                synchronized (stringBuffer1){
                    stringBuffer1.append("a");
                    stringBuffer2.append("1");

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

                    synchronized (stringBuffer2){
                        stringBuffer1.append("b");
                        stringBuffer2.append("2");

                        System.out.println(stringBuffer1);
                        System.out.println(stringBuffer2);
                    }
                }
            }
        }.start();

        //接口实现方式创建匿名内部类实现线程同步
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (stringBuffer2){

                    stringBuffer1.append("c");
                    stringBuffer2.append("3");

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

                    synchronized (stringBuffer1){
                        stringBuffer1.append("d");
                        stringBuffer2.append("4");

                        System.out.println(stringBuffer1);
                        System.out.println(stringBuffer2);
                    }
                }
            }
        }){}.start();
    }
}

5.3.死锁解决方法

  • 1.专门的算法、原则
  • 2.尽量减少同步资源的定义
  • 3.尽量避免嵌套同步

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

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

相关文章

软考架构-面向服务的架构风格

一、SOA 1、概念 面向服务开发&#xff0c;服务之间通过简单、精确定义接口进行通信&#xff0c;不涉及底层编程接口和通信模型。多个服务挂载在ESB&#xff08;企业服务总线&#xff09;上进行通信。 2、特征 可从企业外部访问、随时可用&#xff08;服务请求能被及时响应…

这款神器,运维绝杀 !!! 【送源码】

项目简介 CrowdSec 是一款开源的、基于社区协作的网络安全防护工具&#xff0c;它通过分析和共享IP信誉数据来对抗恶意行为。该软件不仅支持IPv6&#xff0c;而且相较于传统的Python实现&#xff0c;其采用Go语言编写&#xff0c;运行速度提升了60倍。CrowdSec 利用Grok模式解析…

Datasheet SHT20芯片的数据手册

Datasheet SHT20芯片的数据手册 I2C读取湿度传感器返回的16位数据。SCL SDA 14位有效&#xff0c;我以为是将后二位删除&#xff0c;实际上看完手册才知道是后二位值无用&#xff0c;不是删除&#xff0c;而是清0&#xff0c;实际上还是16为&#xff0c;知识后二位是0还是1&…

Java重修笔记 第五十四天 坦克大战(四)多线程基础

线程 当点击运行按钮运行程序时&#xff0c;就相当于启动了一个进程&#xff0c;虚拟机进入 mian 方法后会开启一个名为 mian 的主线程&#xff0c;main 方法体中创建一个线程对象&#xff0c;调用该线程对象的 start 方法又创建一个子线程&#xff0c;子线程的启动并不会阻塞…

2024/9/11学校教的响应式前端能学到什么?

9.11 1&#xff09;砌砖 确定整体框架&#xff0c;而不是想到一点写一点&#xff0c;类似盖大楼&#xff0c;不是想到哪盖到哪&#xff0c;先砌砖&#xff0c;再装修 砌砖前先划分好砌砖范围(初始化样式) 清除body自带的内外边距 * { margin: 0; padding: 0; }去掉li的小圆点…

H5接入Steam 获取用户数据案例 使用 OpenID 登录绑定公司APP账户 steam公开用户信息获取 steam webapi文档使用

官方文档地址 1.注册 Steam API Key&#xff1a; 你需要一个 Steam Web API Key&#xff0c;可以在 Steam API Key 页面 获取。https://steamcommunity.com/dev/apikey 这里开发做demo用自己steam账户的就好&#xff0c;后续上线要用公司的账户 2.使用 OpenID 登录&#xff…

【c++实现】统计上升四元组

&#x1f308;个人主页&#xff1a;Yui_ &#x1f308;Linux专栏&#xff1a;Linux &#x1f308;C语言笔记专栏&#xff1a;C语言笔记 &#x1f308;数据结构专栏&#xff1a;数据结构 &#x1f308;C专栏&#xff1a;C 文章目录 1. 题目描述2. 解释3. DP前缀和枚举 1. 题目描…

3.Kubernetes资源对象之pod

&#x1f482; 个人主页: Java程序鱼 &#x1f4ac; 如果文章对你有帮助&#xff0c;欢迎关注、点赞、收藏(一键三连)和订阅专栏 &#x1f464; 微信号&#xff1a;hzy1014211086&#xff0c;想加入技术交流群的小伙伴可以加我好友&#xff0c;群里会分享学习资料、学习方法…

经典蓝桥题目-------欧拉函数的应用

输入样例&#xff1a; 3 4 9 5 10 42 9999999967 输出样例&#xff1a; 6 1 9999999966 分析&#xff1a; 设 gcd(a,m) d,则 d|a,d|m 而 gcd(a,m) gcd(ax,m) 则有 d|x 根据题目有 0<x<m 同样的有 0< x < m (x,m是同时除以d的值) 于是我们发现只要求…

IPD变革之道内涵是什么?何以与人工智能新技术融合

集成产品开发&#xff08;Integrated ProductDevelopment&#xff0c;IPD&#xff09;引入我国20余年&#xff0c;从探索、扎根到管理驱动&#xff0c;逐渐成为国内企业长存发展之共识。回溯IPD的由来和发展&#xff0c;IPD变革之道内涵是什么&#xff1f;何以与人工智能新技术…

DNS查询报文分析

目录 1. 用 tcpdump工具监听抓包 2. 用 host 工具获取域名对应的IP地址 3. 分析DNS以太网查询数据帧 3.1 linux下查询DNS服务器IP地址 3.2 DNS以太网查询数据帧 &#xff08;1&#xff09;数据链路层 &#xff08;2&#xff09;网络层 &#xff08;3&#xff09;传输层…

如何禁止电脑上某个软件运行?3种管理小白必懂的方法,你学废了吗?

在日常使用电脑的过程中&#xff0c;我们有时会遇到需要阻止某些软件运行的情况。 无论是出于节省资源、提升系统性能&#xff0c;还是为了保护隐私安全&#xff0c;掌握基本的软件管理技能都是非常必要的。 下面&#xff0c;我们就来探讨三种简单易行的方法&#xff0c;即使…

【BFS专题】— BFS解决FloodFill算法

1、 图像渲染 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 代码&#xff1a; class Solution {int[] dx {0,0,1,-1};int[] dy {1,-1,0,0};public int[][] floodFill(int[][] image, int sr, int sc, int color) {//统计刚开始[sr,sc]坐标位置的颜色int prev …

C++当中的多态(三)

&#xff08;六&#xff09;虚表的本质 其实我们大家应该都已经猜到了&#xff1a;我们虚表的本质就是一个函数指针数组。通过访问这个函数指针数组就可以得到我们想要的虚函数的地址&#xff0c;之后通过这个地址就可以调用我们相应的虚函数。我们这个函数指针数组是以nullptr…

开发者的噩梦:如何在抄袭狂潮中杀出一条血路?

开发者的噩梦&#xff1a;如何在抄袭狂潮中杀出一条血路&#xff1f; 作为一个独立开发者&#xff0c;辛辛苦苦打磨出的产品&#xff0c;一旦被别人抄袭&#xff0c;心中往往会涌现出无数的愤怒与无奈。看到自己的创意被别人肆意剽窃&#xff0c;并以此获利&#xff0c;谁能不…

【HTML】Html标签

目录 结构盒子div 标签语义化标签 文本p 段落标签h 标题标签span 行内标签a 超链接标签br 换行标签、hr水平线标签sub 下标字、sup 上标字strong 或 b 加粗、em 或 i 斜体、del 或 s 删除线、ins 或 u 下划线 列表ul 无序列表ol 有序列表dl 自定义列表列表嵌套 表格table 标签合…

【Gateway】网关服务快速上手

微服务的接口都是直接对外暴露的,可以直接通过外部访问,为了保证对外服务的安全性服务端实现的微服务接口通常都带有一定的权限校验机制,由于使用了微服务,原本一个应用的的多个模块拆分成了多个应用,我们不得不实现多次校验逻辑,当这套逻辑需要修改时,我们需要修改多个应用,加…

【4.2】图搜索算法-DFS和BFS解单词拆分

一、题目 给定一个非空字符串 s 和一个包含非空单词的列表 wordDict&#xff0c;判定 s 是否可以被空格拆分 为一个或多个在字典中出现的单词。 说明&#xff1a; 拆分时可以重复使用字典中的单词。 你可以假设字典中没有重复的单词。 示例 1&#xff1a; 输入: s "le…

【2024-09-12】某极验4流程分析-滑块验证码

声明:该专栏涉及的所有案例均为学习使用,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!如有侵权,请私信联系本人删帖! 文章目录 一、前言二、流程分析三、参数分析三、代码一、前言 极验四代滑块没有了滑动轨迹的验证,来看一下 网址:aHR0cHM6Ly9n…

机器学习中的内存优化

随着机器学习模型的复杂性不断增加&#xff0c;内存使用量也随之增长&#xff0c;因此&#xff0c;内存优化变得尤为重要。 机器学习内存足迹 机器学习模型通常由数据结构如张量和矩阵组成。例如&#xff0c;一个形状为(1000, 1000)&#xff0c;每个元素为32位浮点数的二维张量…