线程
java中线程的状态
1、NEW(初始化状态):通过new Thread()创建一个线程。
2.、RUNNABLE(可运行状态+运行状态):调用run方法,则进入可运行状态;若获得了cpu使用权,则从可运行状态变为运行状态;yield会让线程放弃cpu使用权,从运行状态变为可运行状态。
3、 BLOCKED(阻塞状态):竞争没拿到锁则进入阻塞;通过notify唤醒的线程也进入阻塞状态;若竞争到资源,则进入到RUNNABLE。
4.、WAITING(无时限等待+有时限等待):调用sleep、wait、join、LockSupport.park则线程进入等待状态, 可通过超时、notify、LockSupport.unPark唤醒,使线程进入RUNNABLE。
5.、TERMINATED(终止状态):线程执行结束。
常用方法和类
1、sleep:当前线程从 Running 进入TIMED_WAITING状态,不会释放对象锁;支持中断(会清除中断标志)。
2、yield:当前线程从 Running 进入RunableE状态,不会释放对象锁。
3、interrupt(): 将线程的中断标志位设置为true,不会停止线程。
5、isInterrupted(): 判断当前线程的中断标志位是否为true,不会清除中断标志位。
6、Thread.interrupted():判断当前线程的中断标志位是否为true,并清除中断标志位,重置为fasle。
中断说明:相比于stop,不管线程有没有执行完成,强制地关闭;通过中断interrupt可以更加优雅的来实现。需要关闭业务时通过Thread.currentThread().interrupt()来对当前线程发起中断;在正常业务中通过Thread.currentThread().isInterrupted()来监测是否被中断,若中断则结束业务流程。
7、Runnable:run方法是没有返回值的。
8、Callable:call方法是有返回值的,通过Future可以拿到执行的结果。
9、ThreadPoolExecutor线程池提交任务方法。
9.1、execute:是没有返回值的。
9.2、submit:是有返回值的,通过Future可以拿到执行的结果。
BlockingQueue阻塞队列
常用方法
1、poll:获取元素,不阻塞,队列没元素时,返回空。
2、offer:插入元素,不阻塞,队列满时,返回false。
3、take :获取元素,获取并移除队列的头结点,通常在队列里有数据的时候是可以正常移除的。可是一旦执行 take 方法的时候,队列里无数据,则阻塞,直到队列里有数据。一旦队列里有数据了,就会立刻解除阻塞状态,并且取到数据。
4、put :插入元素,如果队列没有满,那就和普通的插入一样是正常的插入,但是如果队列已满,那么就无法继续插入,则阻塞,直到队列里有了空闲空间。如果后续队列有了空闲空间,比如消费者消费了一个元素,那么此时队列就会解除阻塞状态,并把需要添加的数据添加到队列中。
ArrayBlockingQueue
基于数组结构实现的一个有界阻塞队列。
1、数组结构,容量固定,不支持扩容
2、插入元素和获取元素使用的是同一个ReentrantLock锁,因此存元素时不能插入元素,存取互相排斥
3、条件队列notEmpty:没有元素获取时,调用await阻塞在该队列上;当插入元素时,调用signal唤醒阻塞的获取元素的线程。
4、条件队列notFull:插入元素队列满了没容量时,调用await阻塞在该队列上;当获取元素时,调用signal唤醒阻塞的插入元素的线程。
LinkedBlockingQueue
基于链表结构实现的一个无界阻塞队列,指定容量则是有界阻塞队列。
1、链表结构,可以指定容量,不指定默认无限大,通过Node节点存储元素
2、锁分离:存取使用的是两个ReentrantLock锁,获取元素使用takeLock,插入元素使用putLock,提高了队列的吞吐量
3、条件队列notEmpty=takeLock.newCondition():没有元素获取时,调用await阻塞在该队列上;当插入元素时,调用signal唤醒阻塞的获取元素的线程。
4、条件队列notFull=putLock.newCondition():插入元素队列满了没容量时,调用await阻塞在该队列上;当获取元素时,调用signal唤醒阻塞的插入元素的线程。
Future
通常通过FutureTask来使用,FutureTask实现了Future
常用方法
1、boolean isDone():任务是否已经完成。
2、get():阻塞等待任务执行结束,然后获得结果。
Future的缺陷
1、并发执行任务,只能通过get阻塞获取结果
2、无法组合多个任务,如果你运行了多个任务,并期望在它们全部执行结束后执行特定功能,在Future中是做不到的。
使用示例
public static void main(String[] args) throws ExecutionException, InterruptedException {
Task task = new Task();
FutureTask<Integer> futureTask = new FutureTask<>(task);
new Thread(futureTask).start();
System.out.println("task运行结果:" + futureTask.get());
}
static class Task implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 10; i++) {
sum += i;
}
return sum;
}
}
CompletionService
主要功能就是边执行任务,边获取任务的返回值。让两件事分开执行,任务之间不会互相阻塞,可以实现先执行完的先取结果。
实现原理:通过阻塞队列和FutureTask来实现,内部有一个先进先出的阻塞队列,用于保存已经执行完成Future,通过调用它的take方法或poll方法可以获取到一个已经执行完成的Future,通过Future的get方法获取最终的结果。并且将线程池Executor和阻塞队列BlockingQueue的功能融合在了一起,能够让批量异步任务的管理更简单。
常用构造方法
1、runAsync(Runnable runnable, Executor executor);无返回值
2、supplyAsync(Supplier< U> supplier, Executor executor):有返回值,会阻塞
优势
1、批量提交异步任务的时候建议使用CompletionService
2、能够让异步任务的执行结果有序化。先执行完的先进入阻塞队列
3、支持自己创建线程池
4、实现了对任务的编排能力,可以自行组织不同任务的运行顺序、规则以及方式。
使用示例
CompletableFuture<Void> task1 = CompletableFuture
.runAsync(() -> {
System.out.println("task1执行...");
sleep(1, TimeUnit.SECONDS);
});
CompletableFuture<String> task2 = CompletableFuture
.supplyAsync(() -> {
System.out.println("task2执行...");
sleep(2, TimeUnit.SECONDS);
return "task2数据";
});
//任务3:任务1和任务2完成后执行
CompletableFuture<String> task3 = task1.thenCombine(task2, (__, tf) -> {
System.out.println("task2的结果: " + tf);
System.out.println("task1和task2执行完成后返回结果");
return "task3的结果";
});
//等待任务3执行结果
System.out.println(task3.join());