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)程序响 ...
随机推荐
- 阿里巴巴Java开发手册1.4.0
链接:https://pan.baidu.com/s/16kKzcRcu20SMDdydMm9ZUw 提取码:p9ef
- update 字符串拼接
UPDATE store SET food_ordering =1,self_pickup_remark = CONCAT('self pick up notes:',store_code,short ...
- Qt 绘图(QBitmap,QPixmap,QImage,QPicture)
QPainter绘图绘图设备(QPixmap,QImage,QBitmap,QPicture) 重写绘图事件,虚函数 如果窗口绘图,必须放在绘图事件里实现 绘图事件内部自动调用,窗口需要重绘的时候,状 ...
- JDK 8 新特性之函数式编程 → Stream API
开心一刻 今天和朋友们去K歌,看着这群年轻人一个个唱的贼嗨,不禁感慨道:年轻真好啊! 想到自己年轻的时候,那也是拿着麦克风不放的人 现在的我没那激情了,只喜欢坐在角落里,默默的听着他们唱,就连旁边的妹 ...
- oracle的system登不了
(密码对的,密码错直接就是被拒了) 这个一直弹出改密码 但是改了点[确定],又说 oracle改system密码 [oracle@localhost ~]$ sqlplus / as sysdba S ...
- Android开发之制作圆形头像自定义View,直接引用工具类,加快开发速度。带有源代码学习
作者:程序员小冰,CSDN博客:http://blog.csdn.net/qq_21376985 QQ986945193 博客园主页:http://www.cnblogs.com/mcxiaobing ...
- Android Studio 如何导出和导入自己的常用设置,避免重复制造轮子。加快开发速度
Android Studio 如何导出和导入自己的常用设置,避免重复制造轮子.加快开发速度 作者:程序员小冰,CSDN博客:http://blog.csdn.net/qq_21376985 在使用 A ...
- 使用Android的硬件缩放技术优化执行效率
Unity3D研究院之使用Android的硬件缩放技术优化执行效率 http://www.xuanyusong.com/archives/3205 Android中GLSurfaceView在横竖屏切 ...
- 1008 Elevator (20 分)(模拟)
The highest building in our city has only one elevator. A request list is made up with N positive nu ...
- 对韩峰著《SQL优化最佳实践》P7 案例的质疑
事先申明下,我的DB环境是Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit Production,如果与作者环境不同而 ...