JUC之线程间的通信
线程通信
视频1:
2021.12.18
JUC视频学习片段
对上次多线程编程步骤补充(中部):
- 创建资源类,在资源类中创建属性和操作方法
- 在资源类里面操作
- 判断
- 干活
- 通知
- 创建多个线程,调用资源类的操作方法
线程通信的实现例子:
两个线程,实现对一个初始变量为0进行操作,一个线程对其+1,一个线程对其-1,使得变量结果不改变
使用Synchronized实现的线程通信:
package com.JUC;
/**
* 创建资源类
*/
class Share{
//初始值
private int number = 0;
//创建方法
public synchronized void incr() throws InterruptedException {
//判断 干活 通知
if(number != 0){
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"::"+number);
//通知其他线程
this.notifyAll();
//System.out.println(this.getClass());
}
public synchronized void decr() throws InterruptedException {
if(number != 1){
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"::"+number);
//唤醒其他的线程,这里的this指代在方法中指调用该方法的对象
this.notifyAll();
}
}
public class ThreadSignaling {
public static void main(String[] args) throws InterruptedException {
Share share = new Share();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"AAA").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"BBB").start();
}
}
书籍1:
Java并发编程的艺术之线程间通信学习片段
4.3.1章节:volatile和synchronized关键字
volatile:即可见性,当修改一个变量的时候,如果该变量是通过volatile修饰的,那么其他所有的线程都会感知到该变量的变化情况。
如果不使用该关键字的话:
可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
举个简单的例子,看下面这段代码:
//线程1执行的代码
int i = 0;
i = 10; //线程2执行的代码
j = i;
假若执行线程1的是CPU1,执行线程2的是CPU2。由上面的分析可知,当线程1执行 i =10这句时,会先把i的初始值加载到CPU1的高速缓存中,然后赋值为10,那么在CPU1的高速缓存当中i的值变为10了,却没有立即写入到主存当中。
此时线程2执行 j = i,它会先去主存读取i的值并加载到CPU2的缓存当中,注意此时内存当中i的值还是0,那么就会使得j的值为0,而不是10.
这就是可见性问题,线程1对变量i修改了之后,线程2没有立即看到线程1修改的值
上述的解释其实可以对应到书中的以下片段:
Java支持多个线程同时访问一个对象或者对象的成员变量,由于每个线程可以拥有这个变量的拷贝(虽然对象以及成员变量分配的内存是在共享内存中的,但是每个执行的线程还是可以拥有一份拷贝,这样做的目的是加速程序的执行,这是现代多核处理器的一个显著特性),所以程序在执行过程中,一个线程看到的变量并不一定是最新的。
使用关键字synchronized可以修饰方法或者同步块;
作用:确保多个线程在同一时刻,只能由一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性。
任何一个对象都有其对应的监视器,当这个对象由同步块或者同步方法调用的时候,需要进行以下逻辑:

