java之线程同步和线程之间的通信

news2025/1/13 15:55:24

线程同步的概念:

由于同一个进程的多个线程共享同一块存储空间,在带来方便的同时,也会带来访问冲突的问题

举例:

public class Runnable_test implements Runnable {//实现Runnable接口
private  int ticknumbers=10;

    @Override
    public void run() {
        while(true){
            if(ticknumbers<=0){
                break;
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"-->拿到了第"+ticknumbers--+"票");//currentThread()监测线程的状态
        }
    }

    public static void main(String[] args) {
        Runnable_test runnable_test=new Runnable_test();
        new Thread(runnable_test,"小明").start();
        new Thread(runnable_test,"小黄").start();
        new Thread(runnable_test,"小红").start();
    }
}

在输出的数据中,显然出现了,一张票同时被大于1人拿到的情况,这与我们的现实显然不相符合。

为了解决此问题,Java 语言提供专门的机制来避免同一个对象被多个线程同时访问,这个机制就是线程同步

当两个或多个线程同时访问同一个变量,并且有线程需要修改这个变量时,就必须采用同步的机制对其进行控制,否则就会出现逻辑错误的运行结果

在这里插入图片描述

造成上述这种错误逻辑结果的原因是:可能有多个线程取得的是同一个值,各自修改并存入,从而造成修改慢的后执行的线程把执行快的线程的修改结果覆盖掉了

因为线程在执行过程中不同步,多个线程在访问同一资源时,需要进行同步操作,被访问的资源称为共享资源

同步的本质是加锁,Java 中的任何一个对象都有一把锁以及和这个锁对应的等待队列,当线程要访问共享资源时,首先要对相关的对象进行加锁

如果加锁成功,线程对象才能访问共享资源并且在访问结束后,要释放锁:如果加锁不成功,那么线程进入被加锁对象对应的是等待队列。

Java用synchronized关键字给针对共享资源进行操作的方法加锁。每个锁只有一把钥匙,只有得到这把钥匙之后才可以对被保护的资源进行操作,而其他线程只能等待,直到拿到这把钥匙。

实现同步的具体方式有同步代码块和同步方法两种

同步代码块:

使用 synchronized 关键字声明的代码块称为同步代码块

在任意时刻,只能有一个线程访问同步代码块中的代码,所以同步代码块也称为互斥代码块

同步代码块格式如下所示:

synchronized(同步对象){
//需要同步的代码,对共享资源的访问
}

synchronized关键字后面括号内的对象就是被加载的对象,同步代码块要实现对共享资源的访问

对上述实例进行修改:

package Runnable;

public class Runnable_test implements Runnable {//实现Runnable接口
    private int ticknumbers = 20;
    private Object obj = new Object();//被加锁的对象,同步对象

