手写线程池:
线程池的优势:
线程池做的工作只要是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。
它的主要特点为:线程复用;控制最大并发数;管理线程。
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的销耗。
第二:提高响应速度。当任务到达时,任务可以不需要等待线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会销耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
例:
1、在创建了线程池后,开始等待请求。
2、当调用execute()方法添加一个请求l0-0任务时,线程池会做出如下判断:
2.1如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
2.2如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列;
2.3如果这个时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
2.4如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。
3、当一个线程完成任务时,它会从队列中取下一个任务来执行。
4、当一个线程无事可做超过一定的时间(keepAliveTime)时,线程会判断:
如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。
所以线程池的所有任务完成后,它最终会收缩到corePoolSize的大小。
package com.thekingqj;
import java.util.concurrent.*;
public class MyThreadPoolExecutor {
public static void main(String[] args) {
ExecutorService threadPool = new ThreadPoolExecutor(
2,//核心线程数
5,//最大核心线程数
1L,//非核心线程且在没有使用的情况下的超时时间
TimeUnit.SECONDS,//超时时间的单位
new LinkedBlockingQueue<>(3),//阻塞队列类型和大小
Executors.defaultThreadFactory(),//常见线程的工厂(一般情况下是默认)
new ThreadPoolExecutor.AbortPolicy()//线程池饱和后启动的拒绝策略类型
);
for (int i = 0; i < 9; i++) {
final int temp = i;
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "\t" + temp);
});
}
threadPool.shutdown();
}
}
/** * 注意线程池这三个都不用,他们各自有各自的缺点,线程池需要自己手写 */ ExecutorService threadPool = Executors.newFixedThreadPool(3);//一池指定线程数 注意:底层的阻塞队列是LinkedBlockingQueue,他的容量接近无限大 ExecutorService threadPool1 = Executors.newSingleThreadExecutor();//一池一线程 注意:底层的阻塞队列是LinkedBlockingQueue,他的容量接近无限大 ExecutorService threadPool2 = Executors.newScheduledThreadPool(3);//一池N个线程 注意,该线程池的最大线程数量接近无限大
阻塞队列:
抛出异常 | 当阻塞队列满时,再往队列里add插入元素会抛IllegalStateException:Queue full 当阻塞队列空时,再往队列里remove移除元素会抛NoSuchElementException |
特殊值 | 插入方法,成功ture失败false 移除方法,成功返回出队列的元素,队列里没有就返回null |
一直阻塞 | 当阻塞队列满时,生产者线程继续往队列里put元素,队列会一直阻塞生产者线程直到put数据or响应中断退出 当阻塞队列空时,消费者线程试图从队列里take元素,队列会一直阻塞消费者线程直到队列可用 |
超时退出 | 当阻塞队列满时,队列会阻塞生产者线程一定时间,超过限时后生产者线程会退出 |
线程8锁:
1. 标准访问,问先打印邮件还是短信
2. email方法新增暂停3秒冲,问先打印邮件还是短信
3. 新增普通方法hello,问先打印email还是hello
4. 两部手机,问问先打印邮件还是短信
5. 两个静态的同步方法,一部手机,问问先打印邮件还是短信
6. 两个静态的同步方法,两部手机,问问先打印邮件还是短信
7. 一个静态同步方法,一个普通同步方法,一部手机,问问先打印邮件还是短信
8. 一个静态同步方法,一个普通同步方法,两部部手机,问问先打印邮件还是短信
package com.thekingqj;
import java.util.concurrent.TimeUnit;
class Phone {
//synchronized锁的是整个资源(当前对象/方法区中的类资源(Class))
//(加锁时)同一时间段只能有一个线程进入,访问
public static synchronized void sendEmail() throws InterruptedException {
TimeUnit.SECONDS.sleep(2);
System.out.println("****sendEmail");
}
public static synchronized void sendSMS() {
System.out.println("****sendSMS");
}
public void hello() {
System.out.println("*****Hello");
}
}
/**
* 多线程的8锁
*
* <p>
*
*/
public class Lock8 {
public static void main(String[] args) {
Phone phone = new Phone();
Phone phone1 = new Phone();
new Thread(() -> {
try {
Phone.sendEmail();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
Phone.sendSMS();
//phone.hello();
}, "B").start();
}
}
答案
1-2
一个对象 里面如果有多个synchronized方法,某一个时刻内, 只要一个线程去 调用其中的一个synchronized 方法了,
其它的线程都只能等待,换句话说,某一个时刻内,只能有唯一一个线程去 访问这些synchronized方法
锁的是当前对象this,被锁定后,其它的线程都不能进入到当前对象的其它的synchronized方法
3-4
加个普通方法后发现和同步锁无关
换成两个对象后,不是同一把锁了,情况立刻变化。
5-6
都换成静态同步方法后,情况又变化
若是普通同步方法,new
this,具体的一 部部手机, 所有的普通同步方法用的都是同一把锁实例对象本身,
若是静态同步方法,static Class ,唯一的一个模板
synchronized.是实现同步的基础: Java 中的每一个对象 都可以作为锁。
具体表现为以下3种形式。
对于普通同步方法,锁是当前实例对象。它等同于对于同步方法块,锁是synchonized括 号里配置的对象。
对于静态同步方法,锁是当前类的class对象本身,
7-8
当一个线程试图访问同步代码时它首先必须得到锁,退出或抛出异常时必须释放锁。
所有的普通同步方法用的都是同一-把锁一 实例对象本身, ,就是new出来的具体实例对象本身
也就是说如果一个实例对象的 普通同步方法获取锁后,该实例对象的其他普通同步方法必须等待获取锁的方法释放锁后才能获取锁,
可是别的实例对象的普通同步方法因为跟该实例对象的普通同步方法用的是不同的锁,所以不用等待该实例对象已获取锁的普通
同步方法释放锁就可以获取他们自己的锁。
所有的静态同步方法用的也是同一-把锁-类对象本身, 就是我们说过的唯- -模板class
具体实例对象this和唯一-模板class, 这两把锁是两个不同的对象,所以静态同步方法与普通同步方法之间是不会有竞态条件的。
但是一旦,一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,