Java多线程管理

1,线程状态

  • NEW 刚创建(new)
  • RUNNABLE 就绪态(start):调用一个线程的start方法,那么这个线程就进入了就绪态
  • RUNNING 运行中(run):等待的线程被CPU调度后就进入Running状态
  • BLOCK 阻塞(sleep):由于某一个条件没有满足,暂时不需要CPU
  • TERMINATED 结束
image-20210527080214385

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

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

规定资源获取顺序后:

image-20210527124650163

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

  由于main线程结束,守护线程 t1 也跟着结束了,这时候守护线程相当于被强制结束的,它就来不及释放资源,所有不要在守护线程里打开文件或连接数据库等。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值