任意线程对Object(Object由synchronized保护)的访问,首先要获得Object的监视器。如果获取失败,线程进入同步队列,线程状态变为BLOCKED。当访问Object的前驱(获得了锁的线程)释放了锁,则该释放操作唤醒阻塞在同步队列中的线程,使其重新尝试对监视器的获取。
视频2:
JUC视频学习片段-Lock-Condition接口的使用
与synchronized再做一个比较:
Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法wait notify和notifyAll的使用;
使用Lock condition接口实现买票:
package com.JUC;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class shareDemo {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
private int number = 0;
public void inc() throws InterruptedException {
lock.lock();
try{
while(number != 0){
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName()+"::"+number);
/**
* 唤醒多有等待的线程
*/
condition.signalAll();
}finally {
lock.unlock();
}
}
public void sub(){
lock.lock();
try{
while(number != 1){
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+"::"+number);
/**
* 唤醒多有等待的线程
*/
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class ConditionLocal {
public static void main(String[] args) {
shareDemo share = new shareDemo();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share.inc();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"AAA").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
share.sub();
}
},"BBB").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share.inc();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"CCC").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
share.sub();
}
},"DDD").start();
}
}
书籍2:
2021.12.29
Java并发编程的艺术之线程间通信学习片段2
在书籍4.3.1-4.3.3对应的其实是该文章中视频1中的内容。
线程间通信的方式还有管道输入/输出流:与文件的输入输出不同的是,它主要用于线程间的数据传输,传输的媒介是内存;
以下是书中的内容:
管道输入/输出流主要包括了如下4种具体实现:PipedOutputStream、PipedInputStream、 PipedReader和PipedWriter,前两种面向字节,而后两种面向字符。
实现例子:
对于Piped类型的流,必须先要进行绑定,也就是调用connect()方法,如果没有将输入/输
出流绑定起来,对于该流的访问将会抛出异常
package com.JUC;
import java.io.IOException;
import java.io.PipedReader;
import java.io.PipedWriter;
public class PipeInOut {
public static void main(String[] args) throws IOException {
PipedWriter out = new PipedWriter();
PipedReader in = new PipedReader();
//将输入流和输出流进行连接,否则会出现IO错误
out.connect(in);
//创建print线程来接收Main中的输入
Thread thread = new Thread(new Print(in),"PrintThread");
//开启该线程,开始接收数据
thread.start();
int receive = 0;
try {
//接收输入的数据并赋值
while((receive = System.in.read()) != -1){
out.write(receive);
}
}finally{
out.close();
}
}
static class Print implements Runnable {
private PipedReader in;
public Print(PipedReader in) {
this.in = in;
}
@Override
public void run() {
int receive = 0;
try {
while((receive = in.read()) != -1){
System.out.print((char) receive);
}
}catch(IOException ex){
}
}
}
}

