多线程
1.关于多线程的案例--定时器
像是一个闹钟,进行定时,在一定时间之后,被唤醒并执行某个之前设定好的任务
join(指定超时时间)和sleep(休眠指定时间)都是使用定时器实现的(系统的定时器)
(1)标准库的定时器
java.util.Timer
核心方法就一个,schedule(安排),参数有两个:任务是什么,多长时间后执行
public class demo7 {
public static void main(String[] args) {
Timer timer=new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello,timer");
}
},3000);
System.out.println("main");
}
}
schedule有两个参数,第一个参数是一个任务,我们可以new一个TimerTask(),TimerTask这个类实现了Runnable接口,他表示一个任务,重写的run方法就是任务。第二个参数是暂停的时间。
(2)手动实现定时器
Timer内部需要什么?
1.描述任务,创建一个专门的类来表示一个定时器中的任务(TimerTask)
2.组织任务,把任务放到一起
3.执行时间到了的任务
先构造一个任务类,用来描述任务,需要两个属性:任务(Runnable)时间(long),提供构造方法,并在构造方法中将时间做成系统时间(System.currentTimeMills+delay)
class MyTimerTask implements Comparable<MyTimerTask>{
// 任务
private Runnable runnable;
//任务执行的时间
private long time;
public MyTimerTask(Runnable runnable, long delay) {
this.runnable = runnable;
this.time = System.currentTimeMillis()+delay;
}
public void run(){
runnable.run();
}
public long getTime() {
return time;
}
@Override
public int compareTo(MyTimerTask o) {
//时间小的在前面,是this减o,时间大的在前,是o减this
return (int) (this.time-o.time);
}
}
注意:这个类需要实现Comparable接口,因为我们的任务是用PriorityBlockingQueue存放的,这个队列是需要对任务进行排序的,我们需要表明我们的任务排序规则(时间),所以需要实现Comparable接口,重写这个comparaTo方法(小的在前就this-o,大的在前就o-this)。
然后构造一个组织任务的类,因为我们要按顺序进行执行,所以此处选择的数据结构是PriorityBlockingQueue,我们需要设置一个线程,用来检查队列中是否有任务需要被执行,所以我们在构造方法中声明了一个线程,对队列不断进行检查。
//2.组织任务任务
class MyTimer{
//安排任务的时候,任务的顺序是无序的,但是我们需要按顺序执行
//选择堆进行保存任务PriorityQueue
//此处的队列考虑到线程安全问题->
// 由于我们可能在多个线程中执行注册任务,还有线程来执行任务,此处的队列就需要注意线程安全问题
private PriorityBlockingQueue<MyTimerTask> Queue=new PriorityBlockingQueue<>();
public void schedule(Runnable runnable,long delay){
MyTimerTask myTimerTask=new MyTimerTask(runnable,delay);
Queue.put(myTimerTask);
//再插入任务之后,重新进行检查
synchronized (locker){
locker.notify();
}
}
//3.执行时间到了的任务
//需要有一个线程来不停的检查当前优先队列的队首元素,检查时间是否到
private Object locker =new Object();
public MyTimer(){
Thread t=new Thread(()->{
while(true){
try {
//忙等->既没有实质性的产出,又没闲着->浪费CPU
//解决办法:利用wait指定等待时间(不需要notify)
MyTimerTask task=Queue.take();
long curTime=System.currentTimeMillis();
if(curTime< task.getTime()){
//如果任务没到,就把任务加回去
Queue.put(task);
//指定一个等待时间
synchronized (locker){
locker.wait(task.getTime()-curTime);
}
}else {
task.run();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
}
}
注意:此处这个线程一直在检查队列的任务,但是没有必要,这是一种忙等现象,既没有产出又没有闲着。因此会设置一个锁对象locker,在线程检查任务事件后,我们将locker.wait(执行的时间-现在的时间)让线程不用一直检查,等待一会。但是这样有可能会有更靠前更新的任务进队列,所以我们需要在每一次任务入队列成功后,notify这个锁对象,让线程进行检查。
2.关于多线程的案例--线程池
线程,虽然比进程轻了,但是如果创建销毁的频率进一步增加,仍然会发现开销还是挺大的,解决办法:协程or线程池
把线程提前创建好了,放到池子里,后面需要线程直接从池子中取,不必向系统申请了
系统整体架构图
我们一般认为用户态的操作比内核态的操作要效率高。(并不是说内核态操作慢,而是因为用户态的操作是可控的)
(1)标准库的线程池--ThreadPoolExecute
ThreadPoolExecute的构造方法:
int corePoolSize-> 核心线程数
int maxmumPoolSize->最大线程数(核心线程和非核心线程)
long keepAliveTime->允许非核心线程“摸鱼”的时间
TimeUnit unit->时间的单位(s,ms,us......)
BlockingQueue<Runnable> workQueue->任务队列,线程池会提供一个submit方法,程序员会把任务注册到线程池中,加到这个任务队列中
ThreadFactory threadFactory->线程工厂,线程创建
RejectedExecutionHandler handler->拒绝策略,当任务满了,怎么做?
虽然线程池的参数很多,但是最重要的参数还是第一组参数,线程数量->如何确定数量?
正确做法:要通过性能测试的方法,找到合适的值,当线程多了,整体速度会变快,但是CPU占用率会增高,当线程数少了,整体速度会变慢,但是CPU占用率变低,所以我们要通过性能测试,找到一个平衡点。
(2)标准库中简化版本的线程池--Executors
本质上是针对ThreadPoolExecute进行的简化版本,提供了一些默认值
public class demo9 {
public static void main(String[] args) {
//创建一个固定数目的线程池
ExecutorService pool=Executors.newFixedThreadPool(10);
//创建一个自动扩容的线程池
Executors.newCachedThreadPool();
//创建一个一个线程的线程池
Executors.newSingleThreadExecutor();
//创建一个有定时器功能的线程池,类似于Timer
Executors.newScheduledThreadPool();
for (int i = 0; i < 100; i++) {
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
}
}
打印结果:十个线程对这些任务分别进行执行。
(3)手动实现线程池
线程池需要什么:
1.描述任务->直接用Runnable就好
2.需要组织任务->BlockingQueue
3.描述工作线程
4.组织工作线程
5.需要实现往线程池中添加任务
class MyThreadPool{
//1.描述任务
private Runnable runnable;
//2.组织任务
private BlockingDeque<Runnable> Queue=new LinkedBlockingDeque<>();
//3.描述线程
static class Worker extends Thread{
private BlockingDeque<Runnable> Queue=new LinkedBlockingDeque<>();
public Worker(BlockingDeque<Runnable> Queue){
this.Queue=Queue;
}
@Override
public void run() {
while(true){
try {
//如果队列为空就阻塞
Runnable runnable=Queue.take();
runnable.run();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
//4.使用数据结构组织线程
private List<Thread> Workers=new ArrayList<>();
public MyThreadPool(int n){
for (int i = 0; i < n; i++) {
Worker worker=new Worker(Queue);
worker.start();
Workers.add(worker);
}
}
//5.创建一个方法,能够允许程序员放任务到线程池中
public void submit(Runnable runnable){
try {
Queue.put(runnable);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}