一、Java 并发的发展进程
1. 在JDK 1.x release的早期版本,就确立了 Java 最基础的线程模型
具有代表性的类和接口有:
- java.lang.Thread
- java.lang.ThreadGroup
- java.lang.Runnable
- java.lang.Process
java.lang.ThreadDeath
异常类
java.lang.IllegalMonitorStateException
- java.lang.IllegalStateException
java.lang.IllegalThreadStateException.
2. JDK5发布了许多特性功能: 泛型 Generic、枚举类型 Enumeration、可变参数varargs、注解 Annotations等等
在并发方面:提供了java.util.concurrent(并发包)
- 原子(Atomic)类型
- 显式锁(Lock)接口
- 计数器(CountDownLatch)
- 回环栅栏(CyclicBarrier)
- 信号量(Semaphore)
- 并发集合(concurrent collections)
- Callable和Future接口
- 执行器(Executor接口)
3. JDK7
- 添加ForkJoinPool(工作窃取技术)框架的支持
- 添加Phaser类,是可重用的同步屏障,类似于CountDownLatch和CyclicBarrier
-
4. JDK8
加法器(Adder)和累加器(Accumulator):原子类型的扩充与优化,主要有:LongAdder、LongAccumulator、DoubleAdder和DoubleAccumulator,比AtomicLong和AtomicDouble性能更优。
- CompletableFuture:JDK5中Future的增强版。
- StampedLock:JDK5中ReadWriteLock的改进版。
二、了解相关概念
1. 线程与进程的区别?
- 进程(process)和线程(thread)是操作系统的基本概念
- 现代操作系统调度的最小单元是线程,也叫轻量级进程(Light Weight Process)
- 线程都拥有各自的计数器、堆栈和局部变量等属性,并且能够访问共享的内存变量
- 一个进程可以包含多个线程
2. 并发(concurrency)和并行(parallelism)的区别?
- 并发是两个任务在重叠的时间(交替)内开始,运行和完成; 而并行是两个任务能在多核CPU 下,同一时间开始,运行和完成
- 并发是马上处理许多任务,而并行是马上开始执行多个任务
三、了解CAS算法
1. CAS 涉及的三个操作数
3. CAS引发的问题
- ABA问题,一个线程将内存值从A改为B,另一个线程又从B改回到A
- 图示
- 解决方法:在变量前面添加版本号,每次变量更新的时候都将版本号加1,比如juc的原子包中的AtomicStampedReference类。
- 循环时间长开销大:CAS算法需要不断地自旋来读取最新的内存值,长时间读取不到就会造成不必要的CPU开销。
- 只能保证一个共享变量的原子操作(jdk1.5的AtomicReference来保证应用对象之间的原子性,可以把多个变量放在一个对象里来进行CAS操作,解决了这一问题)。
四、了解Java的中线程
1. 简单线程的创建和启动
//继承Thread类
public class MyThread extends Thread{
@Override
public void run() {
System.out.println("线程运行");
}
public static void main(String[] args) {
MyThread myThread=new MyThread();
myThread.start();
}
}
//实现Runable接口
public class MyRunable implements Runnable{
@Override
public void run() {
System.out.println("线程运行");
}
public static void main(String[] args) {http://moguhu.com/article/detail?articleId=39
Thread t=new Thread(new MyRunable());
t.start();
}
}
2. 了解线程的生命周期
对上图进行对线程的生命周期的分析:
- (1)新建状态(NEW):线程被创建出来,还没有调用start方法;
- (2)可运行状态(RUNABLE):调用线程的start方法,线程等待CPU时间
- (3)运行状态(RUNNING):线程获取到CPU时间,开始执行线程任务
- (4)等待状态(WAITING):阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice ,暂时停止运行。直到线程进入可运行状态,才有机会再次获得 cpu timeslice 转到运行状态。
- (5) 等待超时状态(TIME_WAITING): 超时等待状态,与WAITING不同,它可以在指定时间自行退出,释放锁
- (6) 死亡(TERMINATED):线程 run ()、 main () 方法执行结束,或者因异常退出了 run ()方法,则该线程结束生命周期。死亡的线程不可再次复生。
- 等待阻塞(BLOCKED)的情况分三种:
- A:等待阻塞:线程调用wait方法,释放线程占有的锁,JVM把线程加入等待队列
- B:同步阻塞:线程尝试获取对象锁的时候,但是该锁被别的线程占有,那么JVM把该线程加入锁池
- C:其它阻塞:线程调用sleep,join方法(不会释放锁),请求I/O时,JVM把线程设为阻塞状态,当休眠时间到了,join等待的线程超时或者完成任务,I/O处理完毕,线程重新进入运行状态,等待CPU时间
3. 线程的优先级
- 在Thread 类中定义了属性 priority 用来控制线程的优先级
- 优先级的范围从1~10,在线程构建的时候可以通过setPriority(int)方法来修改优先级,默认优先级是5,优先级高的线程分配时间片的数量要多于优先级低的线程。
注意:线程优先级不能作为程序正确性的依赖,因为操作系统可以完全不用理会Java线程对于优先级的设定
4. 守护线程
public class DaemonThread {
public static void main(String[] args) {
Thread thread = new Thread(new DaemonRunner(), "DaemonRunner");
thread.setDaemon(true); //开启守护线程
thread.start();
}
static class DaemonRunner implements Runnable {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//这里不会输出
System.out.println("DaemonThread finally run.");
}
}
}
}
注意:
- Daemon属性需要在启动线程之前设置,不能在启动线程之后设置。不然会抛出IllegalThreadStateException 异常
- 当一个Java虚拟机中不存在非Daemon线程的时候,Java虚拟机将会退出,意味着Daemon 线程会被中断
- 所以,在构建Daemon线程时,不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑
到此, Java 多线程已经入门,接下来要先了解线程的抽象内存模型。
参考
- https://blog.csdn.net/hanchao5272/article/details/79521731
- https://howtodoinjava.com/java/multi-threading/java-multi-threading-evolution-and-topics/
- https://docs.oracle.com/javase/8/docs/technotes/guides/concurrency/changes8.html
- https://www.itcodemonkey.com/article/1830.html
- https://mp.weixin.qq.com/s/-gvhklcWGO5aPiFaBLpp3g