本篇文章做为Java并发系列的第一篇,并不会介绍相应的api,只是简单的提到多线程关键线程的通讯和同步,后续文章会详细介绍其中的原理编程
由于Java线程采用这种一对一映射内核线程方式实现,因此Java线程调度没法经过设置优先级控制(线程调度采用抢占式)api
状态 | 说明 |
---|---|
NEW | 初始状态,线程被构建尚未调用start()方法 |
RUNNABLE | 运行状态,Java中运行和就绪统称运行中,可能正在执行,也可能在等待CPU时间片 |
WAITING | 等待状态,这种状态的线程不会被分配CPU时间片,须要等待其余线程显式唤醒 |
TIMED_WAITING | 超时等待状态,不一样于等待状态的是在必定时间以后它们会由系统自动唤醒 |
BLOCKED | 阻塞状态,表示线程在等待对象的monitor锁,试图经过synchronized去获取某个锁 |
TERMINATED | 终止状态,表示当前线程已执行完毕 |
💡小提示: 操做系统中的运行和就绪两个状态在Java中合并成运行状态,LockSupport类的park()方法会使当前线程进入等待状态,因为并发包中的ReentrantLock和condition等并发工具使用的是LockSupport的park(),因此阻塞于它们的线程不一样于阻塞于synchronized的线程是处于阻塞状态,而是等待状态;关于这些状态的转换你们能够写个小demo试一试,利用jstack查看线程状态。 缓存
推荐使用线程池管理线程,线程池有如下好处bash
💡小提示:尽可能不要使用Executors来建立线程池,由于使用的无界队列会发生内存溢出,具体缘由后续章节会介绍多线程
💡小提示:线程私有的本地内存是一个缓存、写缓冲等的抽象概念并发
Java中的线程通讯方式有如下几种:工具
线程间的通讯方式:共享内存、消息队列;学习
//等待通知模式的经典范式:
synchronized (object) {
while (boolean) {
object.wait();
}
doSomeThing();
}
synchronized (object) {
change boolean;
object.notify();
}
复制代码
💡小提示:等待处使用while而不是if是为了防止错误或者提早的通知 ui
Java中实现线程同步的方式:spa
除了synchronized其它几种同步方式的实现都与AQS有关,后续文章中会详细介绍,下面给你们来个demo感觉下本篇文章的内容。
/**
* @author XiaMu
*/
public class FutureTaskDemo {
private static volatile Integer count = 0;
private static CountDownLatch countDownLatch = new CountDownLatch(500);
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 不推荐,为了方便使用了
ExecutorService executorService = Executors.newFixedThreadPool(10);
List<FutureTask<Integer>> resultList = new ArrayList<>(500);
for (int i = 0; i < 500; i++) {
FutureTask<Integer> task = new FutureTask<>(() -> {
// lock.lock();(1)
int result = count++;
// lock.unlock();(1)
// countDownLatch.countDown();(2)
return result;
});
executorService.submit(task);
resultList.add(task);
}
// countDownLatch.await();(2)
System.out.println("第一处计算结果:" + count);
// 为了查看每一个任务的执行结果
Map<Integer, Integer> resultMap = new HashMap<>(500);
for (FutureTask<Integer> result : resultList) { // (3)
Integer sum = result.get();
if (resultMap.containsKey(sum)) {
System.out.println(sum + "计算过了");
} else {
resultMap.put(sum, 1);
}
}
System.out.println("第二处计算结果:" + count);
executorService.shutdown();
}
}
复制代码
取两次执行结果:
第一处计算结果:0 第一处计算结果:270
135计算过了 492计算过了
163计算过了 16计算过了
454计算过了 437计算过了
458计算过了 445计算过了
463计算过了 第二处计算结果:496
470计算过了
317计算过了
319计算过了
第二处计算结果:492
复制代码
💡:毫无疑问,计算结果出现了错误,能够将(1)注释打开加锁保证计算正确。第一处计算结果不等于第二处是由于主线程执行到第一处时,子任务并无执行完。若是没有(3)的for循环中获取结果的Future.get()阻塞主线程,那么第二处计算结果打印时子任务可能还没执行完。注释(2)打开能够解决两次打印不一致问题,由于CountDownLatch会将主线程阻塞直到第500个子任务执行完毕。
💡:使用集合时最好根据状况指定初始容量,不然在后续的操做中会发生频繁扩容影响效率
【本篇文章只是讲了个大概,若是小伙伴们发现有问题或者疑惑的地方欢迎指出,一块儿学习进步!】
参考:《Java并发编程的艺术》《码出高效》