进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
线程(Thread)是操作系统能够进行运算调度的最小单位。
——百度百科
在操作系统中,每打开一个应用程序(如QQ、网易云音乐),就是启动了一个进程,操作系统会为它们分配内存空间,并按照时间片算法来调度它们,由于CPU计算非常快,视觉感官上会认为是多个程序在同时运行。
每个应用程序都可以同时执行不同的任务,如QQ的聊天、上传文件、下载文件等,每个任务都交给不同的线程处理。
首先,多个线程并发执行,在很多场景下都能大大提高CPU资源的利用率。
对于IO密集型作业,一个任务很可能只执行1ms的CPU,然后花50ms的时间去请求外部资源了(如查数据库、调用RPC),如果没有多线程,程序只能大眼瞪小眼地干等待。而多线程的引入,在线程1请求外部资源时,可以让出CPU给其他线程,从而大大程序整体运行效率。
对于CPU密集型作业,多线程的存在依然是十分重要的,若存在一个新来的小任务,只需要执行不到1ms,而当前CPU正在处理一个大任务,需要很长时间,这时候引入多线程,就能尽快把小任务处理完,避免小任务长时间等待。
其次,目前CPU大多数都支持多核技术,多线程的引入能充分利用多核优势,进一步提高整体性能。
NEW:线程创建状态
RUNNABLE:可运行状态,调用start()方法后进入该状态,可分为:
RUNNING状态,即线程获取到CPU调度资源时
READY状态,即就绪状态,可以随时被CPU调度执行
yeild()方法,主动释放CPU资源,线程状态由RUNNING转为READY,也可能刚释放又获得CPU调度,线程状态由REDAY转为RUNNING
WAITING:等待状态。
Object.wait() && Object.notify()、Object.notifyAll()
线程只有在获取当前对象的锁时,才能调用该方法,否则抛出IllegalMonitorStateException异常
该方法调用后会释放当前的锁
只有在被其他线程notify()或notifyAll()时唤醒
唤醒后进入BLOCKED状态,只有重新获得对象锁,才能继续往下执行
thread.join()
调用该方法的线程(如main线程),会等待thread线程执行结束,thread线程执行结束后,会调用notifyAll()方法
join()方法底层调用了wait()方法
注:上图的Object.join()是不对的,join()方法只属于Thread类
LockSupport.park() && LockSupport.unpark(thread)
park()方法阻塞当前线程,不释放锁
unpark(thread)方法唤醒指定线程
TIMED_WAITING:超时等待状态
Thread.sleep(timeout)
不释放锁,睡一会,timeout后醒来
可被其他线程调用interrupt()中断
Object.wait(timeout)
释放锁
等待timeout,若有notify()或notifyAll(),马上醒来;否则timeout时间过后,自动醒来
Thread.join(timeout)
会阻塞,释放锁
timeout时间过后自动唤醒
当成功获取Thread对象的锁之后,才继续向下执行
LockSupport.parkNanos(timeout) 、LockSupport.parkUntil(time)&&LockSupport.unpark(thread)
parkNanos() :休眠timeout,除非被unpark(thread)唤醒
parkUntil(time):休眠到time时间点
BLOCKED:阻塞状态
当线程获取锁失败时,如synchronized方法、synchronized代码块
执行wait()/wait(timeout)方法的线程被唤醒后
执行join(timeout)方法的线程被唤醒后
TERMINATED:终止状态
线程正常执行完
Thread.currentThread():获取当前执行的线程
isAlive():判断线程是否存活
StackTraceElement[] getStackTrace():返回线程的堆栈跟踪元素数组。如多层调用了方法,则将方法信息层层压栈
dumpStack():将当前线程的堆栈信息输出至标准错误流
getId():获取线程唯一数字标识。main线程的id是1,新建一个线程id从11开始递增,说明2~9由隐藏线程占有
getName():获取线程名称。常用于分析线程执行情况
holdsLock(Obj):若当前线程成功获取obj的锁时,返回true,否则返回false
废弃的方法:
stop():停止线程。暴力停止,有可能造成资源未关闭
suspend()和resume():挂起和恢复。可能造成无法预料到结果。可使用LockSupport.park()和LockSupport.unpark(thread)来实现线程的暂停与继续
wait、sleep、join都会抛出InterruptedException异常,而LockSupport.park会吞噬异常。
可见性:一个线程对共享变量的修改,马上对其他使用该共享变量的线程可见
禁止指令重排序:volatile变量的读写,会加上内存屏障,阻止编译器对volatile变量前后的指令进行重排序:
代码块
- a = 12;
- b = 1;
- volatile c = 0; // 1~2行 与 4~5行不能重排序,但1~2内部、4~5内部可以重排序
- d = a;
- e = b;
synchronized是一个悲观锁、独占锁,一次只能由一个线程执行synchronized所在的代码块。
1. 对象的实例方法
- public class Obj {
- public synchronized void test() {
- // 锁住的是当前Obj对象
- }
-
- public void test2() {
- synchronized(this) {
- // 锁住的是当前Obj对象
- }
- }
- }
2. 对象的静态方法
- public class Obj {
- public static synchronized void test() {
- // 锁住的是当前Obj.class对象
- }
-
- public void test2() {
- synchronized(Obj.class) {
- // 锁住的是当前Obj.class对象
- }
- }
- }
线程优先级分为1~10级,默认是5,当线程A创建另一个线程B时,也即A是B的父线程,B线程继承A线程的优先级。
线程的优先级越高,被JVM调用的几率也会越大。
守护线程:为非守护线程提供服务,如GC保洁线程
非守护线程:实际工作处理任务的线程
当JVM中只剩守护线程时,系统会停止,因为没有非守护线程可以服务了。
当需要某种条件不满足时,执行wait方法等待;当条件满足时,由其它线程唤醒等待的线程。wait/notify机制就是为了控制线程“条件不满足则等待,条件满足则唤醒、重新获取锁、继续执行”。
(1)一个生产者&一个消费者
- public class OneProducerOneConsumer {
- Object obj = new Object();
- Object resource = null;
- public void consume() {
- synchronized(obj) {
- if (resource == null) {
- try {
- obj.wait(); // 没有产品,则等待
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- System.out.println(Thread.currentThread().getName()+"消费了:"+resource);//消费一个产品
- resource = null;
- obj.notify();//通知生产
- }
- }
-
- public void produce() {
- synchronized(obj) {
- if (resource != null) {
- try {
- obj.wait();// 产品未消费完,则等待
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- resource = "demo_" + new Random().nextInt(); //生产一个产品
- System.out.println(Thread.currentThread().getName()+"生产了:"+resource);
- obj.notify();//通知消费
- }
- }
-
- static class Consumer extends Thread {
- private OneProducerOneConsumer demo;
- public Consumer(OneProducerOneConsumer demo) {
- this.demo = demo;
- }
-
- @Override
- public void run() {
- while(true) {
- demo.consume();
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
-
- static class Producer extends Thread {
- private OneProducerOneConsumer demo;
- public Producer(OneProducerOneConsumer demo) {
- this.demo = demo;
- }
-
- @Override
- public void run() {
- while(true) {
- demo.produce();
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
-
- public static void main(String[] args) {
- OneProducerOneConsumer demo = new OneProducerOneConsumer();
- new Consumer(demo).start();
- new Producer(demo).start();
- }
- }
结果如下,每生产一个产品,就消费一个产品,生产与消费总是成对出现。
- Thread-1生产了:demo_1806630852
- Thread-0消费了:demo_1806630852
- Thread-1生产了:demo_240542868
- Thread-0消费了:demo_240542868
- Thread-1生产了:demo_-1755077240
- Thread-0消费了:demo_-1755077240
- Thread-1生产了:demo_-1253326727
- Thread-0消费了:demo_-1253326727
(2)多个生产者&多个消费者
- public class ManyProducerManyConsumers {
- Object consumerLock = new Object(); // 一次一个消费者
- Object producerLock = new Object(); // 一次一个生产者
- Object notEmpty = new Object();
- Object notFull = new Object();
- Object lock = new Object();// 资源互斥访问container资源
- List
container = new ArrayList<>(FULL_SIZE); - final static int FULL_SIZE = 10;
-
- public void consume() {
- // 一次只有一个消费者消费
- synchronized (consumerLock) {
- try {
- // 判断容器是否空
- synchronized (notEmpty) {
- while (container.size() == 0) {
- notEmpty.wait();
- }
- }
- // 互斥访问资源
- synchronized (lock) {
- System.out.println(Thread.currentThread().getName() + "消费了:" + container.remove(container.size() - 1));
- }
- // 唤醒生产者
- synchronized (notFull) {
- notFull.notify();
- }
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
-
- }
-
- public void produce() {
- // 一次只有一个生产者生产
- synchronized (producerLock) {
- try {
- // 若为满,则阻塞
- synchronized (notFull) {
- while (container.size() == FULL_SIZE) {
- notFull.wait();
- }
- }
- // 互斥访问
- synchronized (lock) {
- String str = "proId" + new Random().nextInt();
- container.add(str);
- System.out.println(Thread.currentThread().getName() + "生产了:" + str);
- }
- // 唤醒消费者
- synchronized (notEmpty) {
- notEmpty.notify();
- }
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
-
- static class Consumer extends Thread {
- private ManyProducerManyConsumers demo;
-
- public Consumer(ManyProducerManyConsumers demo) {
- this.demo = demo;
- }
-
- @Override
- public void run() {
- while (true) {
- demo.consume();
- try {
- Thread.sleep(200);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
-
- static class Producer extends Thread {
- private ManyProducerManyConsumers demo;
-
- public Producer(ManyProducerManyConsumers demo) {
- this.demo = demo;
- }
-
- @Override
- public void run() {
- while (true) {
- demo.produce();
- try {
- Thread.sleep(200);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
-
- public static void main(String[] args) throws InterruptedException {
- ManyProducerManyConsumers demo = new ManyProducerManyConsumers();
- for(int i=0;i<5;i++) {
- new Producer(demo).start();
- }
- for (int i = 0; i < 5; i++) {
- new Consumer(demo).start();
- }
- }
- }
每个线程都有自己的ThreadLocalMap变量,因此数据是隔离的。
需要注意的是,使用ThreadLocal后,应当手动调用remove方法清楚entry对象,这是因为Entry类将key(ThreadLocal)设置为弱引用,当GC发现它时,就会回收ThreadLocal对象,而value并没有回收,这就导致ThreadLocalMap中存在很多key为null,value不为null的Entry对象。
应用场景:登陆的用户数据,数据库连接,传参数。
- public class ThreadLocalDemo {
- ThreadLocal
threadLocal = new ThreadLocal<>(); -
- public static void main(String[] args) throws InterruptedException {
- ThreadLocalDemo threadLocalDemo = new ThreadLocalDemo();
- System.out.println(Thread.currentThread().getName()+" put value: main");
- threadLocalDemo.threadLocal.set("main");
- Thread.sleep(1000);
- Thread thread1 = new Thread(() -> {
- System.out.println(Thread.currentThread().getName() + " put value: AAAA");
- threadLocalDemo.threadLocal.set("AAAA");
- try {
- Thread.sleep(500);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName() + " get value: "+ threadLocalDemo.threadLocal.get());
- });
- Thread thread2 = new Thread(() -> {
- System.out.println(Thread.currentThread().getName() + " put value: ZZZZZ");
- threadLocalDemo.threadLocal.set("ZZZZZ");
- try {
- Thread.sleep(500);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName() + " get value: "+ threadLocalDemo.threadLocal.get());
- });
- thread1.start();
- thread2.start();
- System.out.println(Thread.currentThread().getName() + " get value: "+ threadLocalDemo.threadLocal.get());
- }
- }
InheritableThreadLocal能实现子线程继承父线程的值,也即做了一份浅拷贝。
应用场景:子线程需要获取父线程的数据信息。
- public class InheritableThreadLocalDemo {
- static InheritableThreadLocal
local = new InheritableThreadLocal<>(); - public static void main(String[] args) throws InterruptedException {
- System.out.println(Thread.currentThread().getName()+" set Value: hello");
- local.set("hello");
- Thread.sleep(100);
- new Thread(() -> System.out.println(Thread.currentThread().getName()+" getValue:"+local.get())).start();
- }
- }
执行结果:
- main set Value: hello
- Thread-0 getValue:hello