Java 多线程并发编程
导读
创作不易,禁止转载!
并发编程简介
发展历程
早起计算机,从头到尾执行一个程序,这样就严重造成资源的浪费。然后操作系统就出现了,计算机能运行多个程序,不同的程序在不同的单独的进程中运行,一个进程,有多个线程,提高资源的利用率。ok,如果以上你还不了解的话,我这里有2个脑补链接(点我直达1、点我直达2)
简介(百度百科)
所谓并发编程是指在一台处理器上“同时”处理多个任务。并发是在同一实体上的多个事件。多个事件在同一时间间隔发生。
目标(百度百科)
并发编程的目标是充分的利用处理器的每一个核,以达到最高的处理性能。
串行与并行的区别
可能这个栗子不是很恰当,仁者见仁智者见智。智者get到点,愚者咬文爵字,啊!你这个栗子不行,不切合实际,巴拉巴拉 .....为啥加起来是2小时6分钟,吃饭不要时间麽(洗衣服:把要洗的衣服塞到洗衣机,包括倒洗衣液等等3分钟;做饭:同理),你大爷的,吃饭的时候不能看电影嘛。好了,请出门右转,这里不欢迎杠精,走之前把门关上!!!通过这个栗子,可以看出做相同的事情,所花费的时间不同(这就是为啥工作中,每个人的工作效率有高低了叭)。
什么时候适合并发编程
- 任务阻塞线程,导致之后的代码不能执行:一边从文件中读取,一边进行大量计算
- 任务执行时间过长,可以瓜分为分工明确的子任务:分段下载文件
- 任务间断性执行:日志打印
- 任务协作执行:生产者消费者问题
并发编程中的上下文切换
以下内容,百度百科原话(点我直达)。
上下文切换指的是内核(操作系统的核心)在CPU上对进程或者线程进行切换。上下文切换过程中的信息被保存在进程控制块(PCB-Process Control Block)中。PCB又被称作切换桢(SwitchFrame)。上下文切换的信息会一直被保存在CPU的内存中,直到被再次使用。
每个任务都是整个应用的一部分, 都被赋予一定的优先级, 有自己的一套CPU寄存器和栈空间。
最重要的一句话:上下文频繁的切换,会带来一定的性能开销。
减少上下文切换开销方法
- 无锁并发编程
- 多线程竞争锁时,会引起上下文切换,所以多个线程处理数据时,可以用一些办法来避免使用锁,如将数据的ID按照Hash算法取模分段,不同的线程处理不同段的数据
- CAS
- Java的Atomic包使用CAS算法来更新数据,而不需要加锁
- 控制线程数
- 避免创建过多不需要的线程,当任务少的时候,但是创建很多线程来处理,这样会造成大量线程都处于等待状态
- 在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。
协程(GO语言)
知乎上,有个人写的不错,推荐给大家:点我直达
死锁(代码演示)
第一次执行,没有发生死锁,第二次执行时,先让线程A睡眠50毫秒,程序一直卡着不动,发生死锁。你不让我,我不让你,争夺YB_B的资源。
查看死锁(在重要不过啦)(jdk提供的一些工具)
- 命令行工具:jps
- 查看堆栈:jstack pid
- 可视化工具:jconsole
jps&jstack
分析
jconsole
控制台输入:jconsole,然后按照gif,看线程->检测死锁
代码拷贝区
package com.yb.thread; /**
* @ClassName:DeadLockDemo
* @Description:死锁代码演示
* @Author:chenyb
* @Date:2020/9/7 10:23 下午
* @Versiion:1.0
*/
public class DeadLockDemo {
private static final Object YB_A=new Object();
private static final Object YB_B=new Object(); public static void main(String[] args) {
new Thread(()->{
synchronized (YB_A){
try {
//让线程睡眠50毫秒
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (YB_B){
System.out.println("线程-AAAAAAAAAAAAA");
}
}
}).start();
new Thread(()->{
synchronized (YB_B){
synchronized (YB_A){
System.out.println("线程-BBBBBBBBBBBBB");
}
}
}).start();
}
}
线程基础
进程与线程的区别
进程:是系统进行分配和管理资源的基本单位
线程:进程的一个执行单元,是进程内调度的实体、是CPU调度和分派的基本单位,是比进程更小的独立运行的基本单位。线程也被称为轻量级进程,线程是程序执行的最小单位。
一个程序至少一个进程,一个进程至少一个线程。
线程的状态(枚举)
- 初始化(NEW)
- 新建了一个线程对象,但还没有调用start()方法
- 运行(RUNNABLE)
- 处于可运行状态的线程正在JVM中执行,但他可能正在等待来自操作系统的其他资源
- 阻塞(BLOCKED)
- 线程阻塞与synchronized锁,等待获取synchronized锁的状态
- 等待(WAITING)
- Object.wait()、join()、LockSupport.part(),进入该状态的线程需要等待其他线程做出一些特定动作(通知|中断)
- 超时等待(TIME_WAITING)
- Object.wait(long)、Thread.join()、LockSupport.parkNanos()、LockSupport.parkUntil,该状态不同于WAITING
- 终止(TERMINATED)
- 该线程已经执行完毕
创建线程
方式一
方式二(推荐)
好处
- java只能单继承,但是接口可以继承多个
- 增加程序的健壮性,代码可以共享
注意事项
方式三(匿名内部类)
方式四(Lambada)
方式五(线程池)
注意:程序还未关闭!!!!
线程的挂起与恢复
方式一(不推荐)
不推荐使用,会造成死锁~
方式二(推荐)
wait():暂停执行,放弃已获得的锁,进入等待状态
notify():随机唤醒一个在等待锁的线程
notifyAll():唤醒所有在等待锁的线程,自行抢占CPU资源
线程的中断
方式一(不推荐)
注意:使用stop()可以中断线程,但是会带来线程不安全问题(stop被调用,线程立刻停止),理论上numA和numB都是1,结果numB=0;还是没搞明白的,给你个眼神,自己体会~
方式二(推荐)
方式三(更推荐)
线程优先级
线程的优先级告诉程序该线程的重要程度有多大。如果有大量线程都被阻塞,都在等候运行,程序会尽可能地先运行优先级的那个线程。但是,这并不表示优先级较低的线程不会运行。若线程的优先级较低,只不过表示它被准许的机会小一些而已。
线程的优先级
- 最小=1
- 最大=10
- 默认=5
验证
可以看出,打印线程2的几率比较大,因为线程优先级高。线程优先级,推荐使用(不同平台对线程的优先级支持不同):1、5、10
守护线程(不建议使用)
任何一个守护线程都是整个程序中所有用户线程的守护者,只要有活着的用户线程,守护线程就活着。
线程安全性
synchronized
修改普通方法:锁住对象的实例
修饰静态方法:锁住整个类
修改代码块:锁住一个对象synchronized (lock)
volatile
仅能修饰变量,保证该对象的可见性(多线程共享的变量),不保证原子性。
用途
- 线程开关
- 单例修改对象的实例
锁
lock的使用
lock与synchronized区别
lock:需要手动设置加锁和释放锁
synchronized:托管给jvm执行
查看lock的实现类有哪些
多线程下调试
注意看图,线程1、2、3的状态:Runnable|wailting,还没get到点的话,你真的要反思一下了
读写锁
读写互斥、写写互斥、读读不互斥
如果要想debug调试查看效果,可开2个线程,一个自增,一个输出
package com.yb.thread.lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock; /**
* @ClassName:ReentrantReadWriteLockDemo
* @Description:读写锁
* @Author:chenyb
* @Date:2020/9/26 3:14 下午
* @Versiion:1.0
*/
public class ReentrantReadWriteLockDemo {
private int num_1 = 0;
private int num_2 = 0;
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
//读锁
private Lock readLock = lock.readLock();
//写锁
private Lock writeLock = lock.writeLock(); public void out() {
readLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "num1====>" + num_1 + ";num_2======>" + num_2);
} finally {
readLock.unlock();
}
} public void inCreate() {
writeLock.lock();
try {
num_1++;
try {
Thread.sleep(500L);
} catch (InterruptedException e) {
e.printStackTrace();
}
num_2++;
} finally {
writeLock.unlock();
}
} public static void main(String[] args) {
ReentrantReadWriteLockDemo rd = new ReentrantReadWriteLockDemo();
// for(int x=0;x<3;x++){
// new Thread(()->{
// rd.inCreate();
// rd.out();
// }).start();
// } //=========读写互斥
new Thread(() -> {
rd.inCreate();
}, "写").start();
new Thread(() -> {
rd.out();
}, "读").start(); //========写写互斥
new Thread(() -> {
rd.inCreate();
}, "写1").start();
new Thread(() -> {
rd.inCreate();
}, "写2").start(); //==========读读不互斥
new Thread(() -> {
rd.out();
}, "读1").start();
new Thread(() -> {
rd.out();
}, "读2").start();
}
}
锁降级
写线程获取写锁后可以获取读锁,然后释放写锁,这样写锁变成了读锁,从而实现锁降级。
注:锁降级之后,写锁不会直接降级成读锁,不会随着读锁的释放而释放,因此要显示地释放写锁。
用途
用于对数据比较敏感,需要在对数据修改之后,获取到修改后的值,并进行接下来的其他操作。理论上已经会输入依据:“num=1”,实际多线程下没输出,此时可以用锁降级解决。给你个眼神,自己体会
package com.yb.thread.lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock; /**
* @ClassName:LockDegradeDemo
* @Description:锁降级demo
* @Author:chenyb
* @Date:2020/9/26 10:53 下午
* @Versiion:1.0
*/
public class LockDegradeDemo {
private int num = 0;
//读写锁
private ReentrantReadWriteLock readWriteLOck = new ReentrantReadWriteLock();
Lock readLock = readWriteLOck.readLock();
Lock writeLock = readWriteLOck.writeLock(); public void doSomething() {
//写锁
writeLock.lock();
//读锁
readLock.lock();
try {
num++;
} finally {
//释放写锁
writeLock.unlock();
}
//模拟其他复杂操作
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
if (num == 1) {
System.out.println("num=" + num);
} else {
System.out.println(num);
}
} finally {
//释放度锁
readLock.unlock();
}
} public static void main(String[] args) {
LockDegradeDemo ld = new LockDegradeDemo();
for (int i = 0; i < 4; i++) {
new Thread(() -> {
ld.doSomething();
}).start();
}
}
}
锁升级?
注:从图可以看出,线程卡着,验证不存在先读后写,从而不存在锁升级这种说法
StampedLock锁
简介
一般应用,都是读多写少,ReentrantReadWriteLock,因为读写互斥,所以读时阻塞写,性能提不上去。可能会使写线程饥饿
特点
- 不可重入:一个线程已经持有写锁,再去获取写锁的话,就会造成死锁
- 支持锁升级、降级
- 可以乐观读也可以悲观读
- 使用有限次自旋,增加锁获得的几率,避免上下文切换带来的开销,乐观读不阻塞写操作,悲观读,阻塞写
优点
相比于ReentrantReadWriteLock,吞吐量大幅提升
缺点
- api复杂,容易用错
- 实现原理相比于ReentrantReadWriteLock复杂的多
demo
package com.yb.thread.lock; import java.util.concurrent.locks.StampedLock; /**
* @ClassName:StampedLockDemo
* @Description:官方例子
* @Author:chenyb
* @Date:2020/9/26 11:37 下午
* @Versiion:1.0
*/
public class StampedLockDemo {
//成员变量
private double x, y;
//锁实例
private final StampedLock sl = new StampedLock(); //排它锁-写锁(writeLock)
void move(double deltaX, double deltaY) {
long stamp = sl.writeLock();
try {
x += deltaX;
y += deltaY;
} finally {
sl.unlockWrite(stamp);
}
} //乐观读锁
double distanceFromOrigin() {
//尝试获取乐观锁1
long stam = sl.tryOptimisticRead();
//将全部变量拷贝到方法体栈内2
double currentX = x, currentY = y;
//检查在1获取到读锁票据后,锁有没被其他写线程排他性抢占3
if (!sl.validate(stam)) {
//如果被抢占则获取一个共享读锁(悲观获取)4
stam = sl.readLock();
try {
//将全部变量拷贝到方法体栈内5
currentX = x;
currentY = y;
} finally {
//释放共享读锁6
sl.unlockRead(stam);
}
}
//返回计算结果7
return Math.sqrt(currentX * currentX + currentY * currentY);
} //使用悲观锁获取读锁,并尝试转换为写锁
void moveIfAtOrigin(double newX, double newY) {
//这里可以使用乐观读锁替换1
long stamp = sl.readLock();
try {
//如果当前点远点则移动2
while (x == 0.0 && y == 0.0) {
//尝试将获取的读锁升级为写锁3
long ws = sl.tryConvertToWriteLock(stamp);
//升级成功后,则更新票据,并设置坐标值,然后退出循环4
if (ws != 0L) {
stamp = ws;
x = newX;
y = newY;
break;
} else {
//读锁升级写锁失败则释放读锁,显示获取独占写锁,然后循环重试5
sl.unlockRead(stamp);
stamp = sl.writeLock();
}
}
} finally {
//释放锁6
sl.unlock(stamp);
}
}
}
生产者消费者模型
Consumer.java
package com.yb.thread.communication; /**
* 消费者
*/
public class Consumer implements Runnable {
private Medium medium; public Consumer(Medium medium) {
this.medium = medium;
} @Override
public void run() {
while (true) {
medium.take();
}
}
}
Producer.java
package com.yb.thread.communication; /**
* 生产者
*/
public class Producer implements Runnable {
private Medium medium; public Producer(Medium medium) {
this.medium = medium;
} @Override
public void run() {
while (true) {
medium.put();
}
}
}
Medium.java
package com.yb.thread.communication; /**
* 中间商
*/
public class Medium {
//生产个数
private int num = 0;
//最多生产数
private static final int TOTAL = 20; /**
* 接受生产数据
*/
public synchronized void put() {
//判断当前库存,是否最大库存容量
//如果不是,生产完成之后,通知消费者消费
//如果是,通知生产者进行等待
if (num < TOTAL) {
System.out.println("新增库存--------当前库存" + ++num);
//唤醒所有线程
notifyAll();
} else {
try {
System.out.println("新增库存-----库存已满" + num);
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} /**
* 获取消费数据
*/
public synchronized void take() {
//判断当前库存是否不足
//如果充足,在消费完成之后,通知生产者进行生产
//如果不足,通知消费者暂停消费
if (num > 0) {
System.out.println("消费库存-------当前库存容量" + --num);
//唤醒所有线程
notifyAll();
} else {
System.out.println("消费库存--------库存不足" + num);
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
测试
管道流通信
以内存为媒介,用于线程之间的数据传输
面向字节:PipedOutputStream、PipedInputStream
面向字符:PipedReader、PipedWriter
Reader.java
package com.yb.thread.communication.demo; import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PipedInputStream;
import java.util.stream.Collectors; /**
* @ClassName:Reader
* @Description:TODO
* @Author:chenyb
* @Date:2020/9/27 10:22 下午
* @Versiion:1.0
*/
public class Reader implements Runnable{
private PipedInputStream pipedInputStream;
public Reader(PipedInputStream pipedInputStream){
this.pipedInputStream=pipedInputStream;
}
@Override
public void run() {
if (pipedInputStream!=null){
String collect = new BufferedReader(new InputStreamReader(pipedInputStream)).lines().collect(Collectors.joining("\n"));
System.out.println(collect);
}
//关闭流
try {
pipedInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Main.java
package com.yb.thread.communication.demo; import java.io.*; /**
* @ClassName:Main
* @Description:TODO
* @Author:chenyb
* @Date:2020/9/27 10:22 下午
* @Versiion:1.0
*/
public class Main {
public static void main(String[] args) {
PipedInputStream pipedInputStream = new PipedInputStream();
PipedOutputStream pipedOutputStream = new PipedOutputStream();
try {
pipedOutputStream.connect(pipedInputStream);
} catch (IOException e) {
e.printStackTrace();
}
new Thread(new Reader(pipedInputStream)).start();
BufferedReader bufferedReader = null;
try {
bufferedReader = new BufferedReader(new InputStreamReader(System.in));
pipedOutputStream.write(bufferedReader.readLine().getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
pipedOutputStream.close();
if (bufferedReader!=null){
bufferedReader.close();
}
} catch (IOException e) {
e.printStackTrace();
} }
}
}
测试
Thread.join
线程A执行一半,需要数据,这个数据需要线程B去执行修改,B修改完成后,A才继续操作
演示
ThreadLocal
线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构。
1、ThreadLocal.get: 获取ThreadLocal中当前线程共享变量的值。
2、ThreadLocal.set: 设置ThreadLocal中当前线程共享变量的值。
3、ThreadLocal.remove: 移除ThreadLocal中当前线程共享变量的值。
4、ThreadLocal.initialValue: ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值。
原子类
概念
对多线程访问同一个变量,我们需要加锁,而锁是比较消耗性能的,JDK1.5之后,新增的原子操作类提供了一种用法简单、性能高效、线程安全地更新一个变量的方式,这些类同样位于JUC包下的atomic包下,发展到JDK1.8,该包下共有17个类,囊括了原子更新基本类型、原子更新数组、原子更新属性、原子更新引用。
1.8新增的原子类
- DoubleAccumulator
- DoubleAdder
- LongAccumulator
- LongAdder
- Striped64
原子更新基本类型
JDK1.8之前有以下几个
- AtomicBoolean
- AtomicInteger
- AtomicLong
- DoubleAccumulator
- DoubleAdder
- LongAccumulator
- LongAdder
大致3类
- 元老级的原子更新,方法几乎一模一样:AtomicBoolean、AtomicInteger、AtomicLong
- 对Double、Long原子更新性能进行优化提升:DoubleAdder、LongAdder
- 支持自定义运算:DoubleAccumulator、LongAccumulator
演示
元老级
自定义运算
原子更新数组
JDK1.8之前大概有以下几个
- AtomicIntegerArray
- AtomicLongArray
- AtomicReferenceArray
原子更新属性
- AtomicIntegerFieldUpdater
- AtomicLongFieldUpdater
- AtomicStampedReference
- AtomicReferenceFieldUpdater
原子更新引用
- AtomicReference:用于对引用的原子更新
- AtomicMarkableReference:带版本戳的原子引用类型,版本戳为boolean类型
- AtomicStampedReference:带版本戳的原子引用类型,版本戳为int类型
容器
同步容器
Vector、HashTable:JDK提供的同步容器类
Collections.SynchronizedXXX:对相应容器进行包装
缺点
在单独使用里面的方法的时候,可以保证线程安全,但是,复合操作需要额外加锁来保证线程安全,使用Iterator迭代容器或使用for-each遍历容器,在迭代过程中修改容器会抛ConcurrentModificationException异常。想要避免出现这个异常,就必须在迭代过程持有容器的锁。但是若容器较大,则迭代的时间也会较长。那么需要访问该容器的其他线程将会长时间等待。从而极大降低性能。
若不希望在迭代期间对容器加锁,可以使用“克隆”容器的方式。使用线程封闭,由于其他线程不会对容器进行修改,可以避免ConcurrentModificationException。但是在创建副本的时候,存在较大性能开销。toString、hashCode、equalse、containsAll、removeAll、retainAll等方法都会隐式的Iterate,也即可能抛出ConcurrentModificationException。
package com.yb.thread.container; import java.util.Iterator;
import java.util.Vector; /**
* @ClassName:VectorDemo
* @Description:TODO
* @Author:chenyb
* @Date:2020/9/29 9:35 下午
* @Versiion:1.0
*/
public class VectorDemo {
public static void main(String[] args) {
Vector<String> strings = new Vector<>();
for (int i = 0; i <1000 ; i++) {
strings.add("demo"+i);
}
//错误遍历
// strings.forEach(e->{
// if (e.equals("demo3")){
// strings.remove(e);
// }
// System.out.println(e);
// }); //正确迭代---->单线程
// Iterator<String> iterator = strings.iterator();
// while (iterator.hasNext()){
// String next = iterator.next();
// if (next.equals("demo3")){
// iterator.remove();
// }
// System.out.println(next);
// } //正确迭代--->多线程
Iterator<String> iterator = strings.iterator();
for (int i = 0; i < 4; i++) {
new Thread(()->{
synchronized (iterator){
while (iterator.hasNext()){
String next = iterator.next();
if (next.equals("demo3")){
iterator.remove();
}
}
}
}).start();
}
}
}
并发容器
CopyOnWrite、Concurrent、BlockingQueue:根据具体场景进行设计,尽量避免使用锁,提高容器的并发访问性。
ConcurrentBlockingQueue:基于queue实现的FIFO的队列。队列为空,去操作会被阻塞
ConcurrentLinkedQueue:队列为空,取得时候就直接返回空
package com.yb.thread.container; import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList; /**
* @ClassName:Demo
* @Description:TODO
* @Author:chenyb
* @Date:2020/9/29 9:50 下午
* @Versiion:1.0
*/
public class Demo {
public static void main(String[] args) {
CopyOnWriteArrayList<String> strings=new CopyOnWriteArrayList<>();
for (int i = 0; i < 1000; i++) {
strings.add("demo"+i);
}
//正常操作--->单线程
// strings.forEach(e->{
// if (e.equals("demo2")){
// strings.remove(e);
// }
// }); //错误操作,不支持迭代器移除元素,直接抛异常
// Iterator<String> iterator = strings.iterator();
// while (iterator.hasNext()){
// String next = iterator.next();
// if (next.equals("demo2")){
// iterator.remove();
// }
// } //正常操作--->多线程
for (int i = 0; i < 4; i++) {
new Thread(()->{
strings.forEach(e -> {
if (e.equals("demo2")) {
strings.remove(e);
}
});
}).start();
}
}
}
LinkedBlockingQueue
可以作为生产者消费者的中间商(使用put、take)。
package com.yb.thread.container; import java.util.concurrent.LinkedBlockingDeque; /**
* @ClassName:Demo2
* @Description:TODO
* @Author:chenyb
* @Date:2020/9/29 10:05 下午
* @Versiion:1.0
*/
public class Demo2 {
public static void main(String[] args) {
LinkedBlockingDeque<String> strings = new LinkedBlockingDeque<>();
//添加元素,3种方式
strings.add("陈彦斌"); //队列满的时候,会抛异常
strings.offer("陈彦斌"); //如果队列满了,直接入队失败
try {
strings.put("陈彦斌"); //队列满,进入阻塞状态
} catch (InterruptedException e) {
e.printStackTrace();
} //从队列中取元素,3种方式
String remove = strings.remove(); //会抛出异常
strings.poll(); //在队列为空的时候,直接返回null
try {
strings.take(); //队列为空的时候,会进入等待状态
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
并发工具类
CountDownLatch
- await():进入等待状态
- countDown:计算器减一
应用场景
- 启动三个线程计算,需要对结果进行累加
package com.yb.thread.tool; import java.util.concurrent.CountDownLatch; /**
* @ClassName:CountDownLatchDemo
* @Description:TODO
* @Author:chenyb
* @Date:2020/9/29 10:26 下午
* @Versiion:1.0
*/
public class CountDownLatchDemo {
public static void main(String[] args) {
//模拟场景,学校比较,800米,跑完之后,有跨栏
//需要先将800米跑完,在布置跨栏,要不然跑800米的选手会被累死
CountDownLatch countDownLatch = new CountDownLatch(8);
new Thread(()->{
try {
countDownLatch.await();
System.out.println("800米比赛结束,准备清跑道,并进行跨栏比赛");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
for (int i = 0; i < 8; i++) {
int finalI = i;
new Thread(()->{
try {
Thread.sleep(finalI *1000L);
System.out.println(Thread.currentThread().getName()+",到达终点");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
countDownLatch.countDown();
}
}).start();
}
}
}
CyclicBarrier
允许一组线程相互等待达到一个公共的障碍点,之后继续执行
区别
- CountDownLatch一般用于某个线程等待若干个其他线程执行完任务之后,他才执行:不可重复使用
- CyclicBarrier一般用于一组线程相互等待至某个状态,然后这一组线程再同时执行:可重用
package com.yb.thread.tool; import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier; /**
* @ClassName:CyclicBarrierDemo
* @Description:TODO
* @Author:chenyb
* @Date:2020/9/29 10:42 下午
* @Versiion:1.0
*/
public class CyclicBarrierDemo {
public static void main(String[] args) {
//模拟场景:学校800米跑步,等到所有选手全部到齐后,一直跑
CyclicBarrier cyclicBarrier=new CyclicBarrier(8);
for (int i = 0; i < 8; i++) {
int finalI = i;
new Thread(()->{
try {
Thread.sleep(finalI *1000L);
System.out.println(Thread.currentThread().getName()+",准备就绪");
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("选手已到齐,开始比赛");
}).start();
}
}
}
Semaphore(信号量)
控制线程并发数量
应用场景
- 接口限流
package com.yb.thread.tool; import java.util.concurrent.Semaphore; /**
* @ClassName:SemaphoreDemo
* @Description:TODO
* @Author:chenyb
* @Date:2020/9/29 11:11 下午
* @Versiion:1.0
*/
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(8);
for (int i = 0; i < 20; i++) {
new Thread(() -> { try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + ",开始执行");
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放
semaphore.release();
}
}).start();
}
}
}
Exchange
它提供一个同步点,在这个同步点两个线程可以交换彼此的数据(成对)。
应用场景
- 交换数据
package com.yb.thread.tool; import java.util.concurrent.Exchanger; /**
* @ClassName:ExchangerDemo
* @Description:TODO
* @Author:chenyb
* @Date:2020/9/29 11:21 下午
* @Versiion:1.0
*/
public class ExchangerDemo {
public static void main(String[] args) {
Exchanger<String> stringExchanger=new Exchanger<>();
String str1="陈彦斌";
String str2="ybchen";
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"--------------初始值:"+str1);
try {
String exchange = stringExchanger.exchange(str1);
System.out.println(Thread.currentThread().getName()+"--------------交换:"+exchange);
} catch (InterruptedException e) {
e.printStackTrace();
}
},"线程A").start();
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"--------------初始值:"+str2);
try {
String exchange = stringExchanger.exchange(str2);
System.out.println(Thread.currentThread().getName()+"--------------交换:"+exchange);
} catch (InterruptedException e) {
e.printStackTrace();
}
},"线程B").start();
}
}
Java 多线程并发编程的更多相关文章
- Java 多线程并发编程一览笔录
Java 多线程并发编程一览笔录 知识体系图: 1.线程是什么? 线程是进程中独立运行的子任务. 2.创建线程的方式 方式一:将类声明为 Thread 的子类.该子类应重写 Thread 类的 run ...
- 【收藏】Java多线程/并发编程大合集
(一).[Java并发编程]并发编程大合集-兰亭风雨 [Java并发编程]实现多线程的两种方法 [Java并发编程]线程的中断 [Java并发编程]正确挂起.恢复.终止线程 [ ...
- java多线程并发编程与CPU时钟分配小议
我们先来研究下JAVA的多线程的并发编程和CPU时钟振荡的关系吧 老规矩,先科普 我们的操作系统在DOS以前都是单任务的 什么是单任务呢?就是一次只能做一件事 你复制文件的时候,就不能重命名了 那么现 ...
- Java基础系列篇:JAVA多线程 并发编程
一:为什么要用多线程: 我相信所有的东西都是以实际使用价值而去学习的,没有实际价值的学习,学了没用,没用就不会学的好. 多线程也是一样,以前学习java并没有觉得多线程有多了不起,不用多线程我一样可以 ...
- Java多线程并发编程/锁的理解
一.前言 最近项目遇到多线程并发的情景(并发抢单&恢复库存并行),代码在正常情况下运行没有什么问题,在高并发压测下会出现:库存超发/总库存与sku库存对不上等各种问题. 在运用了 限流/加锁等 ...
- java多线程并发编程
Executor框架 Executor框架是指java 5中引入的一系列并发库中与executor相关的一些功能类,其中包括线程池,Executor,Executors,ExecutorService ...
- Java多线程并发编程一览笔录
线程是什么? 线程是进程中独立运行的子任务. 创建线程的方式 方式一:将类声明为 Thread 的子类.该子类应重写 Thread 类的 run 方法 方式二:声明实现 Runnable 接口的类.该 ...
- Java 多线程并发编程面试笔录一览
知识体系图: 1.线程是什么? 线程是进程中独立运行的子任务. 2.创建线程的方式 方式一:将类声明为 Thread 的子类.该子类应重写 Thread 类的 run 方法 方式二:声明实现 Runn ...
- java多线程 并发 编程
转自:http://www.cnblogs.com/luxiaoxun/p/3870265.html 一.多线程的优缺点 多线程的优点: 1)资源利用率更好 2)程序设计在某些情况下更简单 3)程序响 ...
随机推荐
- 初识ABP vNext(7):vue身份认证管理&租户管理
Tips:本篇已加入系列文章阅读目录,可点击查看更多相关文章. 目录 前言 开始 按钮级权限 身份认证管理 R/U权限 权限刷新 租户管理 租户切换 效果 最后 前言 上一篇介绍了vue+ABP国际化 ...
- win10找不到wifi
禁用->启用 就能用了.
- Elasticsearch7.6 集群部署、集群认证及使用、数据备份
window 环境部署集群 注意:window下载解压elasticsearch一定需要解压多次.例如搭建的3节点的,需要解压3次,防止生成 cluster UUID 一致导致只能看到一个节点 1.e ...
- Photogrammetry and Game
https://skulltheatre.wordpress.com/2013/02/11/photogrammetry-in-video-games-frequently-asked-questio ...
- 【jmespath】—3. 进阶 Object Projections
继续,来看Object Projections. 一.Object Projections 上面说的是列表投影,只适用于列表.那么对于json对象,可以用对象投影. 投影最终返回的仍然是个列表,只不过 ...
- 在Win10上安装Apache2.44
下载地址:https://www.apachelounge.com/download/VS16/binaries/httpd-2.4.41-win64-VS16.zip 如果以上地址失效请到 http ...
- nodeJS 下载与安装,环境配置
1.什么是nodeJs: 简单的说 Node.js 就是运行在服务端的 JavaScript. Node.js 是一个基于Chrome JavaScript 运行时建立的一个平台. Node.js是一 ...
- 抽象工厂模式详解 —— head first 设计模式
项目实例 假设你有一家 pizza 店,你有很多种 pizza,要在系统中显示你所有 pizza 种类.实现这个功能并不难,使用普通方式实现: public class PizzaStore { Pi ...
- d3力导图绘制节点间多条关系平行线的方法
之前用d3做了多条线之间的绘图是曲线表示的,现在产品要求改成平行线的样式,经过在网上的调研和自己的尝试,实践出一个可用的方法,分享给大家,先展示下结果: 事先声明,本方法是在以下参考网站上进行的结合和 ...
- linux下redis安装部署
1.获取redis资源 进官网下载https://redis.io/download最新版本后将文件移到linux环境中 或者直接wget http://download.redis.io/relea ...