juc
什么是JUC:java.util.concurrent包名的简写,是关于并发编程的API。
与JUC相关的有三个包:java.util.concurrent、java.util.concurrent.atomic、java.util.concurrent.locks。
入门程序 卖票
synchronized版本
如何理解买票小程序,其实就是线程操作资源类,资源就是票,多个线程操作该资源时,就会产生线程不安全问题,如何解决,那就是加锁。
卖票不安全版本
代码如下(示例):
package com.atguigu.controller;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class saleTickte {
public static void main(String[] args) {
Ticket1 ticket = new Ticket1();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}, "a").start();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}, "b").start();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}, "c").start();
}
}
/**
* 票实体类
*/
class Ticket1 {
private int num = 40;
public void sale() {
if (num > 0) {
try {
TimeUnit.MILLISECONDS.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出第" + num-- + "张票" + " 还剩" + num + "张");
}
}
}
执行结果如下
synchronized版本
代码如下(示例):
package com.atguigu.controller;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class saleTickte {
public static void main(String[] args) {
Ticket1 ticket = new Ticket1();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}, "a").start();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}, "b").start();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}, "c").start();
}
}
/**
* 票实体类
*/
class Ticket1 {
private int num = 15;
public synchronized void sale() {
if (num > 0) {
System.out.println(Thread.currentThread().getName() + "卖出第" + num-- + "张票" + " 还剩" + num + "张");
}
}
}
执行结果
lock版本
package com.atguigu.controller;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class saleTickte {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}, "a").start();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}, "b").start();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}, "c").start();
}
}
/**
*
*/
class Ticket {
private int num = 40;
/**
* 买票
*/
Lock lock = new ReentrantLock();
public void sale() {
lock.lock();
try {
if (num > 0) {
System.out.println(Thread.currentThread().getName() + "卖出第" + num-- + "张票" + " 还剩" + num + "张");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
执行结果
那synchronized 和Lock的区别有哪些?
1.Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问;
2.Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象
生产者消费者问题
现在需求如下 生产者生产一个 消费者消费一个 有序进行
synchronized版本
package com.atguigu.controller;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class threadDemo {
public static void main(String[] args) {
Con1 c = new Con1();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
c.add();
}, "a").start();
}
for (int i = 0; i < 10; i++) {
new Thread(() -> {
c.decre();
}, "b").start();
}
}
}
/**
* 资源类
*/
class Con1 {
private int num = 0;
/**
* 新增操作 也就是生产
*/
public synchronized void add() {
///判断 此处很多人都会写成if 写if是不对的 程序会出问题虚假唤醒
while (num != 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//干活
num++;
System.out.println(Thread.currentThread().getName() + "新增到" + num);
//通知
this.notifyAll();
}
/**
* 减少操作 相当于消费
*/
public synchronized void decre() {
//判断 此处很多人都会写成if 写if是不对的 程序会出问题虚假唤醒
while (num == 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//干活
num--;
System.out.println(Thread.currentThread().getName() + "减少到" + num);
//通知
this.notifyAll();
}
}
执行结果如下 交替运行 线程安全
Lock版本
package com.atguigu.controller;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class threadDemo {
public static void main(String[] args) {
Con2 c = new Con2();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
c.add();
}, "a").start();
}
for (int i = 0; i < 10; i++) {
new Thread(() -> {
c.decre();
}, "b").start();
}
}
}
/**
* 资源类
*/
class Con2 {
private int num = 0;
Lock lock = new ReentrantLock();
Condition c = lock.newCondition();
public void add() {
lock.lock();
try {
//判断 此处很多人都会写成if 写if是不对的 程序会出问题虚假唤醒
while (num != 0) {
c.await();
}
//干活
num++;
System.out.println(Thread.currentThread().getName() + "新增到" + num);
//通知
c.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decre() {
lock.lock();
try {
//判断 此处很多人都会写成if 写if是不对的 程序会出问题虚假唤醒
while (num == 0) {
c.await();
}
//干活
num--;
System.out.println(Thread.currentThread().getName() + "减少到" + num);
//通知
c.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
执行如下
注意
在高内聚 低耦合的前提下 线程操纵资源类 判断 干活 通知 防止虚假唤醒
如何理解上面这句话 高内聚 低耦合就是所有的方法都是资源类提供 ,我们只需要调用就行
判断 干活 通知 ,如上面这个程序 判断 就是我什么时候进行新增操作,什么时候进行减少操作, 干活 就是 新增 (num++) 减少 (num–) ,通知 就是我们新增好了 需要通知其他线程告诉他们我们新增好了 你快来消费 对应的就是notifyAll();
防止虚假唤醒 这个在程序中 判断哪一步 很多人都会写成if 写成if就会造成虚假唤醒 程序会出问题
所有的生产者消费者模式都可以按照 判断 干活 通知 来做
虚假唤醒 在jdk中的说明 那总结一句就是要使用wait()时不能使用if 要用while
精准通知
需求如下 调用打印方法 执行顺序不能变 要求先执行打印5次的 在调用打印10次的 在调用打印15次的
分析:在调用打印5次的方法时候 我们不能全部唤醒其他线程,我们需要唤醒的是打印10次的那个线程,在打印10次的方法里面我们需要做的是唤醒打印5次的那个线程
那么在多个线程的操作下 我们如何确保线程 安全 以及做到线程之间的精准通知呢
package com.atguigu.controller;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
public class EightLock {
public static void main(String[] args) throws InterruptedException, ExecutionException {
Prinlt p = new Prinlt();
for (int i = 0; i < 3; i++) {
new Thread(() -> {
p.priint5();
}, "a").start();
}
for (int i = 0; i < 3; i++) {
new Thread(() -> {
p.priint10();
}, "b").start();
}
for (int i = 0; i < 3; i++) {
new Thread(() -> {
p.priint15();
}, "c").start();
}
}
}
//打印实体类
class Prinlt {
//这是一个标记 当flag=1 打印5 flag=2 打印10 flag=3 打印15
private int flag = 1;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
public void priint5() {
lock.lock();
try {
while (flag != 1) {
condition.await();
}
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + " 打印" + i + "次");
}
flag = 2;
//唤醒
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void priint10() {
lock.lock();
try {
while (flag != 2) {
condition1.await();
}
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName() + " 打印" + i + "次");
}
flag = 3;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void priint15() {
lock.lock();
try {
while (flag != 3) {
condition2.await();
}
for (int i = 1; i <= 15; i++) {
System.out.println(Thread.currentThread().getName() + " 打印" + i + "次");
}
flag = 1;
condition.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
执行结果如下 图未截取完整
总结 在synchronized 里面等待 唤醒用的方法是 wait() notify() notifyAll() 在lock里面用的是 等待 唤醒用的是 await() signal() signalAll 并且lock功能更加强大 支持精准唤醒
集合类不安全
在工作中用到List,大家可能都会用 List list = new ArrayList();但是呢ArrayList是线程不安全的,那么我们该怎么解决呢?
ArrayList 不安全演示
package com.atguigu.thread;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* Exception in thread "a" java.util.ConcurrentModificationException 并发修改异常
* 解决办法 1 List<String> list = new Vector<>();
*/
public class ArrayListDemo {
public static void main(String[] args) {
//List<String> list = new CopyOnWriteArrayList<>();
List list = new ArrayList();
for (int i = 1; i <= 3; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
}
线程不安全
解决办法 创建CopyOnWriteArrayList集合
package com.atguigu.thread;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* Exception in thread "a" java.util.ConcurrentModificationException 并发修改异常
* 解决办法 1 List<String> list = new Vector<>();
*/
public class ArrayListDemo {
public static void main(String[] args) {
/**
* set 集合也不安全 解决办法
* Set set = new CopyOnWriteArraySet();
* Collections.synchronizedSet(new HashSet<>());
*
* Map也不安全 解决办法
* Map map = new ConcurrentHashMap();
*/
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 1; i <= 3; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
}
juc辅助类
package com.atguigu.controller;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
public class EightLock {
public static void main(String[] args) throws InterruptedException, ExecutionException {
/**
* CountDownLatch 辅助类 该怎么理解这个类呢 做减法 案例当所有离开教室 班长才能离开教室
* 如果不用FutureTask 很有可能出现线程不安全问题 人还没走完 班长就离开教室了
*/
CountDownLatch countDownLatch = new CountDownLatch(7);
for (int i = 0; i < 7; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "离开教室");
countDownLatch.countDown();
}, String.valueOf(i)).start();
}
countDownLatch.await();
System.out.println("班长离开教室");
/**
* Semaphore 辅助类 案例停车位 new Semaphore(3) 表示有3个停车位
* 停车位案例
* new Semaphore(3) 代表有3个车位
* for (int i = 0; i < 6; i++) 6代表有6个用户来停车
*/
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 6; i++) {
new Thread(() -> {
try {
//代表已经使用了一个
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "进入车位");
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + "离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//代表离开之后又有车位了
semaphore.release();
}
}, String.valueOf(i)).start();
}
}
}
辅助类 读写锁 ReadWriteLock 多人同时读 写入只能有一个人写
class Maps {
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
Map<String, Object> map = new HashMap();
public void putToMap(String key, String value) {
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "开始写入");
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "写入完成");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
public void getToMap(String key) {
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "读取开始");
System.out.println(Thread.currentThread().getName() + "读取成功" + "结果为" + map.get(key));
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().lock();
}
}
}
package com.atguigu.controller;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.*;
public class EightLock {
public static void main(String[] args) throws InterruptedException, ExecutionException {
Maps maps = new Maps();
for (int i = 1; i <= 3; i++) {
int finalI = i;
new Thread(() -> {
maps.putToMap(finalI + "", finalI + "");
}, String.valueOf(i)).start();
}
for (int i = 1; i <= 3; i++) {
int finalI = i;
new Thread(() -> {
maps.getToMap(finalI + "");
}, String.valueOf(i)).start();
}
}
}
如何实现多线程
除了以前实现Runnable 接口 继承Thread类 我们还有其他方法吗?
实现Callable接口
class future implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("come in callable");
return 1024;
}
}
如上 我们如何调用这个call方法呢?
package com.atguigu.controller;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
public class EightLock {
public static void main(String[] args) throws InterruptedException, ExecutionException {
FutureTask futureTask = new FutureTask(new future());
new Thread(futureTask).start();
System.out.println(futureTask.get());
}
}
执行结果如下
FutureTask 是什么?为什么要用这个类;首先Rannable和callable接口并没有直接联系 那么如何让他们产生联系呢 ?
如上图 FutureTask 实现了Runnable 接口 ,且构造函数可以传一个callable,这就让Runnable 和callable产生了联系,所以new Thread(futureTask).start();就可以直接调用成功;
通过线程池创建
创建指定数量的线程池
package com.atguigu.controller;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPool {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
try {
for (int i = 1; i <= 9; i++) {
executorService.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 执行");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭线程池
executorService.shutdown();
}
}
}
执行结果 始终就3个线程在服务
创建一个线程的线程池
package com.atguigu.controller;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPool {
public static void main(String[] args) {
//ExecutorService executorService = Executors.newFixedThreadPool(3);
ExecutorService executorService = Executors.newSingleThreadExecutor();
try {
for (int i = 1; i <= 9; i++) {
executorService.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 执行");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭线程池
executorService.shutdown();
}
}
}
执行结果如下 始终只有一个线程在服务
创建一个自适应的线程池(根据访问的数量 自己会产生合适的线程数)
package com.atguigu.controller;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPool {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
try {
for (int i = 1; i <= 9; i++) {
executorService.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 执行");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭线程池
executorService.shutdown();
}
}
}
执行结果如下 根据访问数量 自己会产生合适的线程数
在上面的几个案例中查看底层可以发现 他们创建线程池的底层其实都用的是同一个类 ThreadPoolExecutor;所以在实际开发中我们一般都会通过ThreadPoolExecutor自定义线程池
ThreadPoolExecutor的七大参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
如上图 这是ThreadPoolExecutor的底源码 我们发现有七个参数 那这七个参数的作用是啥呢?
corePoolSize – 要保留在池中的线程数,即使它们处于空闲状态,除非设置了allowCoreThreadTimeOut
maximumPoolSize – 池中允许的最大线程数
keepAliveTime – 当线程数大于核心数时,这是多余空闲线程在终止前等待新任务的最长时间。
unit – keepAliveTime参数的时间单位
workQueue – 用于在执行任务之前保存任务的队列。 这个队列将只保存execute方法提交的Runnable任务。
threadFactory – 执行程序创建新线程时使用的工厂
handler – 执行被阻塞时使用的处理程序,因为达到了线程边界和队列容量:
我们以一家银行来举例
银行就代表线程池 那么银行一共有5个窗口提供服务 这5个窗口就是maximumPoolSize (池中允许的最大线程数) 但是今天是周天 业务比较少 只开放了2个窗口提供服务,这2个窗口就是corePoolSize (核心线程数), 但是突然间来了五个人 这时只有两个窗口在服务,显然不够用,这时就需要让那3个人在候客区等待,这个候客区就是workQueue(阻塞队列),假如候客区最多容纳3个人,此时又来了3个人,这个时候候客区已经装不下了,那么此时就需要开放另外3个窗口来提供服务,当所有人的业务都办理完了,这个时候很明显开放的3个窗口是在浪费资源,我们可以设定一个时间,过了多长时间3个窗口就关闭,此时 keepAliveTime 就是这个道理,(unit 代表单位) keepAliveTime设定的时间过了之后,那么这三个窗口就关闭,也就代表三个线程关闭,threadFactory就是线程池工厂,用来产生线程的,这个一般默认即可,RejectedExecutionHandler 这个代表拒绝策略,该怎么理解,比如说银行出了一个理财产品,来的人特别多,所有窗口都开放 候客区也坐满了,那么后面的人我们就不在接纳,告诉他们让他们区别的银行办理
ThreadPoolExecutor的四种拒绝策略
1.默认拒绝策略 AbortPolicy 只要超过最大的服务数量立马抛异常
package com.atguigu.controller;
import java.util.concurrent.*;
public class ThreadPool {
public static void main(String[] args) {
//自定义线程池
ThreadPoolExecutor executorService = new ThreadPoolExecutor(2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
try {
for (int i = 1; i <= 9; i++) {
executorService.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 执行");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭线程池
executorService.shutdown();
}
}
}
如图 当最大线程数是5 阻塞队列最大值为3 那么最大接待的访问数是8 当有9个来访问时 就会抛异常
这就是默认的拒绝策略
2. CallerRunsPolicy拒绝策略 该策略既不会抛弃任务 也不会抛异常 谁创建了该任务 谁来执行
package com.atguigu.controller;
import java.util.concurrent.*;
public class ThreadPool {
public static void main(String[] args) {
ThreadPoolExecutor executorService = new ThreadPoolExecutor(2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());
try {
for (int i = 1; i <= 9; i++) {
executorService.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 执行");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭线程池
executorService.shutdown();
}
}
}
执行结果如下 第九个任务由main方法执行了
3 DiscardPolicy 该策略会默默的丢弃无法处理的任务 不做任何处理 也不报错 如果允许任务丢失 这是最好的一种策略
package com.atguigu.controller;
import java.util.concurrent.*;
public class ThreadPool {
public static void main(String[] args) {
ThreadPoolExecutor executorService = new ThreadPoolExecutor(2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardPolicy());
try {
for (int i = 1; i <= 9; i++) {
executorService.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 执行");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭线程池
executorService.shutdown();
}
}
}
执行结果如下 不报错 但是只执行了8个任务
4 DiscardOldestPolicy 该策略抛弃队列中等待最久的任务 然后把当前任务加入到队列中 尝试再次提交当前任务
package com.atguigu.controller;
import java.util.concurrent.*;
public class ThreadPool {
public static void main(String[] args) {
ThreadPoolExecutor executorService = new ThreadPoolExecutor(2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy());
try {
for (int i = 1; i <= 9; i++) {
executorService.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 执行");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭线程池
executorService.shutdown();
}
}
}
执行结果如下