java多线程学习笔记2
三.Callable接口
1.与runnable接口的对比
callable接口有返回值
callable接口抛异常
callable接口的实现方法是call() ,runnable接口的实现方法是run()
2.Runnable接口使用
无法直接使用Thread()构造器 创建 因为Thread() 的构造器中需填 Runnable接口的实现类
使用FutureTask类 FutureTask类实现类Runnable接口。
FutureTask futureTask = new FutureTask(new Callable() {
@Override
public Object call() throws Exception {
return null;
}
});
new Thread(futureTask,"thread1").start();
new Thread(futureTask,"thread2").start();
try {
futureTask.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
即使创建多个线程 多次调用futuretask的get方法futureTask也只计算一次;
除了第一次 后面的都会get保存的的参数的数据(有一个state状态 判断是否得到过数据)
如果想多次计算,应创建多个FutureTask的实例。
四.JUC提供的几个常用方法
1.CountDownLatch 减少计数
CountDownLatch主要有两个方法 当多个线程调用await方法时,这些线程会阻塞。当其他线程调用countDown方法 会将计数器减1(调用countDown()方法的线程不会阻塞),当计数器降为0时。因await()方法阻塞的所有线程会被唤醒。
/**
* 学生回家问题 全部5个学生回家才锁门
* @throws InterruptedException
*/
@Test
public void test1() throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(5);
for (int i=0; i<5; i++){
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"回家了");
countDownLatch.countDown();
},"线程"+i).start();
}
countDownLatch.await();;
System.out.println("五个学生全部回家 锁门!");
}
2.CyclicBarrier 循环栅栏
字面意思是可循环使用的屏障。
让多个线程达到一个屏障(也叫做同步点)时被阻塞,知道最后一个线程到达屏障时在后开门唤醒全部线程,线程进入线程通过CyclicBarrier的await()方法
/**
* 6个修理工一起修理机械 修理完6个修理工一起回家
*/
@Test
public void test2(){
CyclicBarrier cyclicBarrier = new CyclicBarrier(6,()->{
System.out.println("修理完毕 可以回家了");//线程执行在够数量的线程执行到await(),放开栅栏前
});
for (int i=0; i<6; i++){
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"修理完毕");
cyclicBarrier.await();
System.out.println(Thread.currentThread().getName()+"回家");
} catch (Exception e) {
e.printStackTrace();
}
},"线程"+i).start();
}
}
countDownLatch和CyclicBarrier的区别
countDownLatch是wait()在主线程 等待所有分支线程执行完,CyclicBarrier是await在多线程中等待够数量的线程执行完一起执行之后的任务。
3.semaphore 信号灯
在信号量上我们定义两种操作
acpuire (获取)当一个线程获取信号时,要么成功获取信号,信号量减1,要么一直等待知道有空闲信号或超时
release(释放)信号量的值加一,唤醒等待的线程
信号量主要用于两个目的,一 多个共享资源互斥使用。 二 线程并发数的控制
/**
* 银行取钱问题10人取钱 3个柜台
*/
@Test
public void test3 (){
Semaphore semaphore = new Semaphore(3);
for (int i=0; i<10; i++){
new Thread(()->{
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"来到柜台取钱");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName()+"离开柜台");
semaphore.release();
}
},"线程"+i).start();
}
}
五.读写锁 ReentrantReadWriteLock
ReentrantReadWriteLock只有两个方法 readLock();writeLock();返回一个读锁或写锁,然后调用lock()或unlock()方法加锁解锁
读写锁的特征:
允许多个线程同时读共享变量;
只允许一个线程写共享变量;
如果有一个线程在执行写操作,那么禁止读操作。
六.BlockingQueue阻塞队列
当队列是空的,在队列中取元素会阻塞
当队列是满的,往队列中放元素会阻塞
试图在空的队列中取元素的线程会阻塞,直到其他线程往空的队列中放入新的元素
试图往满的队列中放元素的队列会阻塞,直到其他线程将队列中的元素取走,队列有空位
阻塞队列的用处
种类分类:
BlockingQueue核心方法
七.线程池
1.为什么使用线程池
曾经的单核电脑是假的多线程 实际上是一个cpu来回切换处理多个线程
现在多核电脑可以真正的多个线程跑在各自的cpu上,不用切换,效率高
2.线程池的优势
线程池做的工作只是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果任务数量超过最大线程数,则超出的任务将在队列中排队,等待其他线程执行完毕,在在队列中取出执行
优点:线程复用,控制最大并发,统一管理线程
降低资源的消耗:通过重复利用已创建的线程,降低创建销毁线程造成的损耗
提高响应速度:任务到达后,任务无需等待线程创建,就可以直接执行
提高线程管理性:线程是稀缺资源,无限制新建不仅会消耗系统资源还会降低系统稳定性,使用线程池可对线程统一分配,调优,监控
3.系统的几个默认线程池
//newFixedThreadPool的corePoolSize和maxinumPoolSize数量相等
//阻塞队列是lickedBlockingQueue 大小Integer.MAX_VALUE(可能堆积大量请求导致OOM)
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
//newSingleThreadExecutor newFixedThreadPool的corePoolSize和maxinumPoolSize数量为1
//但是阻塞队列是lickedBlockingQueue 大小Integer.MAX_VALUE(可能堆积大量请求导致OOM)
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
//newSingleThreadExecutor newFixedThreadPool为0 corePoolSize和maxinumPoolSize为Integer.MAX_VALUE(可能创建太多线程导致OOM)
//SynchronousQueue是一个内部只能包含一个元素的队列。插入元素到队列的线程被阻塞,直到另一个线程从队列中获取了队列中存储的元素
//意思是任务来了就创建线程,空闲60秒后销毁线程
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
4.线程池有7个参数:
5.什么是拒绝策略
当线程池的线程全部被占用 并且等待队列满了 ,塞不下新的任务,这是就要使用拒绝策略
以上拒绝策略均实现了RejectedExecutionHandler接口
一般我们应自己手动创建线程池
创建自定义线程池:
ExecutorService executorService = new ThreadPoolExecutor(3,
10,
60L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(20),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());