目录
3、(代码)lock目录下:LSaleTicket.java
4、(代码)sync目录下:ThreadDemo1.java
5、(代码)lock目录下:ThreadDemo2.java
6、(代码)lock目录下:ThreadDemo3.java
7、(代码)lock目录下:ThreadDemo4.java
十五、JUC-集合线程安全-ArrayList线程不安全和解决方案(一)
十七、JUC-集合线程安全-HashSet和HashMap线程不安全
十八、JUC-多线程锁-Synchronized锁的八种情况
3、(代码)lock目录下:LSaleTicket.java
9、(代码)sync目录下:SyncLockDemo.java
二十四、JUC-Callable接口-FutureTask概述和原理
12、(代码)juc目录下:CountDownLatchDemo.java
13、(代码)juc目录下:CyclicBarrierDemo.java
14、(代码)juc目录下:SemaphoreDemo.java
15、(代码)readwrite目录下:ReadWriteLockDemo.java
16、(代码)readwrite目录下:Demo1.java
17、(代码)queue目录下:BlockingQueueDemo.java
18、(代码)pool目录下:ThreadPoolDemo1.java
19、(代码)pool目录下:ThreadPoolDemo2.java
20、(代码)completable目录下:CompletableFutureDemo.java
一、JUC课程介绍
JUC是java.util.concurrent也就是java并发编程包中的实用的工具类,这个工具类在企业开发中占据着举足轻重的地位
二、JUC-JUC概述和进程线程概念(1)
JDK中文文档:https://tool.oschina.net/apidocs/apidoc?api=jdk-zh
1、JUC简介
在Java中,线程部分是一个重点,本篇文章说的JUC也是关于线程的。JUC就是java.util.concurrent工具包的简称。这是一个处理线程的工具包,JDK1.5开始出现的。
2、进程与线程
比如打开一个软件,那在我的电脑中就会实际占用一个空间,那就表示现在就开启了一个进程。
我现在打开一个进程,一个进程中会有很多的线程,多线程在一个进程中进行
3、wait和sleep的区别
三、JUC-JUC概述和进程线程概念(2)
理解定义:
四、JUC-JUC概述和进程线程概念(3)
用户线程和守护线程
用户线程:平时用到的普通线程,自定义线程
守护线程:运行在后台,是一种特殊的线程,比如垃圾回收
当主线程结束后,用户线程还在运行,JVM存活
如果没有用户线程,都是守护线程,JVM结束
1、(代码)Main.java
package com.nanjing;
/**
* 演示用户线程和守护线程
* 用户线程:自定义线程 主线程结束了,用户线程还在运行,jvm存活
* 守护线程:比如垃圾回收 没有用户线程了,都是守护线程,jvm结束
*
* @author xizheng
* @date 2023-03-16 09:02:26
*/
public class Main {
public static void main(String[] args) {
//System.out.println("Hello world!");
Thread aa = new Thread(() -> {
//如果是true就是守护线程,如果是false就是用户线程
System.out.println(Thread.currentThread().getName() + "::" + Thread.currentThread().isDaemon());
while (true) {
}
}, "aa");
//设置守护线程
aa.setDaemon(true);
aa.start();
System.out.println(Thread.currentThread().getName() + " over");
}
}
五、JUC-Synchronized复习和案例分析
synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
1.修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
2.修饰一个方法,被修饰的方法称为同步方法,起作用的范围是整个方法,作用的对象是调用这个方法的对象;
虽然可以使用synchronized来定义方法,但synchronized并不属于方法定义的一部分,因此,synchronized关键字不能被继承。如果在父类中的某个方法使用了synchronized关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显示地在子类的这个方法中加上synchronized关键字才可以。当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步了。
3、修饰一个静态的方法,起作用的范围是整个静态方法,作用的对象是这个类的所有对象;
4、修饰一个类,其作用的范围是synchronized后面括号括起来的部分,作用的对象是这个类的所有对象。
六、JUC-Synchronized实现案例
2、(代码)sync目录下:SaleTicket.java
package com.nanjing.sync;
/**
* 卖票
*
* @author xizheng
* @date 2023-03-16 09:41:20
*/
//第一步 创建资源类,定义属性和操作方法
class Ticket {
//票数
private int number = 30;
//操作方法:卖票
public synchronized void sale() {
//判断:是否有票
if(number > 0) {
System.out.println(Thread.currentThread().getName()+" : 卖出: " +(number--)+" 剩下: "+number);
}
}
}
public class SaleTicket {
//第二步 创建多个线程,调用资源类的操作方法
public static void main(String[] args) {
//创建Ticket对象
Ticket ticket = new Ticket();
//创建三个线程
new Thread(new Runnable() {
@Override
public void run() {
//调用卖票方法
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}
},"AA").start();
new Thread(new Runnable() {
@Override
public void run() {
//调用卖票方法
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}
},"BB").start();
new Thread(new Runnable() {
@Override
public void run() {
//调用卖票方法
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}
},"CC").start();
}
}
七、JUC-Lock接口概述和实现案例
Lock锁实现提供了比使用同步方法和语句可以获得的更广泛的所操作。它们允许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对象。Lock提供了比synchronized更多的功能。
Lock与Synchronized的区别:
Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问;
Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
3、(代码)lock目录下:LSaleTicket.java
我调用start()方法,我这个线程创建了吗?是不一定的,可能等一会创建,也可能马上创建。
源码调用start0()方法,进入到方法中
native关键字,java代码就无能为力了,其部分就是操作系统完成,最终也就是由操作系统决定的,至于什么时候创建由操作系统决定
package com.nanjing.lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Lock锁实现卖票
*
* @author xizheng
* @date 2023-03-16 09:36:21
*/
//第一步 创建资源类,定义属性和操作方法
class LTicket {
//票数量
private int number = 30;
//创建可重入锁
private final ReentrantLock lock = new ReentrantLock(true);
//卖票方法
public void sale() {
//上锁
lock.lock();
try {
//判断是否有票
if(number > 0) {
System.out.println(Thread.currentThread().getName()+" : 卖出: " +(number--)+" 剩下: "+number);
}
}finally {
//解锁
lock.unlock();
}
}
}
public class LSaleTicket {
//第二步 创建多个线程,调用资源类的操作方法
//创建三个线程
public static void main(String[] args) {
LTicket ticket = new LTicket();
new Thread(()-> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
},"AA").start();
new Thread(()-> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
},"BB").start();
new Thread(()-> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
},"CC").start();
}
}
八、JUC-线程间通信-概述和案例分析
小结(重点)
Lock和synchronized有以下几点不同:
1.Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
2、synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要finally块中释放锁;
3.Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
4、通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
5、Lock可以提高多个线程进行读操作的效率。
在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。
线程间通信
九、JUC-线程间通信-Synchronized实现案例
4、(代码)sync目录下:ThreadDemo1.java
十、JUC-线程间通信-虚假唤醒问题
使用while替代if条件判断,不管什么时候唤醒,while的条件语句都会执行一次,而if不会执行两次
package com.nanjing.sync;
/**
* 线程间通信
*
* @author xizheng
* @date 2023-03-16 13:45:00
*/
//第一步 创建资源类,定义属性和操作方法
class Share {
//初始值
private int number = 0;
//+1的方法
public synchronized void incr() throws InterruptedException {
//第二步 判断 干活 通知
while (number != 0) { //判断number值是否是0,如果不是0,等待
this.wait();//在哪里睡,就在哪里醒
}
//如果number值是0,就+1操作
number++;
System.out.println(Thread.currentThread().getName()+" :: "+number);
//通知其他线程
this.notifyAll();
}
//-1的方法
public synchronized void decr() throws InterruptedException {
//判断
while (number != 1) {
this.wait();
}
//干活
number--;
System.out.println(Thread.currentThread().getName()+" :: "+number);
//通知其他线程
this.notifyAll();
}
}
public class ThreadDemo1 {
//第三步 创建多个线程,调用资源类的操作方法
public static void main(String[] args) {
Share share = new Share();
//创建线程
new Thread(()->{
for (int i = 1; i <= 10; i++) {
try {
share.incr();//+1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"AA").start();
new Thread(()->{
for (int i = 1; i <= 10; i++) {
try {
share.decr();//-1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"BB").start();
new Thread(()->{
for (int i = 1; i <= 10; i++) {
try {
share.incr();//+1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"CC").start();
new Thread(()->{
for (int i = 1; i <= 10; i++) {
try {
share.decr();//-1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"DD").start();
}
}
十一、JUC-线程间通信-Lock实现案例
5、(代码)lock目录下:ThreadDemo2.java
package com.nanjing.lock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 线程间通信-Lock实现案例
*
* @author xizheng
* @date 2023-03-16 14:18:34
*/
//第一步 创建资源类,定义属性和操作方法
class Share {
private int number = 0;
//创建Lock
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
//+1
public void incr() throws InterruptedException {
//上锁
lock.lock();
try {
//判断
while (number !=0) {
condition.await();
}
//干活
number++;
System.out.println(Thread.currentThread().getName()+" :: "+number);
//通知
condition.signalAll();
}finally {
//解锁
lock.unlock();
}
}
//-1
public void decr() throws InterruptedException {
lock.lock();
try {
while (number != 1) {
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+" :: "+number);
condition.signalAll();
}finally {
lock.unlock();
}
}
}
public class ThreadDemo2 {
public static void main(String[] args) {
Share share = new Share();
new Thread(()->{
for (int i = 1; i <= 10; i++) {
try {
share.incr();//+1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"AA").start();
new Thread(()->{
for (int i = 1; i <= 10; i++) {
try {
share.decr();//-1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"BB").start();
new Thread(()->{
for (int i = 1; i <= 10; i++) {
try {
share.incr();//+1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"CC").start();
new Thread(()->{
for (int i = 1; i <= 10; i++) {
try {
share.decr();//-1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"DD").start();
}
}
十二、JUC-线程间定制化通信-案例分析
十三、JUC-线程间定制化通信-案例实现
6、(代码)lock目录下:ThreadDemo3.java
package com.nanjing.lock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 线程间定制化通信
*
* @author xizheng
* @date 2023-03-16 14:32:21
*/
//第一步 创建资源类
class ShareResource {
//定义标志位
private int flag = 1; // 1 AA 2 BB 3 CC
//创建Lock锁
private Lock lock = new ReentrantLock();
//创建三个condition
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();
//打印5次,参数第几轮
public void print5(int loop) throws InterruptedException {
//上锁
lock.lock();
try {
//判断
while (flag != 1) {
//等待
c1.await();
}
//干活
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName()+" :: "+i+" : 轮数: "+loop);
}
//通知
flag = 2;//修改标志位 2
c2.signal();//通知BB线程
}finally {
//释放锁
lock.unlock();
}
}
//打印10次,参数第几轮
public void print10(int loop) throws InterruptedException {
lock.lock();
try {
//判断
while (flag != 2) {
//等待
c2.await();
}
//干活
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName()+" :: "+i+" : 轮数: "+loop);
}
//修改标志位
flag = 3;
//通知CC线程
c3.signal();
}finally {
//释放锁
lock.unlock();
}
}
//打印15次,参数第几轮
public void print15(int loop) throws InterruptedException {
lock.lock();
try {
//判断
while (flag != 3) {
//等待
c3.await();
}
//干活
for (int i = 1; i <= 15; i++) {
System.out.println(Thread.currentThread().getName()+" :: "+i+" : 轮数: "+loop);
}
//修改标志位
flag = 1;
//通知CC线程
c1.signal();
}finally {
//释放锁
lock.unlock();
}
}
}
public class ThreadDemo3 {
public static void main(String[] args) {
ShareResource shareResource = new ShareResource();
new Thread(()->{
for (int i = 1; i <= 10; i++) {
try {
shareResource.print5(i);
}catch (InterruptedException e) {
e.printStackTrace();
}
}
},"AA").start();
new Thread(()->{
for (int i = 1; i <= 10; i++) {
try {
shareResource.print10(i);
}catch (InterruptedException e) {
e.printStackTrace();
}
}
},"BB").start();
new Thread(()->{
for (int i = 1; i <= 10; i++) {
try {
shareResource.print15(i);
}catch (InterruptedException e) {
e.printStackTrace();
}
}
},"CC").start();
}
}
十四、JUC-集合线程安全-异常演示
7、(代码)lock目录下:ThreadDemo4.java
package com.nanjing.lock;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* 集合线程安全-异常演示
* List集合线程不安全
*
* @author xizheng
* @date 2023-03-16 14:49:56
*/
public class ThreadDemo4 {
public static void main(String[] args) {
//创建ArrayList集合
List<String> list = new ArrayList<>();
for (int i = 0; i < 30; i++) {
new Thread(()->{
//向集合添加内容
list.add(UUID.randomUUID().toString().substring(0,8));
//从集合获取内容
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
十五、JUC-集合线程安全-ArrayList线程不安全和解决方案(一)
写时复制源码:
public boolean add(E e) {
synchronized (lock) {
Object[] es = getArray();
int len = es.length;
es = Arrays.copyOf(es, len + 1);
es[len] = e;
setArray(es);
return true;
}
}
案例:
package com.nanjing.lock;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* 集合线程安全-异常演示
* List集合线程不安全
*
* @author xizheng
* @date 2023-03-16 14:49:56
*/
public class ThreadDemo4 {
public static void main(String[] args) {
//创建ArrayList集合
//List<String> list = new ArrayList<>();
// Vector解决
// List<String> list = new Vector<>();
// Collections解决
// List<String> list = Collections.synchronizedList(new ArrayList<>());
List<String> list = new CopyOnWriteArrayList<>();//写时复制技术
for (int i = 0; i < 100; i++) {
new Thread(()->{
//向集合添加内容
list.add(UUID.randomUUID().toString().substring(0,8));
//从集合获取内容,会出现并发修改异常(java.util.ConcurrentModificationException)
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
十七、JUC-集合线程安全-HashSet和HashMap线程不安全
//演示HashSet
//Set<String> set = new HashSet<>();
// Set<String> set = new CopyOnWriteArraySet<>();
// for (int i = 0; i < 30; i++) {
// new Thread(() -> {
// //向集合添加内容
// set.add(UUID.randomUUID().toString().substring(0, 8));
// //从集合获取内容
// System.out.println(set);
// }, String.valueOf(i)).start();
// }
//演示HashMap
//Map<String, String> map = new HashMap<>();
Map<String, String> map = new ConcurrentHashMap<>();
for (int i = 0; i < 30; i++) {
String key = String.valueOf(i);
new Thread(() -> {
//向集合添加内容
map.put(key,UUID.randomUUID().toString().substring(0, 8));
//从集合获取内容
System.out.println(map);
},String.valueOf(i)).start();
}
十八、JUC-多线程锁-Synchronized锁的八种情况
synchronized实现同步的基础:Java中的每一个对象都可以作为锁。
具体表现为以下3中形式
对于普通同步方法,锁是当前实例对象。
对于静态同步方法,锁是当前类的Class对象。
对于同步方法块,锁是Synchronized括号里配置的对象
8、(代码)sync目录下:Lock_8.java
package com.nanjing.sync;
import java.util.concurrent.TimeUnit;
/**
* 多线程锁-Synchronized锁的八种情况
*
* @author xizheng
* @date 2023-03-16 16:15:45
*/
class Phone {
public static synchronized void sendSMS() throws Exception {
//停留4秒
TimeUnit.SECONDS.sleep(4);
System.out.println("------sendSMS");
}
public static synchronized void sendEmail() throws Exception {
System.out.println("------sendEmail");
}
public void getHello() {
System.out.println("------getHello");
}
}
/**
* @Description: 8锁
*
1 标准访问,先打印短信还是邮件
------sendSMS
------sendEmail
2 停4秒在短信方法内,先打印短信还是邮件
------sendSMS
------sendEmail
3 新增普通的hello方法,是先打短信还是hello
------getHello
------sendSMS
4 现在有两部手机,先打印短信还是邮件
------sendEmail
------sendSMS
5 两个静态同步方法,1部手机,先打印短信还是邮件
------sendSMS
------sendEmail
6 两个静态同步方法,2部手机,先打印短信还是邮件
------sendSMS
------sendEmail
7 1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件
------sendEmail
------sendSMS
8 1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件
------sendEmail
------sendSMS
*/
public class Lock_8 {
public static void main(String[] args) throws Exception {
Phone phone = new Phone();
Phone phone2 = new Phone();
new Thread(() -> {
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
},"AA").start();
Thread.sleep(100);
new Thread(() -> {
try {
phone.sendEmail();
// phone.getHello();
// phone2.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
},"BB").start();
}
}
十九、JUC-多线程锁-公平锁和非公平锁
3、(代码)lock目录下:LSaleTicket.java
二十、JUC-多线程锁-可重入锁(1)
9、(代码)sync目录下:SyncLockDemo.java
package com.nanjing.sync;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 可重入锁(递归锁)
*
* @author xizheng
* @date 2023-03-16 17:02:46
*/
public class SyncLockDemo {
public synchronized void add() {
add();
}
public static void main(String[] args) {
//Lock演示可重入锁
Lock lock = new ReentrantLock();
//创建线程
new Thread(() -> {
try {
//上锁
lock.lock();
System.out.println(Thread.currentThread().getName() + " 外层");
try {
//上锁
lock.lock();
System.out.println(Thread.currentThread().getName() + " 内层");
} finally {
//释放锁
lock.unlock();
}
} finally {
//释放锁
lock.unlock();
}
}, "t1").start();
//创建新线程(如果上面没有释放锁,下面的代码都无法执行了,因为无法获取到锁)
new Thread(() -> {
lock.lock();
System.out.println("aaaa");
lock.unlock();
}, "aa").start();
// new SyncLockDemo().add();//(验证是否是)可重入锁,java.lang.StackOverflowError
// synchronized
Object o = new Object();
new Thread(() -> {
synchronized (o) {
System.out.println(Thread.currentThread().getName() + " 外层");
synchronized (o) {
System.out.println(Thread.currentThread().getName() + " 中层");
synchronized (o) {
System.out.println(Thread.currentThread().getName() + " 内层");
}
}
}
}, "t1").start();
}
}
二十二、JUC-多线程锁-死锁
10、(代码)DeadLock.java
package com.nanjing.sync;
import java.util.concurrent.TimeUnit;
/**
* 演示死锁
*
* @author xizheng
* @date 2023-03-16 17:15:35
*/
public class DeadLock {
//创建两个对象
static Object a = new Object();
static Object b = new Object();
public static void main(String[] args) {
new Thread(()->{
synchronized (a) {
System.out.println(Thread.currentThread().getName()+" 持有锁a,试图获取锁b");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (b) {
System.out.println(Thread.currentThread().getName()+" 获取锁b");
}
}
},"A").start();
new Thread(()->{
synchronized (b) {
System.out.println(Thread.currentThread().getName()+" 持有锁b,试图获取锁a");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (a) {
System.out.println(Thread.currentThread().getName()+" 获取锁a");
}
}
},"B").start();
}
}
二十三、JUC-Callable接口-概述
1、目前我们学习了有两种创建线程的方法-一种是通过创建Thread类,另一种是通过使用Runnable创建线程。但是,Runnable缺少的一项功能是,当线程终止时(即run()完成时),我们无法使线程返回结果。为了支持此功能,Java中提供了Callable接口
2、
二十四、JUC-Callable接口-FutureTask概述和原理
Callable接口的特点如下(重点)
- 为了实现Runnable,需要实现不返回任何内容的run()方法,而对于Callable,需要实现在完成时返回结果的call()方法。
- call()方法可以引发异常,而run()则不能。
- 为实现Callable而必须重写call方法
- 不能直接替换runnable,因为Thread类的构造方法根本没有Callable
二十五、JUC-Callable接口-创建线程
11、(代码)callable目录下:Demo1.java
package com.nanjing.callable;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
* 找一个类,既和Runnable有关系,又和Callable也有关系
* Runnable接口有实现类 FutureTask
* FutureTask构造可以传递Callable
*
* Runnable接口 和 Callable接口
* ① 是否有返回值
* ② 是否抛出异常
* ③ 实现方法名称不同,一个是run方法,一个是call方法
*
* @author xizheng
* @date 2023-03-16 18:08:39
*/
//比较两个接口
//实现Runnable接口
class MyThread1 implements Runnable {
@Override
public void run() {
}
}
//实现Callable
class MyThread2 implements Callable {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName()+" come in callable");
return 200;
}
}
public class Demo1 {
public static void main(String[] args) throws Exception {
//Runnable接口创建线程
new Thread(new MyThread1(),"AA").start();
//Callable接口,报错
// new Thread(new MyThread2(),"BB").start();
//FutureTask(未来任务)
FutureTask<Integer> futureTask1 = new FutureTask<>(new MyThread2());
//lam表达式
FutureTask<Integer> futureTask2 = new FutureTask<>(()->{
System.out.println(Thread.currentThread().getName()+" come in callable");
return 1024;
});
//创建一个线程
new Thread(futureTask2,"lucy").start();
new Thread(futureTask1,"mary").start();
while (!futureTask2.isDone()) {
System.out.println("wait......");
}
//调用FutureTask的get方法
System.out.println(futureTask2.get());
System.out.println(futureTask1.get());
System.out.println(Thread.currentThread().getName()+" come over");
//FutureTask原理 未来任务
/**
* 1、老师上课,口渴了,去买水不合适,讲课线程继续。
* 单开启线程找班上班长帮我买水,把水买回来,需要时候直接get
*
* 2、4个同学, 1同学 1+2...5 , 2同学 10+11+12....50, 3同学 60+61+62, 4同学 100+200
* 第2个同学计算量比较大,
* FutureTask单开启线程给2同学计算,先汇总 1 3 4 ,最后等2同学计算位完成,统一汇总
*
* 3、考试,做会做的题目,最后看不会做的题目
*
* 汇总一次
*
*/
}
}
二十六、JUC-辅助类(CountDownLatch)
减少计数 CountDownLatch
CountDownLatch类可以设置一个计数器,然后通过countDown方法来进行减1的操作,使用await方法等待计数器不大于0,然后继续执行await方法之后的语句。
CountDownLatch主要有两个方法,当一个或多个线程调用await方法时,这些线程会阻塞
其它线程调用countDown方法会将计数器减1(调用countDown方法的线程不会阻塞)
当计数器的值变为0时,因await方法阻塞的线程会被唤醒,继续执行
12、(代码)juc目录下:CountDownLatchDemo.java
package com.nanjing.juc;
import java.util.concurrent.CountDownLatch;
/**
* 演示 CountDownLatch
*
* @author xizheng
* @date 2023-03-16 18:40:09
*/
public class CountDownLatchDemo {
//6个同学陆续离开教室之后,设置初始值
public static void main(String[] args) throws Exception {
//创建CountDownLatch对象,设置初始值
CountDownLatch countDownLatch = new CountDownLatch(6);
//6个同学陆续离开教室之后
for (int i = 1; i <= 4; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" 号同学离开了教室");
//计数 -1
countDownLatch.countDown();
},String.valueOf(i)).start();
}
//等待
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+" 班长锁门走人了");
}
}
二十七、JUC-辅助类(CyclicBarrier)
循环栅栏CyclicBarrier
CyclicBarrier看英文单词可以看出大概就是循环阻塞的意思,在使用中CyclicBarrier的构造方法第一个参数时目标障碍数,每次执行CyclicBarrier一次障碍数会加一,如果达到了目标障碍数,才会执行cyclicBarrier.await()之后的语句。可以将CyclicBarrier理解为加1操作
13、(代码)juc目录下:CyclicBarrierDemo.java
package com.nanjing.juc;
import java.util.concurrent.CyclicBarrier;
/**
* 循环栅栏 CyclicBarrier
* 集齐7颗龙珠就可以召唤神龙
*
* @author xizheng
* @date 2023-03-16 18:51:30
*/
public class CyclicBarrierDemo {
//创建固定值
private static final int NUMBER = 7;
public static void main(String[] args) {
//创建CyclicBarrier
CyclicBarrier cyclicBarrier = new CyclicBarrier(NUMBER,()->{
System.out.println("*****集齐7颗龙珠就可以召唤神龙");
});
//集齐七颗龙珠过程
for (int i = 1; i <= 7; i++) {
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+" 星龙被收集到了");
//等待
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
}
二十八、JUC-辅助类(Semaphore)
Semaphore的构造方法中传入的第一个参数是最大信号量(可以看成最大线程池),每个信号量初始化为一个最多只能分发一个许可证。使用acquire方法获得许可证,release方法释放许可
14、(代码)juc目录下:SemaphoreDemo.java
package com.nanjing.juc;
import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* 信号灯Semaphore
* 6辆汽车,停3个车位
*
* @author xizheng
* @date 2023-03-16 18:58:01
*/
public class SemaphoreDemo {
public static void main(String[] args) {
//创建Semaphore,设置许可数量
Semaphore semaphore = new Semaphore(3);
//模拟6辆汽车
for (int i = 1; i <= 6; i++) {
new Thread(()->{
try {
//抢占
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+" 抢到了车位");
//设置随机停车时间
TimeUnit.SECONDS.sleep(new Random().nextInt(5));
System.out.println(Thread.currentThread().getName()+"------离开了车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放
semaphore.release();
}
},String.valueOf(i)).start();
}
}
}
二十九、JUC-读写锁-概述
乐观锁和悲观锁(重点)
悲观锁:我现在有一个账户,我账户里面目前有1万块钱,那这个时候呢?用悲观锁是怎样的一个过程呢?比如现在第一个人对这个账户进行操作,他在操作的时候呢?对账户首先先上锁,他在上锁之后,那别人就不能再操作账户,别人只能是一个阻塞或者等待的状态,当我这个人操作之后,他要做一件事情,把锁释放,那别人才能继续操作。那别人在操作时候,他把账户首先也是上锁,他操作完之后,再进行解锁。每个人都进行上锁、解锁过程,这个过程就叫做悲观锁。
悲观的好处是什么呢?(效率很低)
能解决并发中的各种问题,它的缺点就是不支持并发操作,你只能一个人一个人进行操作
乐观锁:它是支持并发的,比如说我现在账户里面有1万块钱,这个时候我两个人都可以得到账户中的数据进行操作,但是操作时候呢?乐观锁有一个特点,它需要在你的账户数据中加一个version版本号,而每次每个人都得到版本号,那这个时候呢?比如说我第一个人把账户减1000,第二个人减5000,最终两个人是不是有一个人要提交这个事务。比如说,第一个人把事务提交了,他在提交之后,除了改账户值之外,他还要做一件事情。版本号之前是不是v1.0,他在提交之后,把版本号改为v1.1,而他在改成v1.1之后,有什么特点呢?我另外一个人,在提交之前,先做个比较,比较当前数据版本号和数据库中的版本号是否一致,发现版本号不一致,最终他就会提交失败,这就叫乐观锁。通过版本号进行控制,谁先提交先改版本号,然后另外的人就不能提交。
行锁和表锁(重点)
三十、JUC-读写锁-案例实现
15、(代码)readwrite目录下:ReadWriteLockDemo.java
package com.nanjing.readwrite;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 读写锁-案例实现
*
* @author xizheng
* @date 2023-03-16 19:14:24
*/
//资源类
class MyCache {
//创建map集合
private volatile Map<String,Object> map = new HashMap<>();
//创建读写锁对象
private ReadWriteLock rwLock = new ReentrantReadWriteLock();
//放数据
public void put(String key,Object value) {
//添加写锁
rwLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+" 正在写操作"+key);
//暂停一会
TimeUnit.MICROSECONDS.sleep(300);
//放数据
map.put(key,value);
System.out.println(Thread.currentThread().getName()+" 写完了"+key);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放写锁
rwLock.writeLock().unlock();
}
}
//取数据
public Object get(String key) {
//添加读锁
rwLock.readLock().lock();
Object result = null;
try {
System.out.println(Thread.currentThread().getName()+" 正在读取操作"+key);
//暂停一会
TimeUnit.MICROSECONDS.sleep(300);
result = map.get(key);
System.out.println(Thread.currentThread().getName()+" 取完了"+key);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放读锁
rwLock.readLock().unlock();
}
return result;
}
}
public class ReadWriteLockDemo {
public static void main(String[] args) throws Exception {
MyCache myCache = new MyCache();
//创建线程放数据
for (int i = 1; i <= 5; i++) {
final int num = i;
new Thread(()->{
myCache.put(num+"",num+"");
},String.valueOf(i)).start();
}
TimeUnit.MICROSECONDS.sleep(300);
//创建线程取数据
for (int i = 1; i <= 5; i++) {
final int num = i;
new Thread(()->{
myCache.get(num+"");
},String.valueOf(i)).start();
}
}
}
三十一、JUC-读写锁-读写锁的演变
三十二、JUC-读写锁-读写锁的降级
16、(代码)readwrite目录下:Demo1.java
package com.nanjing.readwrite;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 演示读写锁降级
*
* @author xizheng
* @date 2023-03-16 22:50:27
*/
public class Demo1 {
public static void main(String[] args) {
//可重入读写锁对象
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();//读锁
ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();//写锁
//锁降级
//1 获取写锁
writeLock.lock();
System.out.println("nanjing");
//2 获取读锁
readLock.lock();
System.out.println("---read");
//3 释放写锁
writeLock.unlock();
//4 释放读锁
readLock.unlock();
}
}
三十三、JUC-阻塞队列-概述和架构
队列:先进先出
栈:后进先出
BlockingQueue(阻塞队列) 简介
Concurrent包中,BlockingQueue很好的解决了多线程中,如何高效安全“传输”数据的问题。通过这些高效并且线程安全的队列类,为我们快速搭建高质量的多线程程序带来极大的便利。本文详细介绍了BlockingQueue家庭中的所有成员,包括他们各自的功能以及常见使用场景。
阻塞队列,顾名思义,首先它是一个队列,通过一个共享的队列,可以使得数据由队列的一端输入,从另外一端输出;
当队列是空的,从队列中获得元素的操作将会被阻塞
当队列是满的,从队列中添加元素的操作将会被阻塞
试图从空的队列中获得元素的线程将会被阻塞,直到其他线程往空的队列插入新的元素
试图向已满的队列中添加新元素的线程将会被阻塞,直到其他线程从队列中移除一个或多个元素或者完全清空,使队列变得空闲起来并后续新增
常用的队列主要有以下两种:
先进先出(FIFO):先插入的队列的元素也最先出队列,类似于排队的功能。从某种程度上来说这种队列也体现了一种公平性
后进先出(LIFO):后插入队列的元素最先出队列,这种队列优先处理最近发生的事件(栈)
为什么需要BlockingQueue
好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue都给你一手包办了
在concurrent包发布以前,在多线程环境下,我们每个程序员都必须去自己控制这些细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度。
三十四、JUC-阻塞队列-分类和核心方法介绍
常用的阻塞队列:
ArrayBlockingQueue:基于数组的阻塞队列实现
LinkedBlockingQueue:基于链表的阻塞队列
三十五、JUC-阻塞队列-核心方法演示
17、(代码)queue目录下:BlockingQueueDemo.java
package com.nanjing.queue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
/**
* 阻塞队列演示
*
* @author xizheng
* @date 2023-03-16 23:00:59
*/
public class BlockingQueueDemo {
public static void main(String[] args) throws Exception {
//创建阻塞队列
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
//第一组
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
System.out.println(blockingQueue.element());
//System.out.println(blockingQueue.add("w"));
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
//System.out.println(blockingQueue.remove());
//第二组
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
System.out.println(blockingQueue.offer("www"));
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
//第三组
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
//blockingQueue.put("w");
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
//System.out.println(blockingQueue.take());
//第四组
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
System.out.println(blockingQueue.offer("w",10L, TimeUnit.SECONDS));
}
}
三十六、JUC-线程池-概述和架构
ThreadPool线程池
线程池(英语:thread pool):一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能保证内核的充分利用,还能防止过分调度。
线程池的优势:线程池做的工作只要是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超过数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。
它的主要特点为:
降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
提高响应速度:当任务到达时,任务可以不需要等待线程的创建就能立即执行。
提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
Java中的线程池是通过Executor框架实现的,该框架中用到了Executor,Executors,ExecutorService,ThreadPoolExecutor这几个类
三十七、JUC-线程池-使用方式和底层原理
1、一池N线程:Executors.newFixedThreadPool(int)
2、一个任务一个任务执行,一池一线程:Executors.newSingleThreadExecutor()
3、线程池根据需求创建线程,可扩容,遇强则强:Executors.newCachedThreadPool()
18、(代码)pool目录下:ThreadPoolDemo1.java
package com.nanjing.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 线程池demo1
*
* @author xizheng
* @date 2023-05-02 21:39:54
*/
public class ThreadPoolDemo1 {
public static void main(String[] args) {
//一池五线程
ExecutorService threadPool1 = Executors.newFixedThreadPool(5);//5个窗口
//一池一线程
//ExecutorService threadPool2 = Executors.newSingleThreadExecutor();//一个窗口
//一池可扩容线程
//ExecutorService threadPool3 = Executors.newCachedThreadPool();
//10个顾客请求
try {
for (int i = 1; i <= 100; i++) {
//执行execute()方法以后,才创建了线程
threadPool1.execute(()->{
System.out.println(Thread.currentThread().getName()+" 办理业务");
});
}
}catch (Exception e) {
e.printStackTrace();
}finally {
//关闭
threadPool1.shutdown();
}
}
}
三十八、JUC-线程池-七个参数介绍
ChatGPT翻译:
corePoolSize
:线程池中保留的线程数,即使它们是空闲的,除非设置了 allowCoreThreadTimeOut
。
maximumPoolSize
:线程池允许的最大线程数。
keepAliveTime
:当线程数大于核心线程数时,多余的空闲线程在终止之前等待新任务的最大时间。
unit
:keepAliveTime
参数的时间单位。
workQueue
:用于保存待执行任务的队列。该队列只会保存通过 execute
方法提交的 Runnable
任务。
threadFactory
:线程池创建新线程时使用的工厂。
handler
:当执行被阻塞因为线程边界和队列容量达到上限时使用的处理程序。
常用参数(重点)
corePoolSize 线程池的核心线程数
maximumPoolSize 能容纳的最大线程数
keepAliveTime 空闲线程存活时间
unit 存活的时间单位
workQueue 存活提交但未执行任务的队列
threadFactory 创建线程的工厂类
handler 等待队列满后的拒绝策略
线程池中,有三个重要的参数,决定影响了拒绝策略:corePoolSize - 核心线程数,也即最小的线程数。workQueue - 阻塞队列。maximumPoolSize - 最大线程数
当提交任务数大于 corePoolSize 的时候,会优先将任务放到 workQueue 阻塞队列中。当阻塞队列饱和后,会扩充线程池中线程数,直到达到 maximumPoolSize 最大线程数配置。此时,再多余的任务,则会触发线程池的拒绝策略了。
总结起来,也就是一句话,当提交的任务数大于(workQueue.size() + maximumPoolSize),就会触发线程池的拒绝策略。
三十九、JUC-线程池-工作流程和拒绝策略
线程池底层工作原理(重要)
1、在创建了线程池后,线程池中的线程数为零
2、当调用execute()方法添加一个请求任务时,线程池会做出如下判断:
2.1 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
2.2 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列;
2.3 如果这个时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
2.4 如果队列满了且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。
3 当一个线程完成任务时,它会从队列中取下一个任务来执行
4 当一个线程无事可做超过一定的时间(keepAliveTime)时,线程会判断:
4.1 如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。
4.2 所以线程池的所有任务完成后,它最终会收缩到corePoolSize 的大小。
拒绝策略
四十、JUC-线程池-自定义线程池
19、(代码)pool目录下:ThreadPoolDemo2.java
package com.nanjing.pool;
import java.util.concurrent.*;
/**
* 线程池demo2
* 自定义线程池创建
*
* @author xizheng
* @date 2023-05-02 21:46:16
*/
public class ThreadPoolDemo2 {
public static void main(String[] args) {
ExecutorService threadPool = new ThreadPoolExecutor(
2,
5,
2L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
//10个顾客请求
try {
for (int i = 1; i <= 10; i++) {
//执行
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" 办理业务");
});
}
}catch (Exception e) {
e.printStackTrace();
}finally {
//关闭
threadPool.shutdown();
}
}
}
注意事项(重要)
1、项目中创建多线程时,使用常见的三种线程池创建方式,单一、可变、定长都有一定问题,原因是FixedThreadPool 和SingleThreadExecutor底层都是用LinkedBlockingQueue实现的,这个队列最大长度为 Integer.MAX_VALUE,容易导致OOM。所以实际生产一般自己通过 ThreadPoolExecutor的7个参数,自定义线程池
2、为什么不允许使用 Executors的方式手动创建线程池,如下图
四十三、JUC-异步回调
1、CompletableFuture简介
CompletableFuture 在Java里面被用于异步编程,异步通常意味着非阻塞,可以使得我们的任务单独运行在与主线程分离的其他线程中,并且通过回调可以在主线程中得到异步任务的执行状态,是否完成,和是否异常等信息。
CompletableFuture 实现了 Future,CompletionStage接口,实现了Future接口就可以兼容现在有线程池框架,而CompletionStage接口才是异步编程的接口抽象,里面定义多种异步方法,通过这两者集合,从而打造出强大的CompletableFuture类。
2、Future 与 CompletableFuture
Future在Java里面,通常用来表示一个异步任务的引用,比如我们将任务提交到线程池里面,然后我们会得到一个Future,在Future里面有isDone方法来判断任务是否处理结束,还有get方法可以一直阻塞直到任务结束然后获取结果,但整体来说这种方式,还是同步的,因为需要客户端不断阻塞等待或者不断轮询才能知道任务是否完成。
Future的主要缺点如下:
(1)不支持手动完成
我提交了一个任务,但是执行太慢了,我通过其他路径已经获取到了任务结果,现在没法把这个任务结果通知到正在执行的线程,所以必须主动取消或者一直等待它执行完成
(2)不支持进一步的非阻塞调用
通过Future的get方法会一直阻塞到任务完成,但是想在获取任务之后执行额外的任务,因为Future不支持回调函数,所以无法实现这个功能
(3)不支持链式调用
对于Future的执行结果,我们想继续传到下一个Future处理使用,从而形成一个链式的pipline调用,这在Future中是没法实现的。
(4)不支持多个Future合并
比如我们有10个Future并行执行,我们想在所有的Future运行完毕之后,执行某些函数,是没法通过Future实现的。
(5)不支持异常处理
Future的API没有任何的异常处理的api,所以在异步运行时,如果出了问题是不好定位的
20、(代码)completable目录下:CompletableFutureDemo.java
package com.nanjing.completable;
import java.util.concurrent.CompletableFuture;
/**
* 异步回调和同步调用
*
* @author xizheng
* @date 2023-05-04 08:29:41
*/
public class CompletableFutureDemo {
public static void main(String[] args) throws Exception {
//异步调用 没有返回值
CompletableFuture<Void> completableFuture1 = CompletableFuture.runAsync(()->{
System.out.println(Thread.currentThread().getName()+" : CompletableFuture1");
});
completableFuture1.get();
//异步调用 有返回值
CompletableFuture<Integer> completableFuture2 = CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName()+" : CompletableFuture2");
//模拟异常
//int i = 10/0;
return 1024;
});
completableFuture2.whenComplete((t,u)->{
System.out.println("------t="+t);//返回值
System.out.println("------u="+u);//异常
}).get();
}
}