    @Override
    public void run() {
        while (true) {
            synchronized (obj) {
                if (ticknumbers > 0) {
                    System.out.println(Thread.currentThread().getName() + "-->拿到了第" + ticknumbers-- + "票");//currentThread()监测线程的状态
                    try {
                        Thread.sleep(300);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else
                    break;
            }
        }
    }
}


class test{
    public static void main(String[] args) {
        Runnable_test runnable_test=new Runnable_test();
        new Thread(runnable_test,"小明").start();
        new Thread(runnable_test,"小黄").start();
        new Thread(runnable_test,"小红").start();
    }
}

将票数产生变化的代码块修改为同步代码块:

在这里插入图片描述
修改过后输出,我们发现,并未出现同一张票,被第二个甚至第三个人拿到的情况:

小明-->拿到了第20票
小明-->拿到了第19票
小明-->拿到了第18票
小明-->拿到了第17票
小明-->拿到了第16票
小明-->拿到了第15票
小红-->拿到了第14票
小红-->拿到了第13票
小红-->拿到了第12票
小黄-->拿到了第11票
小黄-->拿到了第10票
小黄-->拿到了第9票
小黄-->拿到了第8票
小红-->拿到了第7票
小红-->拿到了第6票
小红-->拿到了第5票
小红-->拿到了第4票
小红-->拿到了第3票
小红-->拿到了第2票
小红-->拿到了第1

在上面的修改中,仅仅是将需要互斥的代码放人了同步块中。此时,在抽票的过程中通过给同一个 obj对象加锁来实现互斥,从而保证线程的同步执行。

同步方法:

synchronized关键字也可以出现在方法的声明部分,该方法称为同步方法

当多个线程对象同时访问共享资源时,只有获得锁对象的线程才能进入同步方法执行,其他访问共享资源的线程将会进入锁对象的等待队列,执行完同步方法的线程会释放锁。

[权限访问限定]	synchronized 方法返回值 方法名称(参数列表){
//.............需要同步的代码,对共享资源的访问
}
package Runnable;

public class Runnable_test implements Runnable {//实现Runnable接口
    private int ticknumbers = 20;
    @Override
    public  void run() {
        while (true) {
            if (ticknumbers > 0) {
                ticks();//调用同步方法
            }
        else
            break;
        }
    }
    
    //同步方法
    public synchronized void ticks(){
        if (ticknumbers > 0) {
            System.out.println(Thread.currentThread().getName() + "-->拿到了第" + ticknumbers-- + "票");
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//测试类
class test{
    public static void main(String[] args) {
        Runnable_test runnable_test=new Runnable_test();
        new Thread(runnable_test,"小明").start();
        new Thread(runnable_test,"小黄").start();
        new Thread(runnable_test,"小红").start();
    }
}

输出:

小明-->拿到了第20票
小明-->拿到了第19票
小明-->拿到了第18票
小明-->拿到了第17票
小明-->拿到了第16票
小黄-->拿到了第15票
小黄-->拿到了第14票
小黄-->拿到了第13票
小黄-->拿到了第12票
小黄-->拿到了第11票
小黄-->拿到了第10票
小黄-->拿到了第9票
小红-->拿到了第8票
小红-->拿到了第7票
小红-->拿到了第6票
小红-->拿到了第5票
小红-->拿到了第4票
小红-->拿到了第3票
小红-->拿到了第2票
小红-->拿到了第1

同步方法的本质也是给对象加锁,但是是给同步方法所在类的 this 对象加锁,所以在上述实例中,我们就删除了obj对象的定义。

package Runnable;

public class Runnable_test implements Runnable {//实现Runnable接口
    private int ticknumbers = 20;
    boolean tag = false;//设置此变量的作用是为了让一个线程进入同步块,另一个线程进入同步方法

    @Override
    public void run() {
        if(tag){
            while(true)
                ticks();
        }
        else{
            while (true) {
                synchronized (this) {
                    if (ticknumbers > 0) {
                        System.out.println(Thread.currentThread().getName() + "-->拿到了第" + ticknumbers-- + "票");//currentThread()监测线程的状态
                        try {
                            Thread.sleep(300);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else
                        break;
                }
            }
        }
    }

    //同步方法
    public synchronized void ticks() {
        if (ticknumbers > 0) {
            System.out.println(Thread.currentThread().getName() + "-->拿到了第" + ticknumbers-- + "票");
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        else
            return;
    }
}


    //测试类
    class test {
        public static void main(String[] args) throws InterruptedException {
            Runnable_test runnable_test = new Runnable_test();
            Thread thread1=new Thread(runnable_test, "小明");
            thread1.start();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            runnable_test.tag=true;
            Thread thread2=new Thread(runnable_test, "小黄");
            thread2.start();
        }
    }

输出:

小明-->拿到了第20票
小明-->拿到了第19票
小明-->拿到了第18票
小明-->拿到了第17票
小明-->拿到了第16票
小明-->拿到了第15票
小明-->拿到了第14票
小明-->拿到了第13票
小明-->拿到了第12票
小明-->拿到了第11票
小明-->拿到了第10票
小明-->拿到了第9票
小明-->拿到了第8票
小明-->拿到了第7票
小黄-->拿到了第6票
小黄-->拿到了第5票
小黄-->拿到了第4票
小黄-->拿到了第3票
小黄-->拿到了第2票
小黄-->拿到了第1

通过程序运行结果可以看出:线程thread1执行同步代码块,线程thread2执行同步方法,两个线程之间形成了同步。

由于同步代码块是给 this对象加锁,所以表明同步方法也是给 this对象加锁,否则,两者之间不能形成同步。

注意:多线程的同步程序中,不同的线程对象必须给同一个对象加锁,否则这些线程对象之间无法实现同步

线程组:

线程组可以看作是包含了许多线程的对象集,它拥有一个名字以及一些相关的属性,可以当作一个组来管理其中的线程。

每个线程都是线程组的一个成员,线程组把多个线程集成一个对象,通过线程组可以同时对其中的多个线程进行操作。在生成线程时必须将线程放到指定的线程组,也可以放在缺省的线程组中,缺省的就是生成该线程的线程所在的线程组。一旦一个线程加入了某个线程组,就不能被移出这个组。

java,lang包的ThreadGroup类表示线程组,在创建线程之前,可以创建一个ThreadGroup对象。

下面代码是创建线程组并在其中加人两个线程

ThreadGroup myThreadGroup = new ThreadGroup("a");	//创建线程组

//将下述两个线程加入其中
Thread myThread1 = new Thread(myThreadGroup,"worker1");
Thread myThread2 = new Thread(myThreadGroup,"worker2");
myThread1.start();
myThread2.start();

线程组的相关方法:

String getName();	//返回线程组的名字
ThreadGoup getParent();	//返回父线程
int tactiveCount();	//返回线程组中当前激活的线程的数目,包括子线程组中的活动线程
int enumerate(Thread list[])	//将所有线程组中激活的线程复制到一个线程数组中
void setMaxPriority(int pri)	//设置线程的最高优先级,pri是该线程组的新优先级
void interrupt()	//向线程组及其子组中的线程发送一个中断信息
boolean isDaemon()	//判断是否为Daemon线程组
boolean parentOf(ThreadGoup g)	//判断线程组是否是线程g或g的子线程
toString()	//返回一个表示本线程组的字符串 

线程组对象的基本应用:

举例:

package Runnable;

public class MyThreadgroup {
    public void test(){
        ThreadGroup threadGroup=new ThreadGroup("test");    //创建名为test的线程组
        Thread A=new Thread(threadGroup,"线程A");
        Thread B=new Thread(threadGroup,"线程B");
        Thread C=new Thread(threadGroup,"线程C");

        //为线程设置优先级
        A.setPriority(6);
        C.setPriority(4);
        A.start();
        B.start();
        C.start();
        System.out.println("threadGroup正在进行活动的个数:"+threadGroup.activeCount());
        System.out.println("线程A的优先级:"+A.getPriority());
        System.out.println("线程B的优先级:"+B.getPriority());
        System.out.println("线程C的优先级:"+C.getPriority());
    }
}
class MyThreadgroup_test{
    public static void main(String[] args) {
       MyThreadgroup myThreadgroup=new MyThreadgroup();
       myThreadgroup.test();
    }
}

输出:

threadGroup正在进行活动的个数:3
线程A的优先级:6
线程B的优先级:5
线程C的优先级:4

线程间的通信:

某些情况下,多个线程之间需要相互配合来完成一件事情,这些线程之间就需要进行通信”,把一方线程的执行情况告诉给另一方线程。

“通信”的方法在 java.lang.Object类中定义了,我们可以通过“生产者-消费者”模型来理解线程间的通信。

有两个线程对象,其中一个是生产者,另一个是消费者。生产者线程负责生产产品并放入产品缓冲区,消费者线程负责从产品缓冲区取出产品并消费。

当生产者线程获得 CPU 使用权后:

先判断产品缓冲区是否有产品,如果有产品就调用 wait()方法进入产品缓冲区对象的等待队列并释放产品缓冲区对象的锁;如果发现产品缓冲区中没有产品,就生产产品并放入缓冲区并调用notify()方法发送通知给消费者线程。

当消费者线程获得CPU使用权后:

先判断产品缓冲区是否有产品,如果有产品就拿出来消费并调用 notify()方法发送通知给生产者线程;如果发现产品缓冲区中没有产品,调用 wait()方法进入产品缓冲区对象的等待队列并释放产品缓冲区对象的锁。

注意:线程间通信是建立在线程同步基础上的,所以wait()notify()和notifyAll()方法的调用要出现在同步代码块或同步方法中

线程通信简单应用:

package Runnable;


 class Box {//产品缓冲区
    public String name="苹果";//表示产品的名称
    public boolean isFull=true;//表示当前缓冲区中是否有产品
}



//定义消费者类
class Cossumer implements Runnable {
    Box box;

    Cossumer(Box box) {
        this.box = box;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (box) {//对产品缓冲区对象加锁
                if (box.isFull == true) //缓冲区中有产品
                {
                    System.out.println("消费者拿出----:" + box.name);
                    box.isFull = false;//设置缓冲区中产品为空
                    box.notify();//发送通知给生产者线程对象
                } else {
                    try {
                        //消费者线程进入产品缓冲区的等待队列并释放锁
                        box.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}



    //生产者类
    class product implements Runnable{
        Box box;
        int Count=0;

        public product(Box box) {
            this.box=box;
        }

        @Override
        public void run() {
            while(true){
                synchronized (box)//对产品缓冲区对象加锁
                {
                    if(box.isFull==true)//缓冲区中有产品
                    {
                        try {
                            box.wait();//生产者线程进入等待队列并释放锁
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    else {
                        if (Count == 0) {
                            box.name = "香蕉";
                            System.out.println("生产者放入+++++:" + box.name);
                        } else {
                            box.name = "苹果";
                            System.out.println("生产者放入+++++:" + box.name);
                        }
                        Count=(Count+1)%2;
                        box.isFull=true;//设置缓冲区中有产品
                        box.notify();//发送通知给消费者线程对象
                    }
                }
            }
        }
    }


class box_test{
    public static void main(String[] args) {
        Box box=new Box();//创建产品缓冲区对象
        product product=new product(box);

        Cossumer cossumer=new Cossumer(box);//生产者和消费者对象要共享同一个产品缓冲区
        Thread thread1=new Thread(product);//创建生产者线程对象
        Thread thread2=new Thread(cossumer);//创建消费者线程对象
        thread1.start();//启动生产者线程对象
        thread2.start();//启动消费者线程对象
    }
}

输出:

消费者拿出----:香蕉
生产者放入+++++:苹果
消费者拿出----:苹果
生产者放入+++++:香蕉
消费者拿出----:香蕉
生产者放入+++++:苹果
消费者拿出----:苹果
生产者放入+++++:香蕉
消费者拿出----:香蕉
生产者放入+++++:苹果
消费者拿出----:苹果
生产者放入+++++:香蕉

从运行结果可以看出:生产者线程向缓冲区放入什么产品,消费者就从缓冲区中取出什么产品,生产者生产一个产品,消费者就消费一个产品,两者之间实现了通信

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

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

相关文章

【C++】——数据类型(二)

文章目录2. 数据类型2.1 整型2.2 sizeof关键字2.3 浮点型2.3.1 浮点数表示——小数点表示法2.3.2 浮点数表示——E表示法2.3.3 浮点数类型2.4 字符型2.5 转义字符2.6 字符串型2.7 布尔类型bool2. 数据类型 2.1 整型 整数就是没有小数部分的数字&#xff0c;如2、98、 -5286 和…

【机器学习】模型评估与选择(实战)

模型评估与选择&#xff08;实战&#xff09; 目录一、准备工作&#xff08;设置 jupyter notebook 中的字体大小样式等&#xff09;二、数据集读取与查看三、交叉验证实验1、划分数据集并置乱2、设计交叉验证实验3、进行训练&#xff08;采用随机梯度下降分类器&#xff09;4、…

推荐一款数据可视化分析工具

当今时代数据分析的发展&#xff0c;导致数据可视化成为企业必不可少的一部分&#xff0c;进而市面上也就涌现出各种丰富多彩的工具。传统的Excel无疑是数据可视化工具的典型&#xff0c;我们平时经常使用Excel制作简单表格&#xff0c;简单、方便&#xff0c;但是复杂一点的可…

酒业崛起一支奇兵,009将自信走向全球

“天若不爱酒&#xff0c;酒星不在天。地若不爱酒&#xff0c;地应无酒泉。天地既爱酒&#xff0c;爱酒不愧天。”这是李白《月下独酌》中的句子&#xff0c;诗仙也是酒仙&#xff0c;已是广为人知的轶事。 中国是酒的国度&#xff0c;酒也是历史和文化的一种表达。正因为如此&…

【读书笔记】高级FPGA设计之面积结构设计

目录 面积结构设计 折叠流水线 基于控制的逻辑复用 资源共享 复位对面积的影响 无复位的资源 无置位的资源 无同步复位的资源 复位 RAM 利用置位/复位触发器引脚 总结 面积结构设计 本篇讨论数字设计的三个主要物理特性的第二个&#xff1a;面积。并分析在FPGA中结构…

【代码随想录】Day67哈希表:力扣242,383,1,349,202,454,15,18

目录 基础知识 哈希表 哈希函数 2.哈希碰撞 常见的哈希结构&#xff08;三种&#xff09; 数组 集合set 映射map 经典题目 数组作为哈希表 例题&#xff1a;力扣242 已完成 例题&#xff1a;力扣383 已完成 例题&#xff1a;力扣49 例题&#xff1a;力扣438 set…

小黑实习第二天,正在为hbase而头疼的leetcode之旅:671. 二叉树中第二小的节点

小黑代码(暴力) # Definition for a binary tree node. # class TreeNode: # def __init__(self, val0, leftNone, rightNone): # self.val val # self.left left # self.right right class Solution:def findSecondMinimumValue(self, root: …

执照吊销了能否恢复

一、执照吊销了能否恢复 &#xff11;、按照法律规定&#xff0c;企业法人被吊销营业执照&#xff0c;只是企业解散程序的开始。《公司法》规定&#xff0c;企业法人被吊销营业执照后应当依法进行清算&#xff0c;清算程序结束并办理工商注销登记后&#xff0c;该企业法人才归…

间隔分区表merge into报错“-2903: 语句块/包/存储函数中的间隔分区不支持自动扩展”

描述 版本&#xff1a; DM V8 --08134283904-20220804-166351-20005 Pack4 初始化参数&#xff1a; 默认 ini参数&#xff1a; 默认 执行间隔分区表上执行merge into语句报错&#xff0c;信息如下&#xff1a; 同样的语句&#xff0c;在Oracle中执行正常。 测试 创建环境&a…

Springboot利用redis缓存,结合Aop与自定义注解实现接口节流

接口的节流是开发过程中为了防止单一微服务模块突然遭受太多并发导致用户服务不流畅而产生的业务需求&#xff0c;就是实现在固定时间内访问同一个接口的次数也固定。开发过程中通常采用redis去作为缓存去快存快取&#xff0c;对于需求次数较多的数据可以存储在redis内部&#…

Ansible剧本使用

剧本语言 剧本使用的yaml语言 yaml文件的后缀为.yml或者.yaml 使用空格做为缩进 相同层级的元素左侧对齐即可 缩进时不允许使用 Tab 键&#xff0c;只允许使用空格 创建剧本 直接编辑不存在会自动创建这个文件&#xff0c;先用touch新建也行 vim juben.yml编写剧本 hosts&am…

C语言零基础项目:2D 赛车游戏,详细思路+源码分享

目录 一、简介 二、如何建立一个地图包 三、关于碰撞图的绘制 四、游戏时的说明 五、如何更好地绘制赛场图与碰撞图&#xff1f; 游戏截图 源码下载 一、简介 此游戏是《2D 赛车》的”魔改版“——2.5D 双人赛车&#xff01; 原作实现了 2D 视角的赛车游戏&#xff0c…

关于 国产麒麟系统赋值给双精度double时乘以1.0f编译器优化 的解决方法

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/128459376 红胖子(红模仿)的博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软…

【 uniapp - 黑马优购 | 首页】小程序首页全局配置(home、网络请求、轮播图、分类...)

个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名大二在校生&#xff0c;讨厌编程&#x1f38b; &#x1f43b;‍❄️个人主页&#x1f947;&#xff1a;小新爱学习. &#x1f43c;个人WeChat&#xff1a;hmmwx53 &#x1f54a;️系列专栏&#xff1a;&#x1f5bc…

csdn里的KaTex 公式语法

KaTex 语法 Accents 字母各种上下标 波浪&#xff0c;箭头&#xff0c;声调等 Delimiters 分隔符 大括号&#xff0c;小括号&#xff0c;方括号 Environments 行列式里多行多列数字表达式包含 HTML Letters and Unicode 字符和 Unicode Layout 布局 Spacing 空格 Logic and Se…

Centos系统,防火墙没开,docker部署的rabbitmq不能外网访问监听端口,但别的端口都能正常访问???

真是一个神奇的问题&#xff0c;防火墙firewalld ,iptables都没开。 之前访问都正常&#xff0c;最近可能是服务器被动了。rabbitmq的相关监听接口&#xff0c;只能本机服务器连接了&#xff0c;导致设备连接不了rabbitmq组件了。 排查问题记录 1.防火墙是否开启。发现是关闭…

web仿真或实际内存分析应用及自动化方案

js 自带 GC&#xff08;垃圾回收&#xff09;机制&#xff0c;因此绝大多数 web 开发人员不会在日常开发中考虑内存情况&#xff08;包括本人&#xff09;&#xff0c;在多数业务场景中&#xff0c;这可能没有问题&#xff0c;但在一些核心web应用场景下&#xff08;比如某个页…

【Spring(一)】初识Spring(史上最详细的Spring介绍!)

文章目录前言1.初识Spring2.Spring Framework系统架构3.核心概念前言 在学习 Spring 之前&#xff0c;我们需要先知道为什么要学习它?    IT业的任何一门技术,它只有抢占了很强的市场占有率&#xff0c;才会有更多的人使用和学习它&#xff0c;Spring技术在我们Java开发界拥…

APP怎么免费接入MobPush

1、获取AppKey 申请Appkey的流程&#xff0c;请点击 http://bbs.mob.com/thread-8212-1-1.html?fromuid70819 2、下载SDK 下载解压后&#xff0c;如下图&#xff1a; 目录结构 &#xff08;1&#xff09;Sample&#xff1a;演示Demo。&#xff08;2&#xff09;SDK&#…

【C操作符】详解操作符

操作符前言一、操作符分类二、算数操作符三、移位操作符&#xff08;一&#xff09;原码、补码、反码&#xff08;二&#xff09;操作符应用1.左移操作符&#xff08;1&#xff09;正数&#xff08;2&#xff09;负数&#xff08;3&#xff09;总结2.右移操作符&#xff08;1&a…