阻塞队列的实现
class myBlockingQueue {
private static String[] str = null;
private static int size = 0;
private static int head = 0;
private static int tile = 0;
Object locker = new Object();
public myBlockingQueue(int n) {
str = new String[n];
}
public void put(String s) throws InterruptedException {
synchronized (locker) {
while(size >= str.length) {
locker.wait();
}
str[tile] = s;
tile++;
if(tile >= str.length) {
tile = 0;
}
size++;
locker.notify();
}
}
public String take() throws InterruptedException {
String s = null;
synchronized (locker) {
while(size == 0) {
locker.wait();
}
s = str[head];
head++;
if(head >= str.length) {
head = 0;
}
size--;
locker.notify();
}
return s;
}
}
这是阻塞队列的基本实现。
实现定时器
schedule方法中,第一个参数是到时间要执行的代码,第二个参数是时间。我们还是要自己实现一个类似功能的计时器。
//方法中包含要执行的代码和执行时间
class MySch implements Comparable{
private Runnable runnable;
long time;
public MySch(Runnable runnable, long delay) {
this.runnable = runnable;
this.time = System.currentTimeMillis()+delay;
}
public void run() {
this.runnable.run();
}
@Override
public int compareTo(MySch o) {
return (int)this.time-(int)o.time;
}
}
class MyTimer {
//一个优先级阻塞队列来存放任务,存放的类型为Rannable,long两个类型的schdule类
private PriorityBlockingQueue queue= new PriorityBlockingQueue<>();
//创建线程来扫描队列
private Thread t = null;
private Object locker = new Object();
public void schdule(Runnable runnable, long delay) {
synchronized (locker) {
MySch mySch = new MySch(runnable,delay);
queue.add(mySch);
locker.notify();
}
}
public void cancel() {
t.interrupt();
}
//构造方法创建线程来执行任务
public MyTimer() {
t = new Thread(() -> {
while(!Thread.currentThread().isInterrupted()) {
synchronized (locker) {
while(queue.isEmpty()) {
try {
locker.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
MySch mySch = queue.peek();
if(mySch.time <= System.currentTimeMillis()) {
mySch.run();
queue.poll();
} else {
try {
locker.wait(mySch.time-System.currentTimeMillis());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
});
t.start();
}
}
public class ee_16 {
public static void main(String[] args) {
MyTimer myTimer = new MyTimer();
myTimer.schdule(new Runnable() {
@Override
public void run() {
System.out.println("3000");
}
},3000);
}
}
这是我们实现的一个简单的计时器功能。
这里我们用一个优先级队列来存放我们要执行的任务,在构造方法中有一个线程来执行队列中的代码,而队列里面存放的元素包含两个内容,执行的内容和执行的时间。
线程池
我们为了解决进程开销过大引入了轻量级进程,也就是线程,但是线程数目过多也会影响速度,所以我们又有两种解决方法
1.轻量级线程---纤程/协程
2.线程池
我们这里结束的就是第二种,第一种在Java21中引入的虚拟线程其实就是那个意思。
线程池就是我们在使用后没有销毁而是放到线程池中,使用在拿到即可。
那为什么从线程池中拿比申请块呢?
这是因为从线程池中拿是用户态代码,而创建时内核态代码,用户态代码更可控
标准库中的线程池
这是它构造方法带有的参数
corePoolSize:核心线程数,即线程池中最少要存放的线程个数
maximumPoolSize:最大线程数,即线程池中最大存放的线程个数
keepAliveTime:非必要线程最多空闲下来的时间。
unit:单位,和keepAliveTime一起。
workQueue:存放执行的任务
threadFactory:线程工厂,用来创建线程
handler:拒绝策略,当队列满了在添加任务时的处理方法
AbortPolicy:报异常,两个都不做
CallerRunsPolicy:添加的线程由调用方执行,自己继续之前线程
DiscardOldestPolicy:丢弃老的线程执行新的
DiscardPolicy:丢弃新的线程(和第二个区别于这个新的线程不干),执行旧的线程。
class MyThreadPoolExecutor {
//顺序表存放线程
List list = new ArrayList<>();
//阻塞队列存放任务
private BlockingQueue queue = new ArrayBlockingQueue<>(1000);
//submit方法添加任务
public void submit(Runnable runnable) throws InterruptedException {
queue.put(runnable);
}
//构造方法启动多个线程来执行任务
public MyThreadPoolExecutor(int n) {
for (int i = 0; i < n; i++) {
Thread t = new Thread(() -> {
while(true) {
try {
Runnable runnable = queue.take();
runnable.run();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
list.add(t);
}
}
}
public class ee_19 {
public static void main(String[] args) throws InterruptedException {
MyThreadPoolExecutor myThreadPoolExecutor = new MyThreadPoolExecutor(10);
for (int i = 0; i < 1000; i++) {
int n =i;
myThreadPoolExecutor.submit(new Runnable() {
@Override
public void run() {
System.out.println(n);
}
});
}
}
}
因为ThreadPoolExecutor这个类本身用起来比较复杂所以Java提供了一个类Exectors这个类是一个工厂类,就是对ThreadPoolExector进行封装。
我们在创建线程池的时候用的关键字是ExectorService来创建。
这里主要的方法有newFixedThreadPool(int n)这个方法代表创建的线程数目是固定的。newCachedThreadPool()这个方法代表创建的线程池线程数可以扩容。newSingleThreadPool()这个方法代表单一线程池,即线程池中只包含一个线程。newScheduledThreadPool()这个方法就是定时器类似物,也可以起到延迟执行的作用。
那么我们什么时候使用Exectors什么时候使用ThreadPoolExector???
我们在简单实用一下的时候可以使用Exectors,我们高度定制化就需要使用ThreadPoolExector了。
那我们在使用的时候要创建多少个线程数呢?
这个问题没有一个固定的答案,这取决于线程是I/O密集型还是CPU密集型。CPU密集型占用cpu,而I/O密集型大部分时间在等待。所以我们应该实际尝试来确定线程数。