多线程的方法介绍与使用
# wait函数
wait函数会使得到监视器锁的共享对象从运行状态变为挂起,同时会释放这把锁,但是不会释放当前线程其他共享对象上的锁。 以下几个事件可以打断被wait阻塞挂起的状态
- 其他线程调用的了该共享对象的**notify()和notifyAll()**方法;
- 其他线程调用了该线程的**interrupt()**方法,该线程抛出InterruptedException()异常;
注意:如果该线程没有获得共享对象的监视器锁,直接调用wait方法会抛出IllegalMonitorStateException异常
演示案例
// 线程调用共享对象的wait方法,线程会挂起,并且会释放该共享对象的锁,
// 但不会释放当前线程上其他共享对象的锁
@Test
public void testWaitFreeLock() throws InterruptedException {
// 线程1 先获取资源1和资源2的锁,再让自己等待并释放资源1的锁
Thread thread1 = new Thread(() -> {
synchronized (threadTest1) {
String name = Thread.currentThread().getName();
System.out.println("threadName:" + name + ",获取threadTest1");
synchronized (threadTest2) {
System.out.println("threadName:" + Thread.currentThread().getName() + ",获取threadTest2");
try {
// 等待 并释放资源1的锁
threadTest1.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
// 线程2 先获取资源1和资源2的锁
Thread thread2 = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (threadTest1) {
String name = Thread.currentThread().getName();
System.out.println("threadName:" + name + ",获取threadTest1");
synchronized (threadTest2) {
System.out.println("threadName:" + Thread.currentThread().getName() + ",获取threadTest2");
}
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
// 预计结果是线程2,可以获取到资源1的锁,获取不到资源2的锁
// 演示结果
> threadName:Thread-0,获取threadTest1
> threadName:Thread-0,获取threadTest2
> threadName:Thread-1,获取threadTest1
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
演示案例:
// 如果该共享对象没有获得监视器锁,直接调用wait方法会抛出IllegalMonitorStateException异常
public void waitForObject() throws InterruptedException {
log.info("threadName:{}",Thread.currentThread().getName());
log.info("waitForObject is executing ....");
wait();
}
// 调用waitForObject方法 结果
> 15:08:19.324 [Thread-0] INFO com.zhu.multiple_thread.lock.ThreadTest - threadName:Thread-0
> 15:08:19.326 [Thread-0] INFO com.zhu.multiple_thread.lock.ThreadTest - waitForObject is executing ....
> Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
at java.base/java.lang.Object.wait(Native Method)
at java.base/java.lang.Object.wait(Object.java:516)
at com.zhu.multiple_thread.lock.ThreadTest.waitForObject(ThreadTest.java:18)
at com.zhu.multiple_thread.lock.ThreadTestTest.lambda$testWait$0(ThreadTestTest.java:21)
at java.base/java.lang.Thread.run(Thread.java:844)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# wait(long timeout)函数
传入timeout时间,当时间到达时,会结束共享对象的阻塞挂起状态 当timeout=0作用和wait()一致 当timeout<0会抛出IllegalArgumentException异常
# wait( long timeout, int nanos)函数
在其内部调用的是 wait(long timeout)函数,只有在 nanos>0时才使参数timeout递增1。
# notify函数
线程调用共享对象的notify方法,会唤醒该共享变量上被wait阻塞挂起的线程,一个共享变量可能会有多个阻塞挂起的线程,具体唤醒那个线程是随机的。 同时,被唤醒的线程不是立刻就返回执行,而是要和其他线程竞争拿到监视器锁才能执行,否则就处在就绪状态
注意:如果该线程没有获得共享对象的监视器锁,直接调用notify方法会抛IllegalMonitorStateException异常
演示案例
/**
* @Author ggball
* @Description 测试notify随机唤醒
* @Date 2022/4/26
* @Param []
* @return void
**/
@Test
public void testNotifyRandomThread() throws InterruptedException {
ThreadTest threadTest = new ThreadTest();
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(() -> {
try {
String name = Thread.currentThread().getName();
log.info(name+"获取threadTest锁");
threadTest.waitForObject();
log.info(name+"被唤醒。。。");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread.start();
// thread.join();
}
Thread.sleep(3000);
// 随机唤醒
threadTest.notifyForObject();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# notifyAll 函数
不同于在共享变量上调用 notify函数会唤醒被阻塞到该共享变量上的一个线程,notifyAll方法则会换唤醒所有在该共享变量上由于调用wait系列方法而被挂起的线程。
# join 函数
原理:等待线程执行终止的方法
join方法的主要作用:就是同步,它可以使得线程之间的并行执行变为串行执行 在A线程中调用了B线程的join()方法时,表示只有当B线程执行完毕时,A线程才能继续执行。 join方法中如果传入参数,则表示这样的意思:如果A线程中掉用B线程的join(10),则表示A线程会等待B线程执行10毫秒,10毫秒过后,A、B线程并行执行。需要注意的是,jdk规定,join(0)的意思不是A线程等待B线程0秒,而是A线程等待B线程无限时间,直到B线程执行完毕,即join(0)等价于join()。(其实join()中调用的是join(0)) join方法必须在线程start方法调用之后调用才有意义。这个也很容易理解:如果一个线程都没有start,那它也就无法同步了。
# sleep函数
Thread 类中有一个静态的sleep 方法,当一个执行中的线程调用了Thread 的sleep 方法后,调用线程会暂时让出指定时间的执行权,也就是在这期间不参与CPU 的调度,但是该线程所拥有的监视器资源,比如锁还是持有不让出的。指定的睡眠时间到了后该函数会正常返回,线程就处于就绪状态,然后参与CPU 的调度,获取到CPU 资源后就可以继续运行了。 如果在睡眠期间其他线程调用了该线程的interrupt()方法中断了该线程,则该线程会在调用sleep 方法的地方抛出IntermptedException 异常而返回。
# yield函数
原理:让出CPU 执行权
当一个线程调用了Thread 类的静态方法yield时,是在告诉线程调度器自己占有的时间片中还没有使用完的部分自己不想使用了,这暗示线程调度器现在就可以进行下一轮的线程调度。 当一个线程调用yield 方法时, 当前线程会让出CPU 使用权,然后处于就绪状态,线程调度器会从线程就绪队列里面获取一个线程优先级最高的线程,当然也有可能会调度到刚刚让出CPU 的那个线程来获取CPU 执行权。
# 线程上下文切换
线程上下文切换时机有:当前线程的CPU时 间片使用完处于就绪状态时,当前线程被其他线程中断时。