文章目录
1. start( )和 run( )
测试代码:
/**
* 多线程常用方法
*/
public class UsualMethods {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "...run");
},"t1");
t1.run();
t1.start();
}
}
1.1 run( )
被创建的Thread对象直接调用重写的run方法时, run方法是在主线程中被执行的,而不是在我们所创建的线程中执行。
/* What will be run. */
private Runnable target;
@Override
public void run() {
if (target != null) {
target.run();
}
}
- 实际就是调用Runnable接口的 run方法。
- 也可以直接继承Thread类重写run方法
1.2 start( )
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0();
本地方法 start0 启动线程
2. sleep( ) 和 yield( )
2.1 sleep( ): 使线程阻塞
public static native void sleep(long millis) throws InterruptedException;
- 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞),可通过state()方法查看
- 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
- 睡眠结束后的线程未必会立刻得到执行
- 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性 ,如:
- TimeUnit.SECONDS.sleep(1); // 休眠一秒
- TimeUnit.MINUTES.sleep(1); // 休眠一分钟
2.2 yield( ):让出当前线程
public static native void yield();
- 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态(仍然有可能被执行),然后调度执行其它线程
- 具体的实现依赖于操作系统的任务调度器
2.3 setPriority( ):设置线程优先级
-
线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它
-
如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用
-
设置方法:thread1.setPriority(Thread.MAX_PRIORITY); //设置为优先级最高
3. wait( ) 和 notify( )
3.1 原理概述
- 锁对象调用wait方法(obj.wait),就会使当前线程进入WaitSet中,变为WAITING状态。
- 处于BLOCKED和WAITING状态的线程都为阻塞状态,CPU都不会分给他们时间片。但是有所区别:
- BLOCKED状态的线程是在竞争对象时,发现Monitor的Owner已经是别的线程了,此时就会进入EntryList中,并处于BLOCKED状态
- WAITING状态的线程是获得了对象的锁,但是自身因为某些原因需要进入阻塞状态时,锁对象调用了wait方法而进入了WaitSet中,处于WAITING状态
- BLOCKED状态的线程会在锁被释放的时候被唤醒,但是处于WAITING状态的线程只有被锁对象调用了notify方法(obj.notify/obj.notifyAll),才会被唤醒。
注:只有当对象被锁以后,才能调用wait和notify方法
3.2 Wait与Sleep的区别
不同点
- Sleep是Thread类的静态方法,Wait是Object的方法,Object又是所有类的父类,所以所有类都有Wait方法。
- Sleep在阻塞的时候不会释放锁,而Wait在阻塞的时候会释放锁
- Sleep不需要与synchronized一起使用,而Wait需要与synchronized一起使用(对象被锁以后才能使用)
相同点
- 阻塞状态都为TIMED_WAITING
public class Test1 {
final static Object LOCK = new Object();
public static void main(String[] args) throws InterruptedException {
//只有在对象被锁住后才能调用wait方法
synchronized (LOCK) {
LOCK.wait();
}
}
}
3.3 优雅地使用wait/notify
什么时候适合使用wait
当线程不满足某些条件,需要暂停运行时,可以使用wait。这样会将对象的锁释放,让其他线程能够继续运行。如果此时使用sleep,会导致所有线程都进入阻塞,导致所有线程都没法运行,直到当前线程sleep结束后,运行完毕,才能得到执行。
使用wait/notify需要注意什么
当有多个线程在运行时,对象调用了wait方法,此时这些线程都会进入WaitSet中等待。如果这时使用了notify方法,可能会造成虚假唤醒(唤醒的不是满足条件的等待线程),这时就需要使用notifyAll方法
synchronized (LOCK) {
while(//不满足条件,一直等待,避免虚假唤醒) {
LOCK.wait();
}
//满足条件后再运行
}
//其他线程
synchronized (LOCK) {
//唤醒所有等待线程
LOCK.notifyAll();
}
4. join( )
4.1 设计模式之保护性暂停
4.1.1 概述
4.1.2 举例
没有时间限制的暂停
- 线程 1 进入等待 wait 状态 想要获得response
- 线程2 设置response 并且唤醒线程1 notify
package top.nnzi;
/**
* @program: Java Concurrent
* @description:
* @author: A.iguodala
* @create: 2021-04-04 22:23
**/
public class Demo1 {
public static void main(String[] args) {
String hello = "hello world";
Guarded guarded = new Guarded();
new Thread(()->{
System.out.println("想要得到结果");
synchronized (guarded) {
System.out.println("结果是:"+guarded.getResponse());
}
System.out.println("得到结果");
},"线程1").start();
new Thread(()->{
System.out.println("设置结果");
synchronized (guarded) {
guarded.setResponse(hello);
}
},"线程2").start();
}
}
class Guarded {
/**
* 要返回的结果
*/
private Object response;
//优雅地使用wait/notify
public Object getResponse() {
//如果返回结果为空就一直等待,避免虚假唤醒
while(response == null) {
synchronized (this) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
return response;
}
public void setResponse(Object response) {
this.response = response;
synchronized (this) {
//唤醒休眠的线程
this.notifyAll();
}
}
@Override
public String toString() {
return "Guarded{" +
"response=" + response +
'}';
}
}
带超时判断的暂停
- 只改动getResponse ( ) 方法
- 先获得当前时间 进入循环
- 通过过去的时间和总时间计算出需要等待的时间(因为如果别的线程调用notifyAll () 方法时该对象会被唤醒,然而等待并没有得到response 并且 等待时间不足,而下次也不需要等待最初的完整时间)
- 获得系统时间和开始时间相减获得度过时间
- 如果没有获得response就继续循环, 如果等待时间为0了则跳出循环
public Object getResponse(long time) {
synchronized (this) {
//获取开始时间
long currentTime = System.currentTimeMillis();
//用于保存已经等待了的时间
long passedTime = 0;
while(response == null) {
//看经过的时间-开始时间是否超过了指定时间
long waitTime = time -passedTime;
if(waitTime <= 0) {
break;
}
try {
//等待剩余时间
this.wait(waitTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
//获取当前时间
passedTime = System.currentTimeMillis()-currentTime
}
}
return response;
}
4.2 join ( ) 方法分析
用于等待某个线程结束。哪个线程内调用join()方法,就等待哪个线程结束,然后再去执行其他线程。
如在主线程中调用t1.join(),则需要主线程等待t1线程结束再继续执行
public class UsualMethods {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "...run");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1");
t1.start();
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程");
}
}
Thread thread = new Thread();
//等待thread线程执行结束
thread.join();
//最多等待1000ms,如果1000ms内线程执行完毕,则会直接执行下面的语句,不会等够1000ms
thread.join(1000);
源码分析
- 就是上文第二种情况的具体实现
- 如果是 join(0) 则 如果线程存活( isAlive( ) ) 则一直wait
- delay 用来存储wait的剩余时间 等于 0 则退出循环
- 不直接使用wait (time)是因为防止被其他线程notifyAll( ) 虚假唤醒
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
4.3 设计模式扩展(1) : 解耦等待和生产
举例
- 邮递员和居民送信的关系 简单实现一个邮递员对应一个居民
- 居民开始等待收信( wait( ) )
- 邮递员根据id寄信后 唤醒等待的居民
package top.nnzi;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
/**
* @program: Java Concurrent
* @description:
* @author: A.iguodala
* @create: 2021-04-04 22:23
**/
public class Demo1 {
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
new People().start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (Integer id : MailBox.getIds()) {
new PostMan(id,"信件" + id).start();
}
}
}
class People extends Thread{
@Override
public void run() {
Guarded guarded = MailBox.createGuarded();
System.out.println("开始收信");
Object response = guarded.getResponse();
System.out.println("收到" + response);
}
}
class PostMan extends Thread{
private int id;
private String mail;
public PostMan(int id, String mail) {
this.id = id;
this.mail = mail;
}
@Override
public void run() {
Guarded guarded = MailBox.getGuarded(id);
System.out.println("开始送信");
guarded.setResponse(mail);
System.out.println("送到信了");
}
}
class MailBox {
private static Map<Integer, Guarded> boxs = new Hashtable<>();
private static int id;
private static synchronized int generateId () {
return id++;
}
public static Guarded getGuarded (int id) {
return boxs.remove(id);
}
public static Guarded createGuarded () {
Guarded guarded = new Guarded(generateId());
boxs.put(guarded.getId(), guarded);
return guarded;
}
public static Set<Integer> getIds() {
return boxs.keySet();
}
}
class Guarded {
/**
* 标识 Guarded
*/
private int id;
/**
* 要返回的结果
*/
private Object response;
public int getId() {
return id;
}
public Guarded(int id) {
this.id = id;
}
/**
* 获得响应
* @return
*/
public Object getResponse() {
//如果返回结果为空就一直等待,避免虚假唤醒
while(response == null) {
synchronized (this) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
return response;
}
/**
* 生产响应
* @param response
*/
public void setResponse(Object response) {
this.response = response;
synchronized (this) {
//唤醒休眠的线程
this.notifyAll();
}
}
@Override
public String toString() {
return "Guarded{" +
"response=" + response +
'}';
}
}
4.4 设计模式扩展(2) : 生产者 / 消费者
- 与前面的保护性暂停中的GuardObject 不同,不需要产生结果和消费结果的线程一一对应
- 消费队列可以用来平衡生产和消费的线程资源
- 生产者仅负责产生结果数据,不关心数据该如何处理,而消费者专心处理结果数据
- 消息队列是有容量限制的,满时不会再加入数据,空时不会再消耗数据
- JDK中各种阻塞队列,采用的就是这种模式
举例
- 注意 该情况时 线程之间的消息队列,与 RabbitMQ等进程间通信有一定区别
package top.nnzi;
import java.util.Deque;
import java.util.LinkedList;
/**
* @Description:
* @Author: Aiguodala
* @CreateDate: 2021/4/5 13:55
*/
public class Demo2 {
public static void main(String[] args) {
MessageQueue mq = new MessageQueue(2);
for (int i = 0; i < 3; i++) {
final int id = i;
new Thread(() -> {
mq.put(new Message(id, "消息" + id));
},"生产者" + id + "号").start();
}
new Thread(() -> {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
mq.take();
}
},"消费者").start();
}
}
class MessageQueue {
private int capcity;
public MessageQueue(int capcity) {
this.capcity = capcity;
}
private static Deque<Message> queue = new LinkedList<>();
public Message take () {
synchronized (queue) {
while (queue.isEmpty()) {
try {
System.out.println(Thread.currentThread().getName() + "队列为空,等待消费");
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "消费消息");
Message message = queue.removeFirst();
queue.notifyAll();
return message;
}
}
public void put (Message message) {
synchronized (queue) {
while (queue.size() == capcity) {
try {
System.out.println(Thread.currentThread().getName() + "队列满了 无法生产");
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "生产消息");
queue.add(message);
queue.notifyAll();
}
}
}
class Message {
private int id;
private Object data;
public Message(int id, Object data) {
this.id = id;
this.data = data;
}
public int getId() {
return id;
}
public Object getData() {
return data;
}
@Override
public String toString() {
return "Message{" +
"id=" + id +
", data=" + data +
'}';
}
}
5. interrupt( ):打断线程
- 如果一个线程在在运行中被打断,打断标记会被置为true。
- 如果是打断因sleep wait join方法而被阻塞的线程,会将打断标记置为false 并抛出InterruptedException异常
- 正常运行的线程在被打断后,不会停止,会继续执行。如果要让线程在被打断后停下来,需要使用打断标记来判断。
- isInterrupted() 用于查看是否被打断
5.1 两阶段终止模式
当我们在执行线程一时,想要终止线程二,这是就需要使用interrupt方法来优雅的停止线程二。
public class Test7 {
public static void main(String[] args) throws InterruptedException {
Monitor monitor = new Monitor();
monitor.start();
Thread.sleep(3500);
monitor.stop();
}
}
class Monitor {
Thread monitor;
/**
* 启动监控器线程
*/
public void start() {
//设置线控器线程,用于监控线程状态
monitor = new Thread() {
@Override
public void run() {
//开始不停的监控
while (true) {
//判断当前线程是否被打断了
if(Thread.currentThread().isInterrupted()) {
System.out.println("处理后续任务");
//终止线程执行
break;
}
System.out.println("监控器运行中...");
try {
//线程休眠
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
//如果是在休眠的时候被打断,不会将打断标记设置为true,这时要重新设置打断标记
Thread.currentThread().interrupt();
}
}
}
};
monitor.start();
}
/**
* 用于停止监控器线程
*/
public void stop() {
//打断线程
monitor.interrupt();
}
}
6. setDaemon( ):设置守护线程
当JAVA进程中有多个线程在执行时,只有当所有非守护线程都执行完毕后,JAVA进程才会结束。但当非守护线程全部执行完毕后,守护线程无论是否执行完毕,也会一同结束。
//将线程设置为守护线程, 默认为false
monitor.setDaemon(true);
守护线程的应用
- 垃圾回收器线程就是一种守护线程
- Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等 待它们处理完当前请求
7. park( ) / unpark( )
7.1 基本使用
park/unpark都是LockSupport类中的的方法
//暂停线程运行
LockSupport.park;
//恢复线程运行
LockSupport.unpark(thread);
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()-> {
System.out.println("park");
//暂停线程运行
LockSupport.park();
System.out.println("resume");
}, "t1");
thread.start();
Thread.sleep(1000);
System.out.println("unpark");
//恢复线程运行
LockSupport.unpark(thread);
}
7.2 与wait/notify的区别
- wait,notify 和 notifyAll 必须配合Object Monitor一起使用,而park,unpark不必
- park ,unpark 是以线程为单位来阻塞和唤醒线程,而 notify 只能随机唤醒一个等待线程,notifyAll 是唤醒所有等待线程,就不那么精确
- park & unpark 可以先 unpark,而 wait & notify 不能先 notify
- park不会释放锁,而wait会释放锁
7.3 原理
每个线程都有一个自己的Park对象,并且该对象_counter, _cond,__mutex组成 (c 编写)
先调用park再调用unpark时
- 先调用park
- 线程运行时,会将Park对象中的**_counter的值设为0**;
- 调用park时,会先查看counter的值是否为0,如果为0,则将线程放入阻塞队列cond中
- 放入阻塞队列中后,会再次将counter设置为0
- 然后调用unpark
- 调用unpark方法后,会将counter的值设置为1
- 去唤醒阻塞队列cond中的线程
- 线程继续运行并将counter的值设为0
先调用unpark,再调用park
- 调用unpark
- 会将counter设置为1(运行时0)
- 调用park方法
- 查看counter是否为0
- 因为unpark已经把counter设置为1,所以此时将counter设置为0,但不放入阻塞队列cond中