一 Callable接口
创建线程的方式
Callable接口 Runnable接口比较
- 是否有返回值
- Callable接口有返回值
- Runnable接口没有返回值
- 是否抛出异常
- Callable接口,如果无法计算返回值会抛出异常
- Runnable接口,不会抛出异常
- 实现方法名称不同
- Callable ----> call()
- Runnable ----> run()
Callable使用引入
package new_course.chp4.callable;
import java.util.concurrent.Callable;
/**
* @author Created by Lin Weihong
* @date on 2022/6/2 13:25
*/
public class Demo01 {
public static void main(String[] args) {
//Runnable方式接口创建线程
new Thread(new MyThread01(),"AA").start();
//Callable接口,报错,不能直接用Callable替换Runnable
// new Thread(new MyThread02(),"BB").start();
}
}
//比较两个接口
class MyThread01 implements Runnable{
@Override
public void run() {
}
}
class MyThread02 implements Callable {
@Override
public Integer call() throws Exception {
return 200;
}
}
FutureTask
package new_course.chp4.callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
/**
* @author Created by Lin Weihong
* @date on 2022/6/2 14:13
* FutureTask
*/
public class Callable_FutureTask {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> futureTask = new FutureTask<>(() -> {
System.out.println(Thread.currentThread().getName() + " come in callable");
return 1024;
});
FutureTask<Integer> futureTask2 = new FutureTask<>(() -> {
System.out.println(Thread.currentThread().getName() + " come in callable");
return 2048;
});
//创建线程
new Thread(futureTask, "lucy").start();
new Thread(futureTask2, "kitty").start();
while (!futureTask.isDone()) {
//判断是否做
System.out.println("wait...................");
}
//调用futureTask的get方法
System.out.println(futureTask.get());//需要等待
System.out.println(futureTask2.get());
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(futureTask.get());//直接返回结果
System.out.println(Thread.currentThread().getName() + " come over");
//FutureTask原理 未来任务
/**
* 1、老师上课,口渴,想喝水,去买水不合适,讲课线程继续
* 单开启线程找班长帮我买水
* 把水买回来,我需要的时候直接get
*
* 2、4个同学,1同学(1+2+3+4+5) 2同学(10+11+12...+50) 3同学(60+61+62) 4同学(100+200)
* 2同学计算量比较大,FutureTask单给2同学开线程计算
* 先去汇总别的同学,最后等2计算完,最终在做汇总
*
* 注意:结果只会汇总一次
*
*/
}
}
二 JUC强大辅助类
减少计数 CountDownLatch
该类的构造方法为
**CountDownLatch(int count)**构造一个用给定计数初始化的CountDownLatch在这里插入代码片
两个常用的主要方法
await() 使当前线程在锁存器倒计数至零之前一直在等待,除非线程被中断
**countDown()**递减锁存器的计数,如果计数达到零,将释放所有等待的线程
CountDownLatch 类可以设置一个计数器,然后通过 countDown 方法来进行减 1 的操作,使用 await 方法等待计数器不大于 0,然后继续执行 await 方法之后的语句
具体步骤可以演化为定义一个类,减1操作,并等待到0,为0执行结果
package new_course.chp4.juc;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Created by Lin Weihong
* @date on 2022/6/2 14:49
* 演示 CountDownLatch
*
* 题目:设计一到多线程解决方案,教师有六名同学,只有当六名同学全部走完以后,班长才能锁门
*/
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
//第一步.创建CountDownLatch,并设置初始值
CountDownLatch countDownLatch = new CountDownLatch(6);
//6名同学走出教师
for (int i = 1; i <= 6 ; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " 号同学离开了教室");
//第二步 计数 -1
countDownLatch.countDown();
},String.valueOf(i)).start();
}
//第三步 等待,直到减到0才输出
countDownLatch.await();
System.out.println("班长关门");
}
}
循环栅栏 CyclicBarrier
该类是 允许一组线程 互相 等待,直到到达某个公共屏障点,在设计一组固定大小的线程的程序中,这些线程必须互相等待,因为barrier在释放等待线程后可以重用,所以称为循环barrier
常用的构造方法有:
**CyclicBarrier(int parties,Runnable barrierAction)**创建一个新的CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,并在启动barrier时执行给定的屏障操作,该操作由最后一个进入barrier的线程操作
常用的方法有:
**await()**在所有的参与者都已经在此barrier上调用await方法之前一直等待
package new_course.chp4.juc;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
* @author Created by Lin Weihong
* @date on 2022/6/2 15:07
* 演示CyclicBarrier
* <p>
* 题目,当七把钥匙都得到的时候,才能开启这扇大门
*/
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(() -> {
System.out.println(Thread.currentThread().getName() +" 把钥匙被收集到了");
try {
//每次执行cyclicBarrier,障碍数+1,直到达到NUMBER,才会执行await()之后的方法
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();
}
}
}
信号灯 Semaphore
一个计数信号量,从概念上将,信号量维护了一个许可集,如有必要,在许可可用前会阻塞每一个acquire(),然后在获取该许可。每个**release()**添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore只对可用许可的号码进行计数,并采取相应的行动
具体常用的构造方法有:
**Semaphore(int permits)**创建具有给定的许可数和非公平的公平设置的Semapore
具体常用的方法有:
**acquire()**从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断
**release()**释放一个许可,将其返回给信号量
设置许可数量Semaphore semaphore = new Semaphore(3);
一般acquire()都会抛出异常,release在finally中执行
package new_course.chp4.juc;
import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* @author Created by Lin Weihong
* @date on 2022/6/2 16:26
* 6辆汽车,停3个停车位
*/
public class SemaphoreDemo {
public static void main(String[] args) {
//创建Semaphore,设置3个许可数量
Semaphore semaphore = new Semaphore(3);
//模拟6辆汽车
for (int i = 1; i <= 6 ; i++) {
new Thread(() -> {
//抢占
try {
//抢占
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " 抢到了车位");
//设置随机停车时间
try {
TimeUnit.SECONDS.sleep(new Random().nextInt(5));//随机停留5s以内
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " -----离开了车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//释放
semaphore.release();
}
},String.valueOf(i)).start();
}
}
}
//结果:
1 抢到了车位
3 抢到了车位
2 抢到了车位
1 -----离开了车位
3 -----离开了车位
5 抢到了车位
4 抢到了车位
4 -----离开了车位
6 抢到了车位
5 -----离开了车位
6 -----离开了车位
2 -----离开了车位
三 读写锁
悲观锁
同一时刻只能有一个线程操作资源
乐观锁
同一时刻可以有多个线程操作资源,每次操作都会带上版本号version
表锁
只操作一条记录,但是把整张表锁起来,别的线程操作不了任何资源,不会发生死锁
行锁
只操作一条记录,锁的是操作的那一条记录,别的线程可以操作别行记录,会发生死锁
读锁(共享锁)
会产生死锁
写锁(独占锁)
会产生死锁
读写锁问题引入
package new_course.chp4.readwrite;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* @author Created by Lin Weihong
* @date on 2022/6/3 15:42
*/
class MyCache {
//创建Map集合,因为数据不断发生变化,所以要加入volatile
private volatile Map<String, Object> map = new HashMap<>();
//向集合放入数据
public void put(String key, Object value) {
System.out.println(Thread.currentThread().getName() + " 正在进行写操作:" + key);
try {
TimeUnit.MILLISECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
//放入数据
map.put(key, value);
System.out.println(Thread.currentThread().getName() + " 数据已经写完: " + key);
}
//从集合取出数据
public Object get(String key) {
Object result = null;
System.out.println(Thread.currentThread().getName() + " 正在进行读取操作:" + key);
try {
TimeUnit.MILLISECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
result = map.get(key);
System.out.println(Thread.currentThread().getName() + " 已经读取完毕:" + key);
return result;
}
}
public class ReadWriteLockDemo {
public static void main(String[] args) {
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();
}
//创建线程,往里面放数据
for (int i = 1; i <= 5; i++) {
final int num = i;
new Thread(() -> {
myCache.get(num + "");
}, String.valueOf(i)).start();
}
}
}
结果:还没写完就开始读,不是正确的结果
通过读写锁进行改造
package new_course.chp4.readwrite;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @author Created by Lin Weihong
* @date on 2022/6/3 15:42
*/
class MyCache {
//创建Map集合,因为数据不断发生变化,所以要加入volatile
private volatile Map<String, Object> map = new HashMap<>();
//创建读写锁对象
private ReadWriteLock rwLock = new ReentrantReadWriteLock();
//向集合放入数据
public void put(String key, Object value) {
//添加写锁
Lock lock = rwLock.writeLock();
//加锁
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " 正在进行写操作:" + key);
TimeUnit.MILLISECONDS.sleep(300);
//放入数据
map.put(key, value);
System.out.println(Thread.currentThread().getName() + " 数据已经写完: " + key);
} catch (Exception e) {
e.printStackTrace();
} finally {
//解锁
lock.unlock();
}
}
//从集合取出数据
public Object get(String key) {
//加上读锁
Lock lock = rwLock.readLock();
lock.lock();
Object result = null;
try {
System.out.println(Thread.currentThread().getName() + " 正在进行读取操作:" + key);
TimeUnit.MILLISECONDS.sleep(300);
result = map.get(key);
System.out.println(Thread.currentThread().getName() + " 已经读取完毕:" + key);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
return result;
}
}
public class ReadWriteLockDemo {
public static void main(String[] args) {
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();
}
//创建线程,往里面放数据
for (int i = 1; i <= 5; i++) {
final int num = i;
new Thread(() -> {
myCache.get(num + "");
}, String.valueOf(i)).start();
}
}
}
运行结果:从结果可以发现,读锁是共享锁,所有线程可以一起读,而写锁是独占锁
读写锁总结
一个资源可以被多个读线程访问,或者可以被一个写线程访问,但是不能同时存在读写线程,读写互斥,读读共享
读写锁的演变
读写锁的降级
写锁降级为读锁
package new_course.chp4.readwrite;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @author Created by Lin Weihong
* @date on 2022/6/3 16:43
* 展示读写锁降级
*/
public class Demo01 {
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("小新--write");
//2 获取读锁
readLock.lock();
System.out.println("小新--read");
System.out.println("***************程序正常写和读,证明了写操作时,是可以读的******************");
//3.释放写锁
writeLock.unlock();
//4.释放读锁
readLock.unlock();
}
}