可见性原子性有序性的+线程传参的方式+Java如何实现多个线程之间共享数据+线程间通信+死锁产生

news2024/11/16 23:07:22
//为了均衡CPU和内存的速度差异,增加了缓存 导致了可见性的问题;
//操作系统增加了进程 线程 分时复用CPU,均衡CPU和io设备的速速差异 导致了原子性问题;
//jvm指令重排序(优化指令排序) 导致了有序性的问题

可见性问题是指 线程A修改共享变量,修改后CPU缓存中的数据没有及时同步到内存,线程B读取了内存中的老数据

原子性问题是指 多线个线程增加数据 有几个线程挂了, 数据就会减少;
有序性问题是指 对象创建需要三步,堆中分配内存-初始化-变量指向内存地址;如果重排序会出现1 3 2;导致没有初始化的数据创建;

//如果每个线程执行的代码相同:可以使用同一个Runnable对象,这个Runnable对象中有那个共享数据,例如:卖票系统
//如果每个线程执行的代码不同: 将共享数据封装在另外一个对象中,然后将这个对象逐一传递给各个Runnable对象。每个线程对共享数据的操作方法也分配到那个对象身上去完成,这样容易实现针对该数据进行的各个操作的互斥和通信。

可见性问题实例:

        线程共享:只建立了一个Ticket对象,内存中只有一个tick成员变量,所以是共享数据

        简单的方式,即在任意一个类中定义一个static的变量,这将被所有线程共享

        可见性问题 导致数据不一致需要加锁 定义一个共享的锁对象

总结:要同步互斥的几段代码最好是分别放在几个独立的方法中,这些方法再放在同一个类中,这样比较容易实现它们之间的同步互斥或通信。

class TicketInfo  implements Runnable{

