1,线程状态
- NEW 刚创建(new)
- RUNNABLE 就绪态(start):调用一个线程的start方法,那么这个线程就进入了就绪态
- RUNNING 运行中(run):等待的线程被CPU调度后就进入Running状态
- BLOCK 阻塞(sleep):由于某一个条件没有满足,暂时不需要CPU
- TERMINATED 结束
![image-20210527080214385](https://i-blog.csdnimg.cn/blog_migrate/3bff888b51480cd600254adcc7151bd2.png)
2,线程状态切换
线程的阻塞和唤醒
- sleep:线程自我休眠,时间一到自己就会醒来
- wait,notify/notifyAll:线程进入 wait 状态,需要别的线程 notify(唤醒)
- join:等待另一个线程结束
- interrupt:向另外一个线程发送中断信号,该线程收到信号,会触发InterruptedException,并进行下一步处理
经典生产者消费者问题:
- 生产者不断往仓库存放产品,消费者从仓库中消费产品。
- 其中生产者和消费者可以有若干个
- 仓库规则:容量有限,仓库满时不能存放,仓库空时不能取产品
测试类:
package com.antique.product;
public class ProductTest {
public static void main(String[] args) throws InterruptedException {
Storage storage = new Storage();
Thread producer1 = new Thread(new Producer(storage), "生产者1");
Thread producer2 = new Thread(new Producer(storage), "生产者2");
Thread consumer1 = new Thread(new Consumer(storage), "消费者1");
Thread consumer2 = new Thread(new Consumer(storage), "消费者2");
producer1.start();
producer2.start();
Thread.sleep(1000);
consumer1.start();
consumer2.start();
}
}
仓库类:
package com.antique.product;
public class Storage {
private int capacity = 10; //仓库容量为10
private Product[] products = new Product[capacity];
private int top = 0;
public int getCapacity() {
return capacity;
}
/* 生产者往仓库中放产品 */
public synchronized void push(Product product) {
while (top == capacity) {
try {
System.out.println("仓库已满,producer请等待");
wait();
/* 进入等待后只能由别的线程唤醒
* 当某个生产者生产一个产品后 仓库满,则则这个生产者线程进入等待
* 由于这是一个同步方法,所以另一个线程也会排队等待
* 当前等待线程被(消费者线程)唤醒时,另一个消费者线程才能继续执行。
* 被唤醒时,继续去判断 top 与 capacity 是否相等,是的话就继续等待
* 否则就继续生产产品,消费者线程也是同理。*/
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/* 把产品放入仓库 */
products[top++] = product;
System.out.println(Thread.currentThread().getName() + " 生产了产品 " + product.toString());
System.out.println("producer notifyAll");
notifyAll(); //唤醒所有等待的线程
}
/* 消费者从仓库中取产品 */
public synchronized Product pop() {
while (top == 0) {
try {
System.out.println("仓库已空,请等待");
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/* 从仓库中取出产品 */
Product product = products[--top];
products[top] = null;
System.out.println(Thread.currentThread().getName() + " 消费了产品 " + product.toString());
System.out.println("consumer notifyAll");
/* 若将下面这行注释掉,生产者线程将不会被唤醒
* 只会有10个产品被生产和消费*/
notifyAll(); //唤醒所有等待的线程
return product;
}
}
生产者线程:
package com.antique.product;
import java.util.Random;
public class Producer implements Runnable{
private Storage storage;
public Producer(Storage storage) {
this.storage = storage;
}
@Override
public void run() {
int i = 0;
Random r = new Random();
/* 生产者往仓库放10个产品 两个生产者线程共会生产20个产品*/
while (i < storage.getCapacity()) {
i++;
Product product = new Product(i, "商品" + r.nextInt(100));
storage.push(product);
}
}
}
消费者线程:
package com.antique.product;
public class Consumer implements Runnable{
private Storage storage;
public Consumer(Storage storage) {
this.storage = storage;
}
@Override
public void run() {
int i = 0;
/* 消费者从仓库取出10个产品 两个消费者线程共会消费20个产品*/
while (i < storage.getCapacity()) {
i++;
storage.pop();
}
}
}
产品类:
package com.antique.product;
public class Product {
private int id;
private String name;
public Product(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "产品ID: " + id + "产品名称: " + name;
}
}
在上面的例子中我们看到,线程wait的时候,需要依靠别的线程来notify。
这样的话,线程就是被动的暂停和终止,这样的情况很危险,比如说某个线程打开了一个文件、或者拿着一个对象的锁,一旦这个线程进入wait状态,没有及时释放资源,并且没有被唤醒,别的线程是拿不到东西的。
线程主动暂停和终止:
- 定期检测共享变量。
- 如果需要暂停或终止,先释放资源,再主动暂停或终止。
主动和被动的区别:
- 如果依赖于interrupt标志的话,需要自己去添加异常处理,并且这个地方的异常可能来不及让你释放资源
- 而定期的去监控一个变量,当这个变量被修改了,可以很优雅的释放所有的资源
package com.antique;
public class Main {
public static void main(String[] args) throws InterruptedException {
TestThread1 t1 = new TestThread1();
TestThread2 t2 = new TestThread2();
t1.start();
t2.start();
Thread.sleep(2000);
t1.interrupt();
t2.flag = true;
System.out.println("main thread is exiting");
}
}
class TestThread1 extends Thread {
@Override
public void run() {
/* 当本线程被别人interrupt后,JVM会将本线程设置interrupted标志*/
while (!interrupted()) {
System.out.println("t1 thread is running");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
System.out.println("t1 thread is exiting");
}
}
class TestThread2 extends Thread {
public volatile boolean flag = true;
@Override
public void run() {
while (flag) {
System.out.println("t2 thread is running");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t2 thread is exiting");
}
}
![image-20210527080214385](https://i-blog.csdnimg.cn/blog_migrate/be32f6008dbba2e3348fb77e4ed96b60.png)
3,多线程死锁
每个线程互相持有其他线程需要的锁(哲学家吃饭问题),这样就会造成线程不满足运行条件,从而阻塞。
预防死锁,对资源进行等级排序(规定获取资源的顺序)。
package com.antique;
import java.util.concurrent.TimeUnit;
public class Main {
public static Integer r1 = 1;
public static Integer r2 = 2;
public static void main(String[] args) throws InterruptedException {
TestThread1 t1 = new TestThread1();
TestThread2 t2 = new TestThread2();
t1.start();
t2.start();
}
}
class TestThread1 extends Thread {
@Override
public void run() {
synchronized (Main.r1) {
try {
TimeUnit.SECONDS.sleep(1); //睡眠1s
} catch (InterruptedException e) {
e.printStackTrace();
}
/* t1 在对 r2 对象加锁时,t2 已经取得了 r2 的锁*/
synchronized (Main.r2) {
System.out.println("t1 thread is running");
}
}
}
}
class TestThread2 extends Thread {
@Override
public void run() {
/* 规定资源的获取顺序,r1 -> r2*/
// synchronized (Main.r1) {
synchronized (Main.r2) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
/* t2 在对 r1 对象加锁时,t1 已经取得了 r1 的锁*/
// synchronized (Main.r2) {
synchronized (Main.r1) {
System.out.println("t1 thread is running");
}
}
}
}
![image-20210527124046897](https://i-blog.csdnimg.cn/blog_migrate/e10f284819bab3728ed19b6adca985cc.png)
规定资源获取顺序后:
![image-20210527124650163](https://i-blog.csdnimg.cn/blog_migrate/c54bab94b4662902e22ac87407a154ff.png)
4,守护(后台)线程
- 普通线程的结束,是run方法运行结束
- 守护线程的结束,是run方法运行结束,或main函数结束
- 守护线程永远不要访问资源,如文件或数据库等
thread.setDaemon(true); //设置thread为守护线程
package com.antique;
public class Main {
public static void main(String[] args) throws InterruptedException {
TestThread t1 = new TestThread();
t1.setDaemon(true); // 先设置后启动
t1.start();
Thread.sleep(2000);
System.out.println("main thread is exiting");
}
}
class TestThread extends Thread {
@Override
public void run() {
while (true) {
System.out.println("test thread is running");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
![image-20210527125641524](https://i-blog.csdnimg.cn/blog_migrate/c74816c0b021634134bbecce9b48fc83.png)
由于main线程结束,守护线程 t1 也跟着结束了,这时候守护线程相当于被强制结束的,它就来不及释放资源,所有不要在守护线程里打开文件或连接数据库等。