书籍:4.3.5Thread.join()的使用
书中的定义:其含义是:当前线程A等待thread线程终止之后才从thread.join()返回;感觉不太好理解;
Java 7 Concurrency Cookbook
是主线程等待子线程的终止。也就是说主线程的代码块中,如果碰到了t.join()方法,此时主线程需要等待(阻塞),等待子线程结束了(Waits for this thread to die.),才能继续执行t.join()之后的代码块。
例子:
package com.JUC;
import java.util.concurrent.TimeUnit;
public class Join {
public static void main(String[] args) throws InterruptedException {
Thread previous = Thread.currentThread();
for (int i = 0; i < 10; i++) {
// 每个线程拥有前一个线程的引用,需要等待前一个线程终止,才能从等待中返回
Thread thread = new Thread(new Domino(previous), String.valueOf(i));
thread.start();
previous = thread;
}
TimeUnit.SECONDS.sleep(5);
//主线程
System.out.println(Thread.currentThread().getName()+"--terminate.");
}
static class Domino implements Runnable {
private Thread thread;
public Domino(Thread thread) {
this.thread = thread;
}
@Override
public void run() {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
//子线程
System.out.println(Thread.currentThread().getName()+"---Terminate.");
}
}
}
每个线程终止的前提是前驱线程的终止,每个线程等待前驱线程终止后,才从join()方法返回;
我们查看下join方法的源码可以发现其中也是用的synchronized修饰的;
public final void join() throws InterruptedException {
join(0);
}
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
关于书中的4.3.6ThreadLocal的使用可以看下以前写的文章:点击进入
JUC之线程间的通信的更多相关文章
- Java-JUC(九):使用Lock替换synchronized,使用Condition的await,singal,singalall替换object的wait,notify,notifyall实现线程间的通信
Condition: condition接口描述了可能会与锁有关的条件变量.这些用法上与使用object.wait访问隐式监视器类似,但提供了更强大的功能.需要特别指出的是,单个lock可能与多个Co ...
- iOS开发多线程篇—线程间的通信
iOS开发多线程篇—线程间的通信 一.简单说明 线程间通信:在1个进程中,线程往往不是孤立存在的,多个线程之间需要经常进行通信 线程间通信的体现 1个线程传递数据给另1个线程 在1个线程中执行完特定任 ...
- Java核心知识点学习----多线程并发之线程间的通信,notify,wait
1.需求: 子线程循环10次,主线程循环100次,这样间隔循环50次. 2.实现: package com.amos.concurrent; /** * @ClassName: ThreadSynch ...
- java多线程详解(6)-线程间的通信wait及notify方法
Java多线程间的通信 本文提纲 一. 线程的几种状态 二. 线程间的相互作用 三.实例代码分析 一. 线程的几种状态 线程有四种状态,任何一个线程肯定处于这四种状态中的一种:(1). 产生(New) ...
- iOS边练边学--多线程NSOperation介绍,子类实现多线程的介绍(任务和队列),队列的取消、暂停(挂起)和恢复,操作依赖与线程间的通信
一.NSOperation NSOperation和NSOperationQueue实现多线程的具体步骤 先将需要执行的操作封装到一个NSOperation对象中 然后将NSOperation对象添加 ...
- 新建线程与UI线程间的通信
现在用一个实例来演示一下自己的新建线程与UI线程间的通信. UI界面包含3个控件: 一个输入框,用来输入数字: 一个显示框,用来显示从2开始,到输入数字之间的所有质数: 一个按钮,点击后获取输入框输入 ...
- C# 线程间互相通信
C#线程间互相通信主要用到两个类:AutoResetEvent和ManualResetEvent. 一.AutoResetEvent AutoResetEvent 允许线程通过发信号互相通信,线程通过 ...
- QThread与其他线程间相互通信
转载请注明链接与作者huihui1988 QThread的用法其实比较简单,只需要派生一个QThread的子类,实现其中的run虚函数就大功告成, 用的时候创建该类的实例,调用它的start方法即可. ...
- Handler不同线程间的通信
转http://www.iteye.com/problems/69457 Activity启动后点击一个界面按钮后会开启一个服务(暂定为padService),在padService中会启动一个线程( ...
随机推荐
- linux 内存变量的分布
我们知道,linux通过虚拟内存管理进程的内存(进程的地址空间),而进程的地址空间分布如下 : 从进程的空间中可以看出,内存中的变量有的来自可执行elf文件,在elf文件中已经分配好存储空间,有的是在 ...
- 零基础学习java------37---------mybatis的高级映射(单表查询,多表(一对一,一对多)),逆向工程,Spring(IOC,DI,创建对象,AOP)
一. mybatis的高级映射 1 单表,字段不一致 resultType输出映射: 要求查询的字段名(数据库中表格的字段)和对应的java类型的属性名一致,数据可以完成封装映射 如果字段和jav ...
- Android消除Toast延迟显示
Toast可以用来显示音量改变或者保存更新消息,如果用户一直点击,Toast会排队一个一个的,直到消息队列全部显示完,这样的效果显然是不好的,下面来看解决方法 Toast.makeText(ac ...
- postman 中get传参数
mybatis中: @RequestMapping(value = "/detail/{id}", method = RequestMethod.GET, produces = & ...
- [MySQL实战-Mysql基础篇]-mysql架构
1.基本组成 下面是mysql的基本架构示意图 图一 图二 我们可以从图上看出,mysql大体分为两个部分,一个是server层,另一个是引擎层. server层中包含了连接器.查询缓存.分析器.优 ...
- drone 使用git tag触发构建
配置ref为tag .drone.yml中配置trigger为ref trigger: ref: - refs/tags/FileService 或者配置when为ref when: ref: - r ...
- 配置yum代理
一.说明 很多内网环境无法使用yum 二.配置 1.安装nginx 2.配置 server { listen 808; #禁用multipart range功能 max_ranges 1; serve ...
- shell脚本 微信/钉钉验证登录服务器
一.简介 登录用户需要二次验证码进行验证 可以配合 监控用户登录,发送通知给企业微信/钉钉 来使用 脚本放到/etc/profile.d/ 目录,登录的时候自动触发 二.微信脚本 1.需要修改Crop ...
- 关于Too many levels of symbolic links和 /usr/bin/env: node: 没有那个文件或目录
由于node装了两遍在运行bower install的时候就会报错Too many levels of symbolic links要卸载其中一个nodejs,卸载的方法: 1. 卸载node npm ...
- 关于thinkPHP中的自动加载和手动导入
首先先讲自动加载: 前提:你的第三方类库要满足(1)符合命名规范和后缀的类库(2)使用命名空间,命名空间和路径一致的类库 (1)在ThinkPHP目录下的library目录下的每一个子目录都是一个根命 ...