    private int ticketNumber=20;
    @Override
    public void run() {
        while(true){
            if(ticketNumber>0){
                try {
                    //使用sleep不然执行每个线程都会占用完毕
                    Thread.sleep(100);
                    System.out.println(Thread.currentThread().getName()+"....sale : "+ ticketNumber--);

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

 public static void main(String[] args) {
        int num=10;
        TicketInfo ticket = new TicketInfo();
        //如果每个线程执行的代码相同:可以使用同一个Runnable对象,这个Runnable对象中有那个共享数据,例如:卖票系统
        //只建立了一个Ticket对象,内存中只有一个tick成员变量,所以是共享数据
        Thread t1 = new Thread(ticket);
        Thread t2 = new Thread(ticket);
        Thread t3 = new Thread(ticket);
        Thread t4 = new Thread(ticket);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        //可见性问题是指 线程A修改共享变量,修改后CPU缓存中的数据没有及时同步到内存,线程B读取了内存中的老数据
    }

线程传参的方式

//线程传参的方式2类4种
//第一类:主动给线程传参
//通过构造函数给Thread进行传递
//通过变量和方法给Runnable传递数据
//第二类 线程主动获取参数
//Thread线程主动获取参数通过回调函数获取
//线程Exchanger工具类实现线程间的数据交换

主动给线程传参;

class TicketInfo  implements Runnable{
    private  int ticketNumber=0;
    //通过构造函数进行传参
    TicketInfo(int ticketNumber){
        this.ticketNumber=ticketNumber;
    }
    /**
     * 第二种通过变量传参
     */
    private String parameter ;
    public void setParameter(String parameter) {
        this.parameter  = parameter ;
    }
    @Override
    public void run() {
        try{
            while(true){
                if(ticketNumber>0){
                    try {
                        //使用sleep不然执行每个线程都会占用完毕
                        Thread.sleep(100);
                        System.out.println(Thread.currentThread().getName()+".."+parameter+"..sale : "+ ticketNumber--);

                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            Thread.currentThread().interrupt();
        }

    }
}
public static void main(String[] args) {
        TicketInfo ticket = new TicketInfo(20);
        //如果每个线程执行的代码相同:可以使用同一个Runnable对象,这个Runnable对象中有那个共享数据,例如:卖票系统
        //只建立了一个Ticket对象,内存中只有一个tick成员变量,所以是共享数据
        ticket.setParameter("ticket");
        Thread t1 = new Thread(ticket);
        Thread t2 = new Thread(ticket);
        Thread t3 = new Thread(ticket);
        Thread t4 = new Thread(ticket);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        //可见性问题是指 线程A修改共享变量,修改后CPU缓存中的数据没有及时同步到内存,线程B读取了内存中的老数据
    }

 

Java如何实现多个线程之间共享数据

实现多个线程之间共享数据

一、 如果每个线程执行的代码相同

可以使用同一个Runnable对象,这个Runnable对象中有能共享数据,例如 买票系统
class TicketInfo  implements Runnable{
    private  int ticketNumber=0;
    //通过构造函数进行传参
    TicketInfo(int ticketNumber){
        this.ticketNumber=ticketNumber;
    }
    /**
     * 第二种通过变量传参
     */
    private String parameter ;
    public void setParameter(String parameter) {
        this.parameter  = parameter ;
    }
    Object lock = new Object(); // 定义一个共享的锁对象
    @Override
    public void run() {
        try{
            while(true){
                synchronized (lock){
                    if(ticketNumber>0){
                        try {
                            //使用sleep不然执行每个线程都会占用完毕
                            Thread.sleep(100);
                            System.out.println(Thread.currentThread().getName()+".."+parameter+"..sale : "+ ticketNumber--);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            Thread.currentThread().interrupt();
        }

    }
}

2.如果每个线程执行的代码不同

将共享数据封装在另外一个对象中,然后将这个对象逐一传递给各个Runnable对象。每个线程对共享数据的操作方法也分配到那个对象身上去完成,这样容易实现针对该数据进行的各个操作的互斥和通信。

思想: 一个类提供数据和操作数据的同步方法,另外定义两个线程通过构造函数接收并操作数据,在主函数中直接创建线程对象,即可完成操作(可以实现两个内部类,不用构造方法传值,使用final定义data局部变量

例如: 设计4个线程,其中两个线程每次对j增加1,另外两个线程每次对j减少1

//取款线程 需要传入一个共享数据
class MyRunnable2 implements Runnable{
    private ShareDate data;
    private int num;
    MyRunnable2(ShareDate data,int num){
        this.data = data;
        this.num = num;
    }
    @Override
    public void run() {
        if(num>0){
            for(int i=0;i<num;i++){
                data.decrement();
            }
        }
    }
}
//存款的线程 需要传入一个数据数据
class MyRunnabe1 implements  Runnable{
    private ShareDate data;
    private int num;
    MyRunnabe1(ShareDate data,int num){
        this.num=num;
        this.data=data;
    }
    @Override
    public void run() {
        if(num>0){
            for(int i=0;i<num;i++){
                data.increment();
            }
        }

    }
}
//封装共享数据和操作共享数据方法的类
class ShareDate{
    private int j=10;
    public synchronized void increment(){
        j++;
        System.out.println(Thread.currentThread().getName()+" add :"+j);
    }
    public synchronized void decrement(){
        j--;
        System.out.println(Thread.currentThread().getName()+" del :"+j);
    }

}

 public static void main(String[] args) {
        //将共享数据封装在另外一个对象中,然后将这个对象逐一传递给各个Runnable对象。
        // 每个线程对共享数据的操作方法也分配到那个对象身上去完成,这样容易实现针对该数据进行的各个操作的互斥和通信。

        //思想: 一个类提供数据和操作数据的同步方法,另外定义两个线程通过构造函数接收并操作数据,
        // 在主函数中直接创建线程对象,即可完成操作
        // (可以实现两个内部类,不用构造方法传值,使用final定义data局部变量)

        //例如: 设计4个线程,其中两个线程每次对j增加1,另外两个线程每次对j减少1
        //将数据封装到一个对象,内存中只有一个data成员变量,是共享数据
        ShareDate data=new ShareDate();
        for (int i = 0; i <2;i++ ) {
            System.out.println("内存中只有一个data成员变量");
            new Thread(new MyRunnabe1(data,1)).start();//12
            new Thread(new MyRunnable2(data,2)).start();//12-4=8
        }
        //要同步互斥的几段代码最好是分别放在几个独立的方法中,这些方法再放在同一个类中,这样比较容易实现它们之间的同步互斥或通信。

    }

多线程之间共享数据的方式探讨

总结:要同步互斥的几段代码最好放在几个独立的方法中,这些方法再放入一个类中,这样比较容易实现它们之间的同步互斥和通信。

方式一:代码一致

如果每个线程执行的代码相同,可以用一个Runnable对象,这个Runnable对象中存放能共享数据(卖票系统)

方式二:代码不一致

如果每个线程执行的代码不同时,就需要不同的 Runnable 对象:
a.将共享数据封装在另外一个对象中,然后将这个对象逐一传递给各个Runnable对象。每个线程对共享数据操作的方法也分配到这个对象中,这样容易实现针对改数据进行的各个操作的互斥通信

实例代码为: 设计4个线程,其中两个线程每次对j增加1,另外两个线程每次对j减少1

b.将 Runnable 对象作为某一个类的内部类,共享数据作为这个外部类的成员变量,每个线程对共享数据的操作方法也分配到外部类中,实现共享数据的互斥和通信操作,作为内部类的各个 Runnable 对象调用外部类的这些方法。

public class MultiThreadShareData { 
    private int shareData=0; 
    public static void main(String[] args) {
        MultiThreadShareData m=new MultiThreadShareData();
        //初始化Runnable对象
        MyRunnable1 myRunnable1 = m.new MyRunnable1();
        MyRunnable2 myRunnable2=m.new MyRunnable2(); 
        //开启线程
        new Thread(myRunnable1).start(); 
        new Thread(myRunnable2).start();
    }
  
    private synchronized void increment(){
        this.shareData++;
        System.out.println(Thread.currentThread().getName()+":shareData增加了1后shareData="+shareData);
    }
  
    private synchronized void decrement() {
        this.shareData--;
        System.out.println(Thread.currentThread().getName()+":shareData减少了1后shareData="+shareData);
    }
  
    /**
     * 作为内部类的Runnable对象
     */
    class MyRunnable1 implements Runnable{ 
        @Override
        public void run() {
            for(int i=0;i<100;i++){
                increment();
            }
        }
    }
  
    class MyRunnable2 implements Runnable{ 
        @Override
        public void run() {
            for(int i=0;i<100;i++){
                decrement();
            }
        }
    }
}

c.. 以上两种方法的组合:将共享数据封装到一个对象中,每个线程对共享数据的操作方法也分配到对象中,对象作为外部类的成员变量或方法中的局部变量,每个线程的 Runnable 作为成员内部类或局部内部类。

public class MultiThreadShareData {
   public static void main(String[] args) { 
       ShareData data = new ShareData(); 
       new Thread(()->{
           for(int i=0;i<100;i++){
               data.increment();
           }
       }).start();
  
       new Thread(()->{
           for (int j=0;j<100;j++) {
               data.decrement();
           }
       }).start();
   } 
}
  
/**
封装共享数据的对象
*/
class ShareData{
   //共享数据
   private int j=0;
  
   /**
    对共享数据进行操作的方法
   */
   public synchronized void increment(){
       this.j++;
       System.out.println(Thread.currentThread().getName()+":j增加了1后j="+j);
   }
  
   public synchronized void decrement() {
       this.j--;
       System.out.println(Thread.currentThread().getName()+":j减少了1后j="+j);
   }
  
   public int getJ() {
       return j;
   }
}

总之,要同步互斥的几段代码最好放在几个独立的方法中,这些方法再放入一个类中,这样比较容易实现它们之间的同步互斥和通信。

线程通信(同步)

wait/notify 实例

public class ThreadExecutionOrder {
    private static int sharedCount = 0;
    public static void main(String[] args) {
        Object lock = new Object(); // 定义一个共享的锁对象
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized(lock) { // 通过共享的锁对象来实现同步
                    while(sharedCount != 0) { // 如果前一个线程还未执行完,则等待
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("Thread 1");
                    sharedCount = 1; // 修改共享变量的值,表示当前线程已经完成任务
                    lock.notifyAll(); // 唤醒其他等待线程
                }
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized(lock) {
                    while(sharedCount != 1) {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("Thread 2");
                    sharedCount = 2;
                    lock.notifyAll();
                }
            }
        });

        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized(lock) {
                    while(sharedCount != 2) {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("Thread 3");
                    sharedCount = 3;
                    lock.notifyAll();
                }
            }
        });

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

死锁产生问题

public class LockLock {
    //创建资源
    private static Object resourceA =new Object();//定义一个共享锁对象
    private static Object resourceB =new Object();//定义一个共享锁对象

    public static void main(String[] args) throws InterruptedException {
        Thread threadA=new Thread(()->{
            //线程A使用synchronized(resourceA)方法获取到了resourceA的监视器锁;
            synchronized (resourceA){
                System.out.println(Thread.currentThread().getName() +" get ResourceA");
                try {
                    Thread.sleep(1000);//调用sleep函数休眠1s 休眠1s是为了保证线程A在获取resourceB
                    //对应的锁前让线程B抢占到CPU 获取到资源resourceB上的锁;
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() +" waiting get resourceB");
                synchronized (resourceB){
                    System.out.println(Thread.currentThread().getName()+" get resourceB");
                }
            }
        });
        //产生死锁
        /*Thread threadB =new Thread(()->{
            //获取resourceB 共享资源的监视器锁
            synchronized (resourceB){
                System.out.println(Thread.currentThread().getName()+" get ResourceB");
                try {
                    Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+" waiting get resourceA");
                //获取resourceA共享资源的监视器锁
                synchronized (resourceA){
                    System.out.println(Thread.currentThread().getName()+"get resourceA");
                }
            }
        });*/
        //改变执行顺序 避免死锁
        Thread threadB =new Thread(()->{
            //获取resourceB 共享资源的监视器锁
            synchronized (resourceA){
                System.out.println(Thread.currentThread().getName()+" get ResourceB");
                try {
                    Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+" waiting get resourceA");
                //获取resourceA共享资源的监视器锁
                synchronized (resourceB){
                    System.out.println(Thread.currentThread().getName()+"get resourceA");
                }
            }
        });


        /*
        首先 resourceA和 resourceB 都是互斥资源,当线程A调用 synchronized(resource A)
        方法获取到 resourceA 监视器锁并释放前, 线程B再调 synchronized(resourceA
        法尝试获取该资源会被阻塞,只有线程A主动释放该锁,线程B能获得 这满足了资源互斥条件
         */
        threadA.start();
        //threadA.join();
        threadB.start();
        //要想避免死锁,只需要破坏掉至少一个构造死锁的必要条件即可;
        //学过操作系统的朋友知道 目前只有请求并持有和环路等待条件是可以被破会的
        //1.造成死锁的原因其实和申请资源的顺序有很大关系; 使用资源申请的有序性原则就可以避免死锁,
    }

}

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

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

相关文章

Emacs之目前最快补全插件lsp-bridge(八十八)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

数据分析12——Pandas中数据合并方法

0、前言&#xff1a; 在pandas中进行数据合并的操作和数据库中的join操作非常类似。 1、merge横向合并&#xff1a; 前言&#xff1a;该函数只能做横向合并函数名&#xff1a;merge()函数参数&#xff1a; left: 数据类型为’DataFrame | Series’&#xff0c;需要进行合并的…

[CTF/网络安全] 攻防世界 PHP2 解题详析

[CTF/网络安全] 攻防世界 PHP2 解题详析 index.php.phps扩展名姿势 翻译&#xff1a;你能给这个网站进行身份验证吗&#xff1f; index.php index.php是一个常见的文件名&#xff0c;通常用于Web服务器中的网站根目录下。它是默认的主页文件名&#xff0c;在访问一个网站时&am…

说说计算这事儿:从开关到人工智能

目录 一 前言 二 计算历史 三 计算探秘 四 算力优化 五 未来展望 一 前言 计算本身其实是一个比较抽象的词&#xff0c;或者说比较笼统。很多场景都可能用到计算这个词&#xff0c;因此具体的含义就需要根据上下文来确定。今天我们讨论的计算&#xff0c;是比较狭义的计算…

【环境准备】在虚拟机的Ubuntu下安装VS Code并配置C/C++运行环境

1.点击进入 vscode官网 下载.deb安装包 2.启动虚拟机下的Ubuntu&#xff0c;Windows下的Xftp和Xshell Xftp&#xff1a;用于将刚刚在Windows下下载好的vscode.deb安装包传输到Ununtu中。Xshell&#xff1a;用于远程登录Ununtu&#xff0c;进行 vscode.deb 安装包安装&#xff…

算法26:递归练习

目录 题目1&#xff1a;给你一个字符串&#xff0c;要求打印打印出这个字符串的全部子序列&#xff08;子序列不能重复&#xff09; 题目2&#xff1a;打印一个字符串的全部排列。 题目3&#xff1a;针对题目2&#xff0c;要求去除重复元素 题目4&#xff1a;给定一个字符串…

ARM的读写内存指令与栈的应用

1.基础读写指令 写内存指令&#xff1a;STR MOV R1, #0xFF000000 MOV R2, #0x40000000 STR R1, [R2] 将R1寄存器中的数据写入到R2指向的内存空间 需注意&#xff0c;此命令是将R1中的数据写给R2所指向的内存空间&#xff0c;而不是直接把R1的数据赋给R2&#xff0c;R2寄存器…

chatgpt赋能Python-python3_9如何安装

Python 3.9 安装教程 Python 是一款非常流行的编程语言&#xff0c;而 Python 3.9 是其中的最新版本。不过&#xff0c;有些人可能会遇到一些问题&#xff0c;因为这是一个新版本。在本篇文章中&#xff0c;我们将介绍 Python 3.9 的安装过程&#xff0c;并提供一些关键的步骤…

无线通信网 - 动态主机配置协议 DHCP

文章目录 1 概述2 DHCP2.1 工作原理2.2 报文类型 3 扩展3.1 网工软考真题 1 概述 #mermaid-svg-VTnvU3Vd01Y4gppz {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-VTnvU3Vd01Y4gppz .error-icon{fill:#552222;}#merm…

[CTF/网络安全] 攻防世界 Training-WWW-Robots 解题详析

[网络安全] 攻防世界 Training-WWW-Robots 解题详析 在这个小训练挑战中&#xff0c;你将学习 Robots_exclusion_standard&#xff08;机器人排除标准&#xff09;。 robots.txt 文件是由网络爬虫用来检查是否允许他们爬行和索引你的网站或仅部分内容。有时这些文件揭示目录结构…

Vivado HLS 第1讲 软件工程师该怎么了解FPGA架构

Vivado HLS是将基于C/C++描述的算法转化成相应的RTL代码,最终在FPGA上实现。这就要求软件工程师对FPGA的内部架构有一些基本的认识,目的在于保证生成的RTL代码在性能和资源上能够达到很好的平衡。实际上,C语言与FPGA是有一些对应关系的。比如: C语言中的数组可对应于FPGA中…

直方图与直方图均衡化

直方图 图像直方图是用来表现图像中亮度分布的直方图&#xff0c;给出的是图像中某个亮度或者某个范围亮度下共有几个像素&#xff0c;即统计一幅图某个亮度像素数量。 直方图作为一种简单有效的基于统计特性的特征描述子&#xff0c;在计算机视觉领域广泛使用。 它的优点主要…

上下文无关文法、句柄、正规文法、规范推导、文法二义性

目录 上下文无关文法 句柄 正规文法 规范推导 文法二义性 上下文无关文法 上下文无关文法&#xff08;Context-Free Grammar&#xff0c;CFG&#xff09;是一种形式语言&#xff0c;用于描述一类语言的语法结构。它由一组产生式规则组成&#xff0c;每个规则定义了如何将一…

hackthebox htb interface:CVE-2022-28368

本题考察:CVE-2022-28368 CVE-2022-28368 - 通过远程 CSS 字体缓存安装的 RCE 参考: https://www.0le.cn/archives/58.htmlhackthebox-interface信息搜集nmap扫描端口发现开放的22和80PORT STATE SERVICE REASON22/tcp open ssh syn-ac...https://www.0le.cn/archives/58.htm…

Spring 经典面试题总结

❤ 作者主页&#xff1a;欢迎来到我的技术博客&#x1f60e; ❀ 个人介绍&#xff1a;大家好&#xff0c;本人热衷于Java后端开发&#xff0c;欢迎来交流学习哦&#xff01;(&#xffe3;▽&#xffe3;)~* &#x1f34a; 如果文章对您有帮助&#xff0c;记得关注、点赞、收藏、…

learn_C_deep_12 (深度理解“取整“、“取余“、“取模“运算、掌握运算符优先级 )

目录 关于“取整” "取整"规则 1、向零取整 2、向-∞取整 3、向∞取整 4、四舍五入 关于"取模和取余" 运算符优先级 关于“取整” #include <stdio.h> int main() {//本质是向0取整int i -2.9;int j 2.9;printf("%d\n", i); /…

【C++】 排列与组合算法详解(进阶篇)

文章目录 写在前面算法1&#xff1a;朴素算法思路缺点 算法2&#xff1a;递推预处理思路时间复杂度&#xff1a; O ( n 2 ) O(n^2) O(n2) 算法3&#xff1a;阶乘逆元思路时间复杂度&#xff1a; O ( n log ⁡ n ) O(n \log n) O(nlogn)思考&#xff1a;读者也可以尝试写 O ( n…

PySide6/PyQT多线程之 多线程 与 线程池的模板(拿来即用)

前言 关于PySide6/PyQT多线程系列的最后一篇。写这篇文章的动机是方便后续代码的直接复用。 本篇文章实际是水文&#xff0c;给出了 PySide6/PyQT的多线程以及线程池的基础使用模板&#xff0c;方便后面有需要时候直接拿来就用。 多线程 这里分两种情况来谈论&#xff0c;有返…

[Hadoop]MapReduce与YARN

目录 大数据导论与Linux基础 Apache Hadoop、HDFS MapReduce MapReduce思想 MapReduce设计构思 MapReduce介绍 MapReduce官方实例 Map阶段执行流程 Reduce阶段执行流程 shuffle机制 YARN YARN介绍 YARN架构、组件 程序提交YARN交互流程 YARN资源调度器Scheduler…

IOS新建应用

一&#xff1a;Application App。普通app。Document App。打开是记事本类似App。Game。新建游戏相关app。RealityKit为新建一个打开摄像机&#xff0c;一个Ar立方体的应用。 SenceKit为有一架飞机旋转的游戏App。 SpirteKit为一个手指头按上会出一个手指特效的应用。 Metal为一…