1、什么是线程
线程(Thread)是CPU调度和分派的基本单位。线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。 线程和进程的关系是:线程是属于进程的,线程运行在进程空间内,同一进程所产生的线程共享同一内存空间,当进程退出时该进程所产生的线程都会被强制退出并清除。
2、线程的创建和使用
线程的使用有继承Thread,和传入Runnable,FutureTask等方式
1、继承Thread重写run方法
class PrimeThread extends Thread {
long minPrime;
PrimeThread(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
2、传入runnable参数
class PrimeRun implements Runnable {
long minPrime;
PrimeRun(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
PrimeRun p = new PrimeRun(143);
new Thread(p).start();
3、传入FutureTask参数
val futureT= FutureTask<String>(object : Callable<String> {
override fun call(): String {
return "yyds"
}
})
Thread(futureT).start()
//不要在ui线程中执行,会阻塞
futureT.get()
跟runnable不同的是通过FutureTask.get()可以获取线程执行的返回结果。
3、线程状态
如下图线程有6种状态
1、NEW 创建线程但是还没有执行start。
2、RUNNABLE 线程执行start方法之后,线程执行中。
3、BLOCKED 线程等待锁,一般是在线程进入synchronized同步方法或者同步代码块的时候等待锁。
4、WAITTING 线程等待状态,一般是在调用了Objecr.wait,Thread.join或者LockSupport.park等方法。
5、TIMED_WAITTING 线程等待某个时长,调用Thread.sleep,Onject.wait(time),Thread.join(time) LockSupport.partNanos
6、TERMINATED **线程执行完成,或者调用exit方法。
public enum State {
//线程还未执行start方法
NEW,
//调用start方法后,线程执行中
RUNNABLE,
//等待锁,一般是在进入synchronized的同步方法或者同步代码块。
BLOCKED,
//等待状态,一般是在调用了Objecr.wait,Thread.join或者LockSupport.park等方法。
WAITING,
//等待某个时长,调用Thread.sleep,Onject.wait(time),Thread.join(time) LockSupport.partNanos
TIMED_WAITING,
//线程执行exit方法
TERMINATED;
}
下面用一个demo来看下Thread的state,用两个线程t1,t2,t2负责获取t1的状态:
val t1 = Thread {
//runnable中执行 Thread.join ,或者Object.wait 或者Thread.sleep 进行测试
//先sleep
Log.d(TAG, "before sleep ")
Thread.sleep(1000)
Log.d(TAG, "after sleep")
}
//试试这个方法
// t1.join()
//在t2中去获取t1的状态试试
val t2 = Thread {
Log.d(TAG, "the state of t1 ${t1.state}")
t1.start()
Log.d(TAG, "the state of t1 ${t1.state}")
Thread.sleep(20)
Log.d(TAG, "the state of t1 ${t1.state}")
t1.join()
Log.d(TAG, "the state of t1 ${t1.state}")
} .start()
上面代码中在t1线程的runnable方法中,执行Thread.sleep(1000),然后在t2线程中获取t1线程的状态,在t1.start之前获取一次状态,t1.start后获取一次状态,然后t2中执行Thread.sleep(20),再获取t1的状态,打印log如下:
D/Current_H: thread start
D/Current_H: the state of t1 NEW
D/Current_H: the state of t1 RUNNABLE
D/Current_H: before sleep
D/Current_H: the state of t1 TIMED_WAITING
D/Current_H: after sleep
D/Current_H: the state of t1 TERMINATED
由上面可知在线程执行start之前是NEW状态,执行start方法之后是RUNNABLE状态,当在线程中执行Thread.sleep(1000)方法后进入TIMED_WAITING状态。执行t1.join方法之后进入TERMINATED状态。
4、Thread中核心方法
下面是Thread中比较常用和重要的方法,有些是静态方法。
1、yield()
/**
* A hint to the scheduler that the current thread is willing to yield
* its current use of a processor. The scheduler is free to ignore this
* hint.
*
* <p> Yield is a heuristic attempt to improve relative progression
* between threads that would otherwise over-utilise a CPU. Its use
* should be combined with detailed profiling and benchmarking to
* ensure that it actually has the desired effect.
*
* <p> It is rarely appropriate to use this method. It may be useful
* for debugging or testing purposes, where it may help to reproduce
* bugs due to race conditions. It may also be useful when designing
* concurrency control constructs such as the ones in the
* { @link java.util.concurrent.locks} package.
*/
public static native void yield();
yieldThread中的静态方法,使用的场景比较少,一般是在debug或者测试复现某些场景下的bug。暂停当前正在执行的线程对象,并执行其他线程。在多线程的情况下,由CPU决定执行哪一个线程,而yield()方法就是暂停当前的线程,让给其他线程(包括它自己)执行,具体由谁执行由CPU决定。
2、sleep(time)
sleep是静态方法最终调用的是(long millis, int nanos),如果直接调用Thread.sleep,millis0,nanos0。调用sleep方法可以使线程进入WAIT或者WAIT_TIME状态,但是线程不会释放所持有的锁。
public static void sleep(long millis, int nanos)
throws InterruptedException {
//如果时间小于0,抛出异常
if (millis < 0) {
throw new IllegalArgumentException("millis < 0: " + millis);
}
//nanos小于0,抛出异常
if (nanos < 0) {
throw new IllegalArgumentException("nanos < 0: " + nanos);
}
if (nanos > 999999) {
throw new IllegalArgumentException("nanos > 999999: " + nanos);
}
if (millis == 0 && nanos == 0) {
// 如果线程是interrupted抛出异常
if (Thread.interrupted()) {
throw new InterruptedException();
}
return;
}
final int nanosPerMilli = 1000000;
final long durationNanos;
if (millis >= Long.MAX_VALUE / nanosPerMilli - 1L) {
// > 292 years. Avoid overflow by capping it at roughly 292 years.
durationNanos = Long.MAX_VALUE;
} else {
durationNanos = (millis * nanosPerMilli) + nanos;
}
long startNanos = System.nanoTime();
Object lock = currentThread().lock;
synchronized (lock) {
//获取当前线程的锁,并且调用sleep(lock,millis,nanos)方法
for (long elapsed = 0L; elapsed < durationNanos;
elapsed = System.nanoTime() - startNanos) {
final long remaining = durationNanos - elapsed;
millis = remaining / nanosPerMilli;
nanos = (int) (remaining % nanosPerMilli);
sleep(lock, millis, nanos);
}
}
}
private static native void sleep(Object lock, long millis, int nanos)
下面是一个简单的生成者消费者实例进行测试sleep方法。
Thread {
var count = 0
while (true) {
Log.d(TAG, "add Queue $count")
addQueue(++count)
}
} .start()
Thread {
while (true) {
//执行移除
Log.d(TAG, "remove queue ---${removeQueue()}")
}
} .start()
/***
* 添加 队列
*/
@Synchronized
private fun addQueue(name: Int) {
Thread.sleep(1000)
Log.d(TAG, "${Thread.currentThread().name} 距离上次调用addQueue时间--${System.currentTimeMillis() - preTime}")
preTime = System.currentTimeMillis()
queue.add("name-$name")
this.notify()
}
/***
* 移除队列
*/
@Synchronized
private fun removeQueue(): String {
while (queue.isEmpty()) {
//执行wait
this.wait()
}
//进行移除
return queue.remove()
}
上面就是两个线程,一个往队列里面添加,一个往队列里面取,每次add之前都会sleep 1000ms,由于调用sleep时不会释放锁,所以上面addQueue方法中调用sleep之后由于线程持有的锁是当前类的对象锁,所以在另一个线程在第一个线程处于sleep状态时执行removeQueue时由于无法获取锁是执行不了queue.remove的。下面是log:
D/Current_H: add Queue 15
D/Current_H: Thread-6 距离上次调用addQueue时间--1001
D/Current_H: add Queue 16
D/Current_H: Thread-6 距离上次调用addQueue时间--1001
D/Current_H: add Queue 17
D/Current_H: Thread-6 距离上次调用addQueue时间--1002
D/Current_H: add Queue 18
D/Current_H: Thread-6 距离上次调用addQueue时间--1001
D/Current_H: add Queue 19
D/Current_H: Thread-6 距离上次调用addQueue时间--1002
D/Current_H: add Queue 20
D/Current_H: remove queue ---name-1
由上面log可知,在第一个线程执行sleep时,第二个线程由于无法获取锁,所以没有执行remove queue.
sleep(0)
当线程调用sleep(0)方法时类似于yield方法,会暂停当前线程让其他线程执行,也可能当前现在会再次获取执行机会。
3、interrupt()
interrupt方法的目的是给线程发出中断信号,但是不保证线程真的会中断
- 中断一个线程只是为了引起该线程的注意,被中断线程可以决定如何应对中断。
- Thread.interrupt()方法不会中断一个正在运行的线程。
- 如果线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者该Thread类的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法过程中受阻,则其中断状态将被清除,它还将收到一个InterruptedException异常。这个时候,我们可以通过捕获InterruptedException异常来终止线程的执行,具体可以通过return等退出或改变共享变量的值使其退出。
- synchronized在获锁的过程中是不能被中断的,意思是说如果产生了死锁,则不可能被中断(请参考后面的测试例子)。与synchronized功能相似的reentrantLock.lock()方法也是一样,它也不可中断的,即如果发生死锁,那么reentrantLock.lock()方法无法终止,如果调用时被阻塞,则它一直阻塞到它获取到锁为止。但是如果调用带超时的tryLock方法reentrantLock.tryLock(long timeout, TimeUnit unit),那么如果线程在等待时被中断,将抛出一个InterruptedException异常,这是一个非常有用的特性,因为它允许程序打破死锁。你也可以调用reentrantLock.lockInterruptibly()方法,它就相当于一个超时设为无限的tryLock方法。
- 如果该线程在可中断的通道上的 I/O 操作中受阻,则该通道将被关闭,该线程的中断状态将被设置并且该线程将收到一个 ClosedByInterruptException。这时候处理方法一样,只是捕获的异常不一样而已。 你如果正使用Java1.0之前就存在的传统的I/O,而且要求更多的工作。既然这样,Thread.interrupt()将不起作用,因为线程将不会退出被阻塞状态。尽管interrupt()被调用,线程也不会退出被阻塞状态,比如ServerSocket的accept方法根本不抛出异常。很幸运,Java平台为这种情形提供了一项解决方案,即调用阻塞该线程的套接字的close()方法。在这种情形下,如果线程被I/O操作阻塞,当调用该套接字的close方法时,该线程在调用accept地方法将接收到一个SocketException(SocketException为IOException的子异常)异常,这与使用interrupt()方法引起一个InterruptedException异常被抛出非常相似。
@Synchronized
private fun addQueue(name: Int) {
try {
Thread.sleep(1000)
} catch (e: Exception) {
Log.d(TAG, "addQueue e ${e.printStackTrace()}")
}
if (!Thread.interrupted()) {
Thread.currentThread().interrupt()
}
Log.d(
TAG,
"${Thread.currentThread().name} 距离上次调用addQueue时间--${System.currentTimeMillis() - preTime}"
)
preTime = System.currentTimeMillis()
queue.add("name-$name")
this.notify()
}
Thread {
var count = 0
while (true) {
Log.d(TAG, "add Queue $count")
addQueue(++count)
}
} .start()
如上代码中,在sleep(1000)后执行Thread.currentThread().interrupt(),第二次调用sleep(1000)时会抛出异常,并且sleep时间只有1ms。
D/Current_H: Thread-6 距离上次调用addQueue时间--1079
D/Current_H: add Queue 1
D/Current_H: addQueue e kotlin.Unit
//这里没有sleep到1000ms
D/Current_H: Thread-6 距离上次调用addQueue时间--1
4、join()
如果在线程A中调用了线程B.join()那么线程A在执行到B.join()后需要等待B线程执行完成后再进行执行B.join()之后的代码。join可以用于线程等待其他线程执行完成后再执行。join方法是通过lock来实现等待的源码如下:
public final void join(long millis)
throws InterruptedException {
synchronized(lock) {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
//如果当前线程还存活,那么调用lock
lock.wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
//lock.wait
lock.wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
}
下面用一个demo来看下join方法的使用,还是一个生成者和消费者,消费者要等生成者生成完成100个后再进行消费。
val t1 = Thread {
var count = 0
while (count<100) {
Log.d(TAG, "add Queue $count")
addQueue(++count)
}
}
t1.start()
Thread {
// whe
t1.join()
while (true) {
//执行移除
Log.d(TAG, "remove queue ---${removeQueue()}")
}
} .start()
/***
* 添加队列
*/
@Synchronized
private fun addQueue(name: Int) {
try {
Thread.sleep(10)
} catch (e: Exception) {
Log.d(TAG, "addQueue e ${e.printStackTrace()}")
}
// if (!Thread.interrupted()) {
// Thread.currentThread().interrupt()
// }
Log.d(
TAG,
"${Thread.currentThread().name} 距离上次调用addQueue时间--${System.currentTimeMillis() - preTime}"
)
preTime = System.currentTimeMillis()
queue.add("name-$name")
this.notify()
}
/***
* 移除队列
*/
@Synchronized
private fun removeQueue(): String {
while (queue.isEmpty()) {
//执行wait
this.wait()
}
//进行移除
return queue.remove()
}
上面的消费者消除中调用了t1.join(),就会等待生产者线程t1执行完成后再执行消费,log如下
......
D/Current_H: add Queue 98
Thread-6 距离上次调用addQueue时间--11
D/Current_H: add Queue 99
D/Current_H: Thread-6 距离上次调用addQueue时间--11
D/Current_H: remove queue ---name-1
D/Current_H: remove queue ---name-2
D/Current_H: remove queue ---name-3
D/Current_H: remove queue ---name-4
......
join(time)
B线程中执行A.join(10),那么B线程不需要等待A线程执行完成才能执行,只要等待10ms之后就可以获得执行的机会。
5、setDaemon()
setDaemon方法必须在Thread调用start之前设置,否则会抛异常,
public final void setDaemon(boolean on) {
checkAccess();
if (isAlive()) {
throw new IllegalThreadStateException();
}
daemon = on;
}
守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。也就是说守护线程不依赖于终端,但是依赖于系统,与系统“同生共死”。那Java的守护线程是什么样子的呢。当JVM中所有的线程都是守护线程的时候,JVM就可以退出了;如果还有一个或以上的非守护线程则JVM不会退出。
Object.wait方法
wait方法是Object中的方法,只能在用synchronized修饰的同步代码块中,wait方法会使执行到当前synchronized代码块的线程释放锁,并且进入等待队列中,由Object.notify方法进行唤醒。
通过下面生产者消费者的模式理解wait,和notify的使用。
private val queue = LinkedList<String>()
/***
* 添加 队列
*/
@Synchronized
private fun addQueue(name: Int) {
queue.add("name-$name")
this.notify()
}
/***
* 移除队列
*/
@Synchronized
private fun removeQueue(): String {
//使用while来判断是否empty,便于唤醒
while (queue.isEmpty()) {
//执行wait,wait方法放在while中的原因:
//就是用if判断的话,唤醒后线程会从wait之后的代码开始运行,但是不会重新判断if条件,直接继续运行if代码块之后的代码,而如果使用while的话,也会从wait之后的代码运行,但是唤醒后会重新判断循环条件,如果不成立再执行while代码块之后的代码块,成立的话继续wait。
//这也就是为什么用while而不用if的原因了,因为线程被唤醒后,执行开始的地方是wait之后。
this.wait()
}
//进行移除
return queue.remove()
}
//生产者
Thread {
var count = 0
while (true) {
Log.d(TAG, "add Queue $count")
addQueue(++count)
}
} .start()
//消费者
Thread {
// whe
while (true) {
//执行移除
Log.d(TAG, "remove queue ---${removeQueue()}")
}
} .start()
注意上面的synchronized作用的对象和wait以及notify是同一个对象。调用this.wait的时候是释放线程加在当前对象上的锁。调用this.notify方法是唤醒当前在等待加载对象上锁的线程中的一个。
注意:只有同步方法中才使用wait和notify。如果在非同步方法中调用wait,或者notify会报下面错误:
//wait
Caused by: java.lang.IllegalMonitorStateException: object not locked by thread before wait()
//notify
Caused by: java.lang.IllegalMonitorStateException: object not locked by thread before notify()
参考
1、https://www.geeksforgeeks.org/lifecycle-and-states-of-a-thread-in-java/
2、https://www.liaoxuefeng.com/wiki/1252599548343744/1306580911915042