进程(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