线程通信

视频1:

2021.12.18

JUC视频学习片段

对上次多线程编程步骤补充(中部):

  1. 创建资源类,在资源类中创建属性和操作方法
  2. 在资源类里面操作
    • 判断
    • 干活
    • 通知
  3. 创建多个线程,调用资源类的操作方法

线程通信的实现例子:

两个线程,实现对一个初始变量为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之线程间的通信的更多相关文章

  1. Java-JUC(九):使用Lock替换synchronized,使用Condition的await,singal,singalall替换object的wait,notify,notifyall实现线程间的通信

    Condition: condition接口描述了可能会与锁有关的条件变量.这些用法上与使用object.wait访问隐式监视器类似,但提供了更强大的功能.需要特别指出的是,单个lock可能与多个Co ...

  2. iOS开发多线程篇—线程间的通信

    iOS开发多线程篇—线程间的通信 一.简单说明 线程间通信:在1个进程中,线程往往不是孤立存在的,多个线程之间需要经常进行通信 线程间通信的体现 1个线程传递数据给另1个线程 在1个线程中执行完特定任 ...

  3. Java核心知识点学习----多线程并发之线程间的通信,notify,wait

    1.需求: 子线程循环10次,主线程循环100次,这样间隔循环50次. 2.实现: package com.amos.concurrent; /** * @ClassName: ThreadSynch ...

  4. java多线程详解(6)-线程间的通信wait及notify方法

    Java多线程间的通信 本文提纲 一. 线程的几种状态 二. 线程间的相互作用 三.实例代码分析 一. 线程的几种状态 线程有四种状态,任何一个线程肯定处于这四种状态中的一种:(1). 产生(New) ...

  5. iOS边练边学--多线程NSOperation介绍,子类实现多线程的介绍(任务和队列),队列的取消、暂停(挂起)和恢复,操作依赖与线程间的通信

    一.NSOperation NSOperation和NSOperationQueue实现多线程的具体步骤 先将需要执行的操作封装到一个NSOperation对象中 然后将NSOperation对象添加 ...

  6. 新建线程与UI线程间的通信

    现在用一个实例来演示一下自己的新建线程与UI线程间的通信. UI界面包含3个控件: 一个输入框,用来输入数字: 一个显示框,用来显示从2开始,到输入数字之间的所有质数: 一个按钮,点击后获取输入框输入 ...

  7. C# 线程间互相通信

    C#线程间互相通信主要用到两个类:AutoResetEvent和ManualResetEvent. 一.AutoResetEvent AutoResetEvent 允许线程通过发信号互相通信,线程通过 ...

  8. QThread与其他线程间相互通信

    转载请注明链接与作者huihui1988 QThread的用法其实比较简单,只需要派生一个QThread的子类,实现其中的run虚函数就大功告成, 用的时候创建该类的实例,调用它的start方法即可. ...

  9. Handler不同线程间的通信

    转http://www.iteye.com/problems/69457 Activity启动后点击一个界面按钮后会开启一个服务(暂定为padService),在padService中会启动一个线程( ...

随机推荐

  1. API 管理在云原生场景下的机遇与挑战

    作者 | 张添翼 来源 | 尔达Erda公众号 ​ 云原生下的机遇和挑战 标准和生态的意义 自从 Kubernetes v1.0 于 2015 年 7 月 21 日发布,CNCF 组织随后建立以来,其 ...

  2. GPU随机采样速度比较

    技术背景 随机采样问题,不仅仅只是一个统计学/离散数学上的概念,其实在工业领域也都有非常重要的应用价值/潜在应用价值,具体应用场景我们这里就不做赘述.本文重点在于在不同平台上的采样速率,至于另外一个重 ...

  3. myatoi

    atoi (表示 ascii to integer)是把字符串转换成整型数的一个函数,应用在计算机程序和办公软件中.int atoi(const char *nptr) 函数会扫描参数 nptr字符串 ...

  4. C++11的auto自动推导类型

    auto是C++11的类型推导关键字,很强大 例程看一下它的用法 #include<vector> #include<algorithm> #include<functi ...

  5. LeetCode1579题——圆圈中最后剩下的数字

    1.题目描述:0,1,,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字.求出这个圆圈里剩下的最后一个数字.例如,0.1.2.3.4这5个数字组成一个圆圈,从数字0开始每次删 ...

  6. Oracle LOB类型

    一.Oracle中的varchar2类型1.我们在Oracle数据库存储的字符数据一般是用VARCHAR2.VARCHAR2既分PL/SQL Data Types中的变量类型,也分Oracle Dat ...

  7. Insert into select语句引发的生产事故

    前言   Insert into select请慎用.这天xxx接到一个需求,需要将表A的数据迁移到表B中去做一个备份.本想通过程序先查询查出来然后批量插入.但xxx觉得这样有点慢,需要耗费大量的网络 ...

  8. spring注解-web

    以往进行web项目开发都需要在web.xml配置servlet.filter.listener,在Servlet3.0可以通过注解的方式配置它们(注意:必须用tomcat7以上版本) @WebServ ...

  9. SpringAOP简单例子

    这个只是个简单AOP例子,包括前置通知,后置通知,环绕通知,和目标对象.写这个例子的主要目标只是想让想学AOP的能更快地入门,了解一下如何去配置AOP里面的东东.目标对象的接口:IStudent.ja ...

  10. Kafaka相关命令

    开启zookeeper命令(备注:先进入zookeeper的bin目录) ./zkServer.sh start 关闭zookeeper命令(备注:先进入zookeeper的bin目录) ./zkSe ...