线程的概念
线程不能独立存在,资源由操作系统分配给进程,但是CPU
资源是分配给线程的.同一个进程内的线程:
- 共享: 堆(主要存放使用
new
操作创建的对象实例)和方法区(JVM
加载的类,常量以及静态变量等) - 独立: 程序计数器(获得
CPU
控制权后可以从计数器指定地址继续执行)和栈区域.
当启动main
函数时,就相当于启动了一个JVM
的进程,而main
所在的线程就是该进程中的主线程.
如何创建线程
有三种方式:
- 继承
Thread
类,重写run
方法:
// Thread1.java
public class Thread1 extends Thread{
@Override
public void run() {
System.out.println("current-thread: "+this.getName());
System.out.println("继承自Thread");
}
public static void main(String[] args) {
Thread1 t = new Thread1();
t.start();
}
}
不需要使用
currentThread
就可以获取当前线程,但是不能再继承其他类
- 实现
Runnable
方法,并重写run
方法:
//Thread2.java
public class Thread2 implements Runnable{
private String name;
public Thread2(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println("this is "+ name + " running");
System.out.println("实现了Runnable接口");
}
public static void main(String[] args) {
Thread t1 = new Thread(new Thread2("t1"));
t1.start();
Thread t2 = new Thread(new Thread2("t2"));
t2.start();
}
}
可以继承其他类,还可以添加参数区分
- 实现
Callable
接口的call
方法:
//Thread3.java
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Thread3 implements Callable<String> {
@Override
public String call(){
System.out.println("使用FutureTask方式,可以有返回值");
return "done";
}
public static void main(String[] args) throws InterruptedException {
FutureTask<String> ft = new FutureTask<String>(new Thread3());
Thread t = new Thread(ft);
t.start();
try {
String result = ft.get();
System.out.println(result);
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
具有返回值,可以使用
get
方法获得返回值.也可以继承其他类.
当调用Thread
的start
方法,线程只是进入了就绪状态,只有当cpu
资源得到满足时,才会真正开始运行.run
方法结束后,线程就处于终止状态.
wait与notify
wait
与notify
都是Object
类中的方法.
wait
只有获得了变量的监视锁,才能调用该变量的wait
方法,否则会抛出异常:
public class WaitExample {
private int a;
void incr() {
a++;
}
int getA() {
return a;
}
public static void main(String[] args) {
WaitExample waitExample = new WaitExample();
try {
// wrong
waitExample.wait();
for (int i = 0; i < 20000; i++) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
waitExample.incr();
}
});
t.start();
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(waitExample.getA());
}
}
运行结果:
java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at com.may.learning.WaitExample.main(WaitExample.java:17)
0
获得监视锁可以通过synchronized
关键字:
比如在方法上加上关键字:
synchronized void incr() {
a++;
}
或者,在调用时对该类加上关键字:
synchronized (waitExample) {
...
}
当调用wait
时,只会释放当前变量的锁:
public static void main(String[] args) throws InterruptedException{
WaitExample waitExample = new WaitExample();
WaitExample anotherWaitExample = new WaitExample();
Thread t = new Thread(new Runnable() {
@Override
public void run() {
synchronized (waitExample) {
System.out.println("t: get waitExample lock success!");
System.out.println("t: try get anotherWaitExample lock");
try {
synchronized (anotherWaitExample) {
System.out.println("t: get anotherWaitExample success!");
System.out.println("t: release waitExample lock");
// t只释放了waitExample的锁
waitExample.wait();
}
} catch (InterruptedException e) {
System.out.println("被中断");
}
}
}
});
t.start();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (waitExample) {
System.out.println("t1: get waitExample lock success!");
System.out.println("t1: try get anotherWaitExample lock");
try {
// 由于t未释放锁,因此t1获取不到anotherWaitExample的锁
synchronized (anotherWaitExample) {
System.out.println("t1: get anotherWaitExample success!");
System.out.println("t1: release waitExample lock");
waitExample.wait();
}
} catch (InterruptedException e) {
System.out.println("被中断");
}
}
}
});
t1.start();
Thread.sleep(1000);
}
运行结果:
t: get waitExample lock success!
t: try get anotherWaitExample lock
t: get anotherWaitExample success!
t: release waitExample lock
t1: get waitExample lock success!
t1: try get anotherWaitExample lock
如果需要指定时间,可以使用wait(long timeout)
或者wait(long timeout, int nanos)
.
被wait
挂起时,被中断时会抛出异常:
public class WaitExample {
private int a;
void incr() {
a++;
}
int getA() {
return a;
}
public static void main(String[] args) throws InterruptedException{
WaitExample waitExample = new WaitExample();
Thread t = new Thread(new Runnable() {
@Override
public void run() {
synchronized (waitExample) {
waitExample.incr();
try {
waitExample.wait();
} catch (InterruptedException e) {
System.out.println("被中断");
}
}
}
});
t.start();
Thread.sleep(1000);
t.interrupt();
System.out.println(waitExample.getA());
}
}
运行结果:
1
被中断
notify
notify
会唤醒被wait
阻塞挂起的线程.与wait
类似,需要获得了变量的监视锁后才能对其进行进行notify
操作.如果有多个线程都在等待唤醒,那么会随机唤醒某一个线程.而notifyAll
会唤醒所有阻塞等待的线程.
类似wait
方法,notify
也需要先获得对象的监视锁(但是notify
不会释放锁)。
public static void main(String[] args) throws InterruptedException {
NotifyExample notifyExample = new NotifyExample();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
synchronized (notifyExample) {
System.out.println("t1 挂起");
notifyExample.wait();
System.out.println("t1 被唤醒了");
}
} catch (InterruptedException e) {
System.out.println("被中断了");
}
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
synchronized (notifyExample) {
System.out.println("t2 挂起");
notifyExample.wait();
System.out.println("t2 被唤醒了");
}
} catch (InterruptedException e) {
System.out.println("被中断了");
}
}
});
t2.start();
Thread.sleep(1000);
synchronized (notifyExample) {
notifyExample.notify();
}
}
运行结果:
t1 挂起
t2 挂起
t1 被唤醒了
如果将notifyExample.notify()
换成notifyExample.notifyAll()
,那么t1
和t2
都能被唤醒:
t1 挂起
t2 挂起
t2 被唤醒了
t1 被唤醒了
生产者消费者
利用wait
和notify
写一个简单的生产者消费者:
import java.util.LinkedList;
import java.util.Queue;
public class ProducerConsumer {
public static void main(String[] args) throws InterruptedException {
Queue<Integer> queue = new LinkedList<>();
Thread producer = new Thread(new Runnable() {
@Override
public void run() {
int i=0;
synchronized (queue) {
while (true){
try {
if (!queue.isEmpty()) {
queue.wait();
}else{
queue.add(++i);
System.out.println("生产了"+i);
queue.notify();
}
} catch (InterruptedException e) {
System.out.println("被中断了");
return;
}
}
}
}
});
Thread consumer = new Thread(new Runnable() {
@Override
public void run() {
synchronized (queue) {
while (true){
try{
if (queue.isEmpty()) {
queue.wait();
}else{
int i = queue.poll();
System.out.println("消费了"+i);
queue.notify();
}
}catch (InterruptedException e){
System.out.println("被中断了");
return;
}
}
}
}
});
producer.start();
consumer.start();
Thread.sleep(10000);
producer.interrupt();
consumer.interrupt();
}
}
运行结果:
...
生产了782421
消费了782421
生产了782422
消费了782422
生产了782423
消费了782423
生产了782424
被中断了
被中断了
join
wait
与notify
属于Ojbect
的方法,而join
是Thread
的方法.
调用某一线程的join
方法,可以使当前线程(下例中的main
线程)阻塞等待该线程完成:
public class JoinTest {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
System.out.println("t 结束了!");
} catch (InterruptedException e) {
System.out.println("被中断了");
}
}
});
t.start();
t.join();
System.out.println("main 结束了!");
}
}
运行结果:
t 结束了!
main 结束了!
sleep
sleep
和wait
一样可以挂起当前线程,被中断时也会抛出异常.但不同的是它是属于Thread
的方法,并且它不会释放对象的监视锁.指定睡眠时间到达后,会进入就绪状态,等待cpu
的调度.
public class SleepTest {
public static void main(String[] args) throws InterruptedException {
SleepTest sleepTest = new SleepTest();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (sleepTest){
try {
System.out.println("t1 获取到锁");
Thread.sleep(60000);
} catch (InterruptedException e) {
System.out.println("被中断了!");
}
}
}
});
t1.start();
Thread.sleep(1000);
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("t2 等待锁");
synchronized (sleepTest) {
System.out.println("t2 获取到锁了");
}
}
});
t2.start();
Thread.sleep(2000);
t1.interrupt();
}
}
运行结果:
t1 获取到锁
t2 等待锁
被中断了!
t2 获取到锁了
yield
线程会主动让出自己的cpu
时间,并不会被挂起,只是进入了就绪状态.但是下一次仍然可能被调度到.
中断
前面已经使用了interrupt
很多次,需要注意与isInterrupted
以及interrupted
进行区分.
isInterrupted
: 判断当前线程是否被中断;
interrupted
: 判断当前线程是否被中断,并且复位(清除中断标志);
使用interrupted
判断中断并且复位:
public class InterruptTest {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
while (!Thread.interrupted()) {
}
System.out.println("t1 is Interrupted: "+Thread.currentThread().isInterrupted());
}
});
t1.start();
t1.interrupt();
t1.join();
System.out.println("done");
}
}
运行结果:
t1 is Interrupted: false
done
需要注意的是调用线程的interrupt
方法,只是为该线程设置中断位.当线程在运行时,会立即往下执行.而当被wait
,join
或者sleep
方法阻塞挂起时,才会抛出InterruptedException
异常,此时中断位会被恢复,如下面这个例子:
public class InterruptTest throws InterruptedException{
public static void main(String[] args){
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("t1 running");
while (true) {
}
}
});
t1.start();
t1.interrupt();
System.out.println("main thread isInterrupted: " + Thread.interrupted());
System.out.println("t1 isInterrupted: " + t1.isInterrupted());
boolean isInterrupted = Thread.interrupted();
System.out.println("isInterrupted: " + isInterrupted);
System.out.println("main thread isInterrupted: " + Thread.interrupted());
System.out.println("t1 isInterrupted: " + t1.isInterrupted());
t1.join();
System.out.println("done");
}
}
输出结果:
main thread isInterrupted: false
t1 isInterrupted: true
isInterrupted: false
main thread isInterrupted: false
t1 running
t1 isInterrupted: true
因为t1
并没有被挂起,所以即使中断位为true
,也仍然在继续运行,不会输出"done"
.而interrupted
针对是当前线程,也就是主线程,所以中断标志位一直是false
.
如果在阻塞情况下,中断位会被马上复位,对上面的例子稍加修改:
public class InterruptTest {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try{
while (!Thread.currentThread().isInterrupted()) {
System.out.println("t1: running");
Thread.sleep(1000);
}
}catch (Exception e){
System.out.println("t1 被中断");
System.out.println("thread t1 isInterrupted : "+Thread.currentThread().isInterrupted());
}
}
});
t1.start();
Thread.sleep(1000);
t1.interrupt();
System.out.println("main thread isInterrupted: " + Thread.interrupted());
System.out.println("t1 isInterrupted: " + t1.isInterrupted());
t1.join();
System.out.println("done");
}
}
运行结果:
t1: running
t1 被中断
thread t1 isInterrupted : false
main thread isInterrupted: false
t1 isInterrupted: false
done
主线程中的isInterrupted
仍然是false
,但是打印出的t1
的isInterrupted
由于处在阻塞状态被中断,中断位又被复位成了false
.
上下文切换与死锁
当出现:
-
CPU
时间用尽,线程进入就绪状态时 - 被其他线程中断时
会出现上下文的切换.
避免死锁的出现,可以采用不同线程都按照相同的顺序去请求资源,保证资源获取的有序一致性.
守护线程与用户线程
守护线程的存在不影响JVM
的退出,只要最后一个用户线程没有退出,正常情况下JVM
也不会退出.
JVM
没有退出:
public class DaemonTest {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
}
}
});
// t.setDaemon(true);
t.start();
System.out.println("main exit");
}
}
运行结果:
main exit
将t
利用setDeamon
设置为守护线程的注释打开,运行结果:
main exit
Process finished with exit code 0
原理是,main
运行后,JVM
会自动启动一个叫做DestoryJavaVM
的线程,它会等待所有用户线程结束后终止JVM
进程.
ThreadLocal
如果创建了一个ThreadLocal
变量,每个访问这个变量的线程都会获得一个副本.
public class ThreadLocalTest {
static ThreadLocal<Integer> val = new ThreadLocal<>();
static void print(String string) {
System.out.println(string + ":" + val.get());
}
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
val.set(1);
for (int i = 0; i < 50; i++) {
int v = val.get();
v++;
val.set(v);
}
print("thread1");
val.remove();
print("after remove thread1");
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
val.set(1);
for (int i = 0; i < 50; i++) {
int v = val.get();
v += 2;
val.set(v);
}
print("thread2");
val.remove();
print("after remove thread2");
}
});
t1.start();
t2.start();
System.out.println("done");
}
}
运行结果:
thread1:51
after remove thread1:null
done
thread2:101
after remove thread2:null
ThreadLocal
的原理:
-
Thread
中有一个threadLocals
变量,它的类型是ThreadLocal.ThreadLocalMap
,实际上是一个hashMap
. - 当使用
set
方法赋值时:
// ThreadLocal.java
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
getMap
方法获得的是threadLocals
,如果不为空,将当前threadLocal
变量作为key
,设置的值作为value
放入进行存储,而如果为空,则利用createMap
进行初始化.
// ThreadLocal.java
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
需要注意的是
createMap
中传入的是当前线程,因为需要为其设置threadLocals
变量的值.而threadLocals
中的key
实际上是ThreadLocal
变量,也就是可以理解为:thread1.threadLocals -> {ThreadLocal@xxx:1, ThreadLocal@yyy:"hello"}
但是ThreadLocal
不支持继承,如果需要继承父线程的值,需要使用InheritableThreadLocal
:
public class InheritThreadLocalTest {
static InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
static void print(String str) {
System.out.println(str + ":" + inheritableThreadLocal.get());
}
public static void main(String[] args) {
inheritableThreadLocal.set("hello");
Thread t = new Thread(new Runnable() {
@Override
public void run() {
String initVal = inheritableThreadLocal.get();
if (initVal != null) {
inheritableThreadLocal.set(initVal+" world");
}
print("thread 1");
}
});
t.start();
print("main");
System.out.println("done");
}
}
运行结果:
main:hello
done
thread 1:hello world
InheritableThreadLocal
和ThreadLocal
的set
方法是一样的,区别在于createMap
,它将变量写入了inheritableThreadLocals
里:
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
在初始化线程的时候,init
方法会设置inheritableThreadLocals
:
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals){
...
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
...
}
它会将父线程的inheritableThreadLocals
都复制到自己的inheritableThreadLocals
中.