JUC并发编程(下集)
前言
本篇文章仅供自己学习使用,参考与B站尚硅谷的JUC并发编程视频整理,如果能对读者有一点点启发,本人深感荣幸! 该篇是下集
全面发展,一专多能!!!!
6.实现Callable接口
实现Callable接口是创建线程的第三种方式
拓展知识(实现多线程的四种方式)
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
- 线程池
后续有时间会总结一下四种多线程的创建方式并且发布新的笔记类博文
6.1 Callback接口
实现Callable接口和实现Runnable接口的区别:
- call方法多了返回值
- call方法可以抛出异常
- 执行Callable方式,需要FutrueTask类的支持,用来接收运算结果
注:FutureTask接口是Future的实现类 使用get()方法可以获取运算结果 FutureTask 会导致线程阻塞,可以实现闭锁的操作
上代码:
public class TestCallable {
public static void main(String[] args) {
ThreadDemo td = new ThreadDemo();
//1.执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。
FutureTask<Integer> result = new FutureTask<>(td);
new Thread(result).start();
//2.接收线程运算后的结果
try {
Integer sum = result.get(); //FutureTask 可用于 闭锁
System.out.println(sum);
System.out.println("------------------------------------");
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
class ThreadDemo implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum += i;
}
return sum;
}
}
7.Lock同步锁
7.1 解决多线程安全问题的方式
有三种解决方案,如下:
- 同步代码块
- 同步方法
- 同步锁
在JDK1.5之前使用1和2两种方法,需要使用synchronized关键字,JDK1.5之后有更灵活的方式 Lock,同步代码块和同步方法是隐式锁,同步锁是一个显示锁,需要通过lock方法上锁,并且使用unlock解锁。
注意:使用lock上锁后必须在finally块中使用unlock进行释放锁
7.2 使用lock操作的实际代码
public class TestLock {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(ticket, "1号窗口").start();
new Thread(ticket, "2号窗口").start();
new Thread(ticket, "3号窗口").start();
}
}
class Ticket implements Runnable{
private int tick = 100;
private Lock lock = new ReentrantLock();
@Override
public void run() {
while(true){
// 上锁
lock.lock();
try{
if(tick > 0){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + " 完成售票,余票为:" + --tick);
}
}finally{
// 必须执行 因此放在finally中 释放锁
lock.unlock();
}
}
}
}
7.3 虚假唤醒的解决方案
从消费者和生产者的示例中可以得知 解决虚假唤醒的方案是将wait方法放入循环中,出现虚假唤醒的原因为if中wait被唤醒会继续执行,然而while中调用wait被唤醒会重新执行判断条件,贴一张CSDN大佬的评论图
上代码:
package cn.atguigu.juc;
/*
* 虚假唤醒的解决:
* wait要始终保证在while循环当中。
*/
public class LockTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producter producter = new Producter(clerk);
Customer customer = new Customer(clerk);
new Thread(producter,"生产者A").start();
new Thread(customer,"消费者A").start();
new Thread(producter,"生产者B").start();
new Thread(customer,"消费者B").start();
}
}
// 售货员
class Clerk {
private int product = 0;
// 进货
public synchronized void add() {
// 产品已满
while (product >=1) {
System.out.println(Thread.currentThread().getName() + ": " + "已满!");
try {
this.wait();
} catch (InterruptedException e) {
}
}
++product;
// 该线程从while中出来的时候,是满足条件的
System.out.println(Thread.currentThread().getName() + ": " +".....进货成功,剩下"+product);
this.notifyAll();
}
// 卖货
public synchronized void sale() {
while (product <=0) {
System.out.println(Thread.currentThread().getName() + ": " + "没有买到货");
try {
this.wait();
} catch (InterruptedException e) {
}
}
--product;
System.out.println(Thread.currentThread().getName() + ":买到了货物,剩下 " + product);
this.notifyAll();
}
}
// 生产者
class Producter implements Runnable {
private Clerk clerk;
public Producter(Clerk clerk) {
this.clerk = clerk;
}
// 进货
@Override
public void run() {
for(int i = 0; i < 20; ++i) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
clerk.add();
}
}
}
// 消费者
class Customer implements Runnable {
private Clerk clerk;
public Customer(Clerk clerk) {
this.clerk = clerk;
}
// 买货
@Override
public void run() {
for(int i = 0; i < 20; ++i) {
clerk.sale();
}
}
}
7.4 Condition 控制线程通信
Condition 接口描述了可能会与锁有关联的条件变量。这些变量在用 法上与使用 Object.wait 访问的隐式监视器类似,但提供了更强大的功能。需要特别指出的是,单个 Lock 可能与多个 Condition 对象关联。为了避免兼容性问题,Condition 方法的名称与对应的 Object 版本中的不同。
在 Condition 对象中,与 wait、notify 和 notifyAll 方法对应的分别是 await、signal 和 signalAll。
Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得 Condition 实例,请使用Lock其 newCondition() 方法获取Condition实例
8 ReadWriteLock 读写锁
ReadWriteLock 是一对相关的锁,一个用于读操作, 另一个用于写操作。只要没有 writer,读取锁可以由多个 reader 线程同时保持。写入锁是独占的。
ReadWriteLock 读取操作通常不会改变共享资源,但执行写入操作时,必须独占方式来获取锁。对于读取操作占多数的数据结构。 ReadWriteLock 能提供比独占锁更高的并发性。而对于只读的数据结构,其中包含的不变性 可以完全不需要考虑加锁操作。
8.1 读写锁常用方法
- readLock() // 获取读锁 “非互斥锁”
- writeLock() // 获取写锁 “互斥锁”
9 线程八锁
线程八锁是八种使用多线程锁的常用情况,核心是为了证明两点,这里只阐述结论,不做具体例子代码
- 非静态方法的默认锁是this,静态方法的默认锁是class
- 某一时刻内,只能有一个线程有锁,无论几个方法
10 线程池
线程池是第四种创建多线程的方式,提供了一个线程队列,队列中保存着所有等待状态的线程。避免了创建与销毁,可以提高响应速度,通常使用Executors工厂方法配置
10.1 线程池的体系结构
java.util.concurrent.Executor :负责线程的使用与调度的根接口
|-- ExecutorService 子接口:线程池的主要接口
|-- ThreadPoolExecutor 实现类:线程池的实现类
|-- ScheduledExecutorService 子接口 : 负责线程调度
|-- ScheduledThreadPoolExecutor :继承了ThreadPoolExecutor,实现了ScheduledExecutorService
10.2 Executors工具类常用方法
newFixedThreadPool() :创建固定大小的线程池
newCachedThreadPool() :缓存线程池,线程池的数量不固定,可以根据需求自动更改
newSingleThreadExecutor() : 创建单个线程池,线程池只有一个线程
newScheduledThreadPool() : 创建单个固定大小的线程池,可以延迟或定时的任务
10.3 线程池与Callable联合使用示例
public class TestThreadPool {
public static void main(String[] args) throws Exception {
//1. 创建线程池
ExecutorService pool = Executors.newFixedThreadPool(5);
List<Future<Integer>> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Future<Integer> future = pool.submit(new Callable<Integer>(){
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum += i;
}
return sum;
}
});
list.add(future);
}
// 关闭连接
pool.shutdown();
for (Future<Integer> future : list) {
System.out.println(future.get());
}
10.4 线程调度
使用Executors.newScheduledThreadPool()方法进行线程调度 代码如下
public class TestScheduledThreadPool {
public static void main(String[] args) throws Exception {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);
for (int i = 0; i < 5; i++) {
Future<Integer> result = pool.schedule(new Callable<Integer>(){
@Override
public Integer call() throws Exception {
int num = new Random().nextInt(100);//生成随机数
System.out.println(Thread.currentThread().getName() + " : " + num);
return num;
}
}, 1, TimeUnit.SECONDS); //延迟线程,延迟时间,时间单位
System.out.println(result.get());
}
pool.shutdown();
}
}
11 ForkJoinPool 分支/合并框架 工作窃取
11.1 Fork/Join 框架
11.2 Fork/Join框架和连接池的区别
采用 “工作窃取”模式(work-stealing):
当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中。
相对于一般的线程池实现,fork/join框架的优势体现在对其中包含的任务的处理方式上。在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态。而在fork/join框架实现中, 如果某个子问题由于等待另外一个子问题的完成而无法继续运行。那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行。这种方式减少了 线程的等待时间,提高了性能。
以上这段话取自CSDN大佬-- yZzc_XQ 感谢!
完结撒花!!! 感谢尚硅谷老师,感谢CSDN大佬yZzc_XQ 好好学习天天向上!!
本篇学习笔记成功收尾,下篇中介绍了两种创建线程的方式,分别是实现Callable接口和线程池的方法,并且介绍了Lock线程锁,读写锁,以及线程八锁的情况,每日坚持学习新知识充实自己
人生目标:全面发展,一专多能!!!