文章目录
7 Callable接口
runnable 与callable的区别
相同点:
1.两个都是接口
2.两者都可以用来编写多线程程序
3.两者都可以调用Thread.start()来启动线程
不同点:
1.Runnable提供run(),无法通过throws抛出异常,所有CheckedException必须在run()内部处理;Callable提供call方法,直接抛出Exception异常。
2.Runnable的run无返回值,Callable的call方法提供返回值用来表示任务运行的结果
3.Runnable可以作为Thread构造器的参数,通过开启新的线程来执行,也可以通过线程池来执行。而Callable只能通过线程池执行。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @Author: mei_ming
* @DateTime: 2022/5/29 14:42
* @Description: runnable 与callable的区别
*/
class MyThread1 implements Runnable{
@Override
public void run() {
}
}
class MyThread2 implements Callable{
@Override
public Integer call() throws Exception {
return 200;
}
}
public class Demo1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//Runnable方式
new Thread(new MyThread1(),"AA").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,"luck").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(futureTask2.get()); //第二次,直接输出第一次执行结果
System.out.println(Thread.currentThread().getName()+" come over");
}
}
8. JUC强大的辅助类
8.1 减少计数CountDownLatch
CountDownLatch类可以设置一个计数器,然后通过countDown方法来进行减1的操作,使用await方法等待计数器不大于0,然后继续执行await方法之后的语句。
CountDownLatch主要有2个方法,当一个或多个线程调用await方法时,这些线程会阻塞。
其它线程调用countDown方法会将计数器减1
当计数器为0时,await方法会被唤醒,执行
package ming.xuexi.juc;
import java.util.concurrent.CountDownLatch;
/**
* @Author: mei_ming
* @DateTime: 2022/5/29 15:11
* @Description: 6个同学走后,班长才关门
*/
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();
}
//改进 等待
countDownLatch.await();
System.out.println("班长锁门走人....");
}
}
8.2 循环栅栏CyclicBarrier
CyclicBarrier 循环阻塞的意思,在使用中CyclicBarrier的构造方法第一个参数是目标障碍数,每次执行CyclicBarrier一次障碍数会加1,如果达到了目标障碍数,才会执行cyclicBarrier.await()之后的语句。可以将CyclicBarrier理解为+1操作
package ming.xuexi.juc;
import java.util.concurrent.CyclicBarrier;
/**
* @Author: mei_ming
* @DateTime: 2022/5/29 15:25
* @Description: 集齐7颗龙珠,可召唤神龙
*/
public class CyclicBarrierDemo {
private static int num=7;
public static void main(String[] args) {
//创建CyclicBarrier
CyclicBarrier cyclicBarrier = new CyclicBarrier(num, () -> {
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();
}
}
}
8.3 信号灯Semaphore
Semaphore的构造方法中传入的第一个参数是最大信号量,每个信号量初始化为一个最多只能分发一个许可证。使用acquire方法获取许可证,release方法释放许可。
package ming.xuexi.juc;
import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* @Author: mei_ming
* @DateTime: 2022/5/29 15:40
* @Description: 6辆汽车,停3个停车位
*/
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();
}
}
}
9. ReentrantReadWriteLock 读写锁
乐观锁与悲观锁
悲观锁每次去拿数据的时候,都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其他线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里面就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。
乐观锁每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实多是提供的乐观锁。在Java中Java.util.concurrent.atomic 包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
表锁和行锁
MyISAM不支持行锁,InnoDB支持行锁和表锁。
在Mysql的InnoDB引擎支持行锁,MySQL的行锁是通过索引加载的,即是行锁是加在索引响应的行上的,要是对应的SQL语句没有走索引,则会全表扫描,行锁则无法实现,取而代之的是表锁。
表锁 不会出现死锁,发生锁冲突几率高,并发低
表锁 会出现死锁,发生锁冲突几率低,并发高
共享锁和排它锁
行锁分为共享锁和排他锁
共享锁又称:读锁。当一个事务对某几行上读锁时,允许其它事务对这几行进行读操作,但不允许其进行写操作,也不允许其他事务给这几行上排它锁,但允许上读锁。(会产生死锁)
排它锁又称:写锁。当一个事务对某几行上写锁时,不允许其他事务写,但允许读。更不允许其他事务给这几行上任何锁。(会产生死锁)
ReadWriteLock 接口
实现类 ReentrantReadWriteLock
里面有两个静态内部类
public class ReentrantReadWriteLock
implements ReadWriteLock, java.io.Serializable{
//读锁
public static class ReadLock implements Lock, java.io.Serializable{}
//写锁
public static class WriteLock implements Lock, java.io.Serializable{}
}
package ming.xuexi.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: mei_ming
* @DateTime: 2022/6/1 14:53
* @Description: TODO
*/
//资源类
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()+" 写完了..");
} 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()+" 取完了..");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
rwLock.readLock().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();
}
}
}
10. BlockingQueue阻塞队列
10.1 阻塞队列的概述
Concurrent包中,阻塞队列很好的解决了多线程中,如何高效安全“传输”数据的问题,通过这些高效并线程安全的队列类,为我们快速搭建高质量的多线程程序带来了极大的便利。
阻塞队列,是一个队列,通过共享的队列,可以使得数据由队列的一端输入,从另外一端输出
当队列是空的,从队列中获取元素的操作将会被阻塞。
当队列是满的,从队列中添加元素的操作将会被阻塞。
试图从空的队列中获取元素的线程将会被阻塞,直到其他线程往空的队列插入新的元素。
试图向已满的队列中添加元素的线程会被阻塞。直到其他线程移除一个元素。
10.2 阻塞队列的框架
BlockingQueue 接口,
其实现类:ArrayBlockingQueue、LinkedBlockingQueue
10.3 阻塞队列分类
ArrayBlockingQueue:由数组结构组成的有界阻塞队列
LinkedBlockingQueue:由链表结构组成的有界的(默认大小为integer.max)阻塞队列
DelayQueue:使用优先级队列实现的延迟无界阻塞队列
PriorityBlockingQueue:支持优先级排序的无界阻塞队列
…
10.4 阻塞队列核心方法
方法类型 抛出异常 特殊值 阻塞 超时
插入 add(e) offer(e) put(e) offer(e,time,unit)
移除 remove() poll() take() poll(time,unit)
检查 element() peek() 不可用 不可用
- 抛出异常:
当阻塞队列满时,在往里面加元素就会抛IllegalStateException:Queue full
当阻塞队列空时,在往里面取元素就会抛NoSuchElementException
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class BlockingQueueDemo {
public static void main(String[] args) {
BlockingQueue 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());//return the head of this queue
//System.out.println(blockingQueue.add("w"));//java.lang.IllegalStateException: Queue full
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
//System.out.println(blockingQueue.remove());//java.util.NoSuchElementException
}
}
- 特殊值:
插入成功,返回true,失败返回false
移除成功,返回出队列的元素,失败返回null
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class BlockingQueueDemo {
public static void main(String[] args) {
BlockingQueue blockingQueue = new ArrayBlockingQueue(3);
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
//System.out.println(blockingQueue.peek());//return the head of this queue
//System.out.println(blockingQueue.offer("w"));//false
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());//null
}
}
- 阻塞:
当阻塞队列满时,在往里面加元素会一直阻塞到其他线程取出元素后,在插入
当阻塞队列空时,在往里面取元素会一直阻塞到其他线程插入元素后,在取出
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class BlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
BlockingQueue blockingQueue = new ArrayBlockingQueue(3);
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
//blockingQueue.put("d"); //处于阻塞等待状态中
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
//System.out.println(blockingQueue.take()); //处于阻塞等待状态中
}
}
- 超时:
当阻塞队列满时,在往里面加元素会阻塞到一定时间,还是没有其他线程取出元素,则退出
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
public class BlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
BlockingQueue blockingQueue = new ArrayBlockingQueue(3);
blockingQueue.offer("a");
blockingQueue.offer("b");
blockingQueue.offer("c");
//blockingQueue.offer("d",3L, TimeUnit.SECONDS);//处于阻塞等待状态中 阻塞3s
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll(3L,TimeUnit.SECONDS)); //处于阻塞等待状态中 返回null
}
}
11 ThreadPool线程池
11.1 线程池概述
线程池:一种线程使用模式,线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理着分配可并发执行的任务。避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。
优点:
线程池做的工作只要是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等待,等其他线程执行完毕,再从队列中取出任务来执行。
主要特点:
降低资源消耗:
提高响应速度:
提高线程可管理性:
11.2 线程池架构
Executor : 总接口
Executors:工具类
ExecutorService :Executor的子接口
ThreadPoolExecutor : AbstractExecutor接口的实现类
11.3 线程池使用方式
- 一池N线程
Executors.newFixedThreadPool(int)
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolDemo1 {
public static void main(String[] args) {
ExecutorService executorService01 = Executors.newFixedThreadPool(5);
try{
for (int i = 1; i <=10; i++) {
//执行
executorService01.execute(()->{
System.out.println(Thread.currentThread().getName()+" 办理业务");
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
//关闭
executorService01.shutdown();
}
}
}
/*
pool-1-thread-1 办理业务
pool-1-thread-2 办理业务
pool-1-thread-3 办理业务
pool-1-thread-2 办理业务
pool-1-thread-2 办理业务
pool-1-thread-2 办理业务
pool-1-thread-2 办理业务
pool-1-thread-3 办理业务
pool-1-thread-5 办理业务
pool-1-thread-4 办理业务
*/
- 一个任务一个任务的执行,一池一线程
Executors.newSingleThreadExecutor()
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolDemo1 {
public static void main(String[] args) {
ExecutorService executorService02 = Executors.newSingleThreadExecutor();
try{
for (int i = 1; i <=10; i++) {
//执行
executorService02.execute(()->{
System.out.println(Thread.currentThread().getName()+" 办理业务");
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
//关闭
executorService02.shutdown();
}
}
}
/*
pool-1-thread-1 办理业务
pool-1-thread-1 办理业务
pool-1-thread-1 办理业务
pool-1-thread-1 办理业务
pool-1-thread-1 办理业务
pool-1-thread-1 办理业务
pool-1-thread-1 办理业务
pool-1-thread-1 办理业务
pool-1-thread-1 办理业务
pool-1-thread-1 办理业务
*/
- 线程池根据需要创建线程,可扩容
Executors.newCachedThreadPool()
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolDemo1 {
public static void main(String[] args) {
ExecutorService executorService03 = Executors.newCachedThreadPool();
try{
for (int i = 1; i <=10; i++) {
//执行
executorService03.execute(()->{
System.out.println(Thread.currentThread().getName()+" 办理业务");
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
//关闭
executorService03.shutdown();
}
}
}
/*
pool-1-thread-1 办理业务
pool-1-thread-2 办理业务
pool-1-thread-3 办理业务
pool-1-thread-4 办理业务
pool-1-thread-5 办理业务
pool-1-thread-6 办理业务
pool-1-thread-7 办理业务
pool-1-thread-8 办理业务
pool-1-thread-10 办理业务
pool-1-thread-9 办理业务
*/
11.4 线程池底层原理
上面3个创建线程池,底层都是ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler){}
11.5 线程池7个参数
int corePoolSize : 常驻线程数量(核心)
int maximumPoolSize : 最大线程数量
long keepAliveTime,TimeUnit unit : 线程存活时间
BlockingQueue workQueue : 阻塞队列
ThreadFactory threadFactory : 线程工厂
RejectedExecutionHandler handler: 拒绝策略
11.6 线程池底层工作流程
拒绝策略:
- AbortPolicy:默认策略,在需要拒绝任务时抛出RejectedExecutionException;
- CallerRunsPolicy: 直接在execute方法的调用线程中运行被拒绝的任务,如果线程池已经关闭,任务将被抛弃。
- DiscardPolicy:直接抛弃任务
- DiscardOldestPolicy:抛弃队列中等待时间最长的任务。
工作流程:
- 执行execute()时,线程池判断核心线程池里的核心线程是否都在执行。如果不是,让空闲的核心线程来执行任务,如果核心线程池里的线程都在执行任务,则进入下个流程。
- 线程池判断阻塞队列是否已满,如果阻塞队列没有满,则将新提交的任务存储在阻塞队列中,如果阻塞队列已满,则进入下一个流程。
- 线程池判断线程池里的线程数量是否小于最大线程数量,如果小于,则创建一个新的工作线程(非核心,设置超时时间)来执行任务,如果已满,则交给拒绝策略来处理这个任务。
11.7 自定义线程池
一般开发中使用ThreadPoolExecutor自定义线程池
import java.util.concurrent.*;
public class ThreadPoolDemo2 {
public static void main(String[] args) {
//自定义线程池
ExecutorService threadPool = new ThreadPoolExecutor(
2,
8,
2L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
try{
for (int i = 1; i <=10; i++) {
//执行
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" 办理业务");
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
//关闭
threadPool.shutdown();
}
}
}
12 . Fork/Join分支合并框架
Fork : 把一个复杂任务进行拆分,大事化小
Join : 把拆分任务的结果进行合并
个人学习笔记,欢迎纠错