线程(二)

1. 守护线程

守护线程是用来守护非守护线程的

非守护线程: 非守护线程就是平常写的线程

每个程序都要有至少一个非守护线程

非守护线程一旦结束守护线程无论完成没有完成都要结束

强制结束

守护线程依附非守护线程,如果非守护线程消亡,那么守护线程随之消亡。

main 主函数是非守护线程

真实开发的时候:

后台记录操作日志,监控内存,垃圾回收等 都可以使用守护线程


thread.setDaemon(true);
package com.day_w.a_Daemon;

class MyThread implements Runnable {
   @Override
   public void run() {
       for (int i = 1; i <= 1000; i++) {
           System.out.println("子线程正在执行... " + i );
      }
  }
}

public class Demo1 {
   public static void main(String[] args) {
       MyThread myThread = new MyThread();
       Thread thread = new Thread(myThread);
       thread.setDaemon(true);
       thread.start();
       for (int i = 1; i <= 200; i++) {
           System.out.println("主线程正在执行... " + i);
      }

  }
}

2.死锁

死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁线程。

你的钥匙丢完了。进不了门?咋办?找开锁公司。

开锁公司说你得给我提供你的身份证原件。我才能给你开锁。

你说你得先开门,我的身份证原件在屋里锁着呢。

结果你们两个僵持不下,导致开门这个事推进不了

开发中禁止使用死锁。

面试会问:

应用场景:并发场景,多线程。线程之间互不相让。得借助锁。

加锁的目的是为了线程安全,但是物极必反。尤其是加了锁以后。

死锁是一种状态,当两个线程互相持有对方的资源的时候,却又不主动释放这个资源的时候。会导致死锁。这两个线程就会僵持住。代码就无法继续执行。

线程1 有锁1

线程2 锁2

线程1会等待锁2 的释放

线程2会等待锁1的释放

1

面试题:

手写一个死锁!!!

package com.qfedu.b_deadThread;

class DeadLock implements Runnable {
   private boolean flag;//标记属性
   private Object obj1;//锁住的对象
   private Object obj2;//锁住的对象

   public DeadLock(boolean flag, Object obj1, Object obj2) {
       this.flag = flag;
       this.obj1 = obj1;
       this.obj2 = obj2;
  }

   @Override
   public void run() {
       //true
       if (flag) {//如果设置为true,就让线程1进入到if语句中
           synchronized (obj1) {//锁住的是obj1对象
               //线程1持有obj1锁
               System.out.println(Thread.currentThread().getName() + "拿到了锁1");
               try {
                   Thread.sleep(1000);
              } catch (InterruptedException e) {
                   e.printStackTrace();
              }
               System.out.println("等待锁2的释......");
               //我想在线程1中去使用线程2中的那个锁2 obj2
               //线程1里面想用obj2锁对象
               //也走不下去了
               //线程1也没有释放obj1
               synchronized (obj2) {
                   System.out.println("123");
                   System.out.println(Thread.currentThread().getName() + "拿到了锁1");

              }
          }
      }
       if (!flag) {//如果设置为false,就让线程2进入到if语句中
           synchronized (obj2) {//锁住的是obj2对象
               //线程2持有obj2这个锁
               System.out.println(Thread.currentThread().getName() + "拿到了锁2");
               try {
                   Thread.sleep(1000);
              } catch (InterruptedException e) {
                   e.printStackTrace();
              }
               System.out.println("等待锁1的释......");
               //只有obj1释放掉以后,才能在线程2中对obj1加锁
               //想一个问题,如果obj1锁对象没有被释放,那么下面这个代码
               //线程2中去锁obj1
               //在这等着呢 往下走不下去了 线程2没有释放obj2对象
               synchronized (obj1) {
                   System.out.println("456");
                   System.out.println(Thread.currentThread().getName() + "拿到了锁1");
              }
          }
      }

  }
}
public class Demo1 {
   public static void main(String[] args) {
       Object obj1 = new Object();
       Object obj2 = new Object();
       //线程1可以进入到run方法中 if (flag)
       DeadLock deadLock = new DeadLock(true, obj1, obj2);
       new Thread(deadLock, "线程1").start();
       //线程2 可以进入倒run方法中if(!flag)
       DeadLock deadLock1 = new DeadLock(false, obj1, obj2);
       new Thread(deadLock1, "线程2").start();
  }
}

3.线程生命周期【面试】

1)新建:当一个Thread类或其子类的对象被声明并创建时。新生的线程对象属于新建状态。

(2)就绪(可运行状态):处于新建状态的线程执行start()方法后,进入线程队列等待CPU时间片,该状态具备了运行的状态,只是没有分配到CPU资源。

(3)运行:当就绪的线程分配到CPU资源便进入运行状态,run()方法定义了线程的操作。

(4)阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时终止自己的的执行,进入阻塞状态。

(5)死亡:当线程执行完自己的操作或提前被强制性的终止或出现异常导致结束,会进入死亡状态。

2

4.和线程相关的Object类下面的方法

Object类下面的方法:

public final void wait()
        throws InterruptedException
voidnotify()唤醒正在等待对象监视器的单个线程。
void notifyAll()唤醒正在等待对象监视器的所有线程。

、导致当前线程等待,直到另一个线程调用该对象的notify()方法或notifyAll()方法。 换句话说,这个方法的行为就好像简单地执行呼叫wait(0)

总结:至少两个线程,其中一个线程中使用对象.wait() 那么这个线程就会阻塞,代码不会往下执行了。如何想让这个线程往下执行呢?再开另外一个线程,使用对象.notify()去唤醒另外那个等待线程。

3

 

package com.qfedu.c_wait;

//创建这个类的目的,就是实例化出来对象,然后拿这个对象
//调用wait方法和notify方法
//wait方法和notify方法是object对象的方法
class Message {
   private String message;

   public Message(String message) {

       this.message = message;
  }

   public String getMessage() {
       return message;
  }

   public void setMessage(String message) {
       this.message = message;
  }

   @Override
   public String toString() {
       return "Message{" +
               "message='" + message + '\'' +
               '}';
  }

}
//导致当前线程等待,直到另一个线程调用该对象的[`notify()`
class WaiterThread implements Runnable {
   //想一个问题?WaiterThread 使用message对象调用
   //wait() 咋解决?
   private Message msg;

   public WaiterThread(Message msg) {
       this.msg = msg;
  }

   @Override
   public void run() {
       //先获取当前线程名字
       String name = Thread.currentThread().getName();
       System.out.println(name + "等待唤醒时间:" +System.currentTimeMillis());
       //让等待线程去阻塞,去等待 这个线程执行不下去了
       //锁的是msg对象
       synchronized (msg) {//为啥是哟个wait的时候要加锁?等会将
           try {
               msg.wait();//代码走到这,当前这个线程阻塞,不往下走了
               //咱们得想办法让这个等待线程继续执行下去,咋办?
               //在另外一个线程中去调用notify方法那么等待线程就会执行下去
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
           System.out.println("123");
           System.out.println(name + "被唤醒的时间:" + System.currentTimeMillis());
      }


  }
}
//唤醒线程
class NotifyThread implements Runnable {
   //也要用同一个对象是WaiterThread线程中同一个对象调用notify()方法
   private Message msg;

   public NotifyThread(Message msg) {
       this.msg = msg;
  }
   @Override
   public void run() {
       try {
           //我的想法是不能先让唤醒线程执行
           Thread.sleep(1000);
      } catch (InterruptedException e) {
           e.printStackTrace();
      }
       String name = Thread.currentThread().getName();
       System.out.println(name + "开始唤醒等待线程");
       synchronized (msg) {
           msg.setMessage("我是修改之后的message值");
           //msg.notify();
           msg.notifyAll();//唤醒所有线程
      }

  }
}
public class Demo1 {
   public static void main(String[] args) {
       Message message = new Message("我是message属性");
       WaiterThread waiterThread = new WaiterThread(message);
       NotifyThread notifyThread = new NotifyThread(message);
       //如果等待线程好几个 咋办呢?
       new Thread(waiterThread, "等待线程1").start();
       new Thread(waiterThread, "等待线程2").start();
       new Thread(waiterThread, "等待线程3").start();
       new Thread(notifyThread, "唤醒线程").start();

       //等待线程等待唤醒时间:1660187660718   等待线程
       //唤醒线程开始唤醒等待线程       唤醒线程
       //123 等待线程
       //等待线程被唤醒的时间:1660187661740 等待线程
       //这叫线程之间的通信问题!!!
  }
}

总结:

新建两个线程:
一个是等待线程
线程里面的代码从上往下执行的,但是使用object.wait(),就这个方法一用,你的线程就
阻塞了,就处于等待状态。意味着当前的代码到了wait方法以后的代码暂时不执行了
另外一个是唤醒线程。
唤醒线程中使用object.notify()方法,这个方法是专门唤醒刚才那个等待线程。让等待线程继续执行

思考:

在使用wait和notify的时候都加了锁 为啥?
因为我要知道对那个对象进行唤醒的!!!
再举个生活中的例子:
大学的时候在楼底下等过自己的女朋友。
你就是等待线程
你女朋友就是唤醒线程
你在楼底下等的时候 就是在wait。
你女朋友下楼之后,唤醒你 咱们走吧。
但是你女朋友 去唤醒别人的男朋友?这就扯犊子了,所有加锁de保证是同一个对象的

wait需要持有锁的原因是,你肯定需要知道在哪个对象上进行等待,如果不持有锁,将无法做到对象变更时进行实时感知通知的作用。与此同时,为了让其他线程可以操作该值的变化,它必须要先释放掉锁,然后在该节点上进行等待。不持有锁而进行wait,可能会导致长眠不起。而且,如果不持有锁,则当wait之后的操作,都可能是错的,因为可能这个数据已经过时,其实也叫线程不安全了。总之,一切为了安全,单独的wait做不成这事。

扩展

Thread类join方法
join()方法,因为join()方法底层是就是通过wait()方法实现的。
让"主线程"等待(WAITING状态),一直等到其他线程不再活动为止,然后"主线程"再执行
join在英语中是“加入”的意思,join()方法要做的事就是,当有新的线程加入时,主线程会进入等待状态,一直到调用join()方法的线程执行结束为止。
面试题:
如何先让所有的子线程执行,最后再执行主线程,咋解决?
join!!!
package com.qfedu.c_wait;

class MyThread1 implements Runnable {

   @Override
   public void run() {
       //join方法的作用是阻塞,即等待线程结束,才继续执行。
       //如果使用了Thread.currentThread().join()之后
       //一直阻塞,无法终止
//       Thread thread = Thread.currentThread();
//       try {
//           thread.join();
//       } catch (InterruptedException e) {
//           e.printStackTrace();
//       }
       for (int i = 0; i < 100; i++) {
           System.out.println(Thread.currentThread().getName() + ":"+i);
      }
  }
}
class MyThread2 implements Runnable {

   @Override
   public void run() {

       for (int i = 0; i < 100; i++) {
           System.out.println(Thread.currentThread().getName() + ":"+ i);
      }
  }
}

public class Demo2 {
   public static void main(String[] args) throws InterruptedException {

       //main是主线程!!!
       Thread thread1 = new Thread(new MyThread1(), "MyThread1");
       thread1.start();
       thread1.join();
       Thread thread2 = new Thread(new MyThread2(), "MyThread2");
       thread2.start();

       thread2.join();
       //让"主线程"等待(WAITING状态),一直等到其他线程不再活动为止,然后"主线程"再执行
       //所以这次打印的结果都是主线程在最下面!!!
       //加join的目的是可以让主线程和子线程可控
       //如果不加join的话,主线程和子线程随机交叉打印!!!
       //2204班有个面试题:
       //   先让所有的子线程执行,最后再执行主线程,咋出来?join!!!
       
       for (int i = 0; i < 100; i++) {
           System.out.println("主线程:" + i);
      }

  }
}
package com.qfedu.c_wait;

class Father implements Runnable {

   @Override
   public void run() {
       //son是一个线程 Father是一个线程
       //但是Father 线程的run方法里面调用了Son线程
       //在main主函数中,只需要启动Father线程,在Father线程
       //又执行了Son线程,所以可堪看成Father 是Son的父线程(主线程)

       Son son = new Son();
       Thread thread = new Thread(son);
       thread.start();
       try {
           //加了join之后,thread 是son线程,son线程对应的主线程(Father)
           //会等待,等待子线程执行完以后才执行的
           thread.join();
      } catch (InterruptedException e) {
           e.printStackTrace();
      }
       for (int i = 0; i < 1000; i++) {
           System.out.println("Father线程:" + i);
      }
  }
}
class Son implements Runnable {
   @Override
   public void run() {
       for (int i = 0; i < 1000; i++) {
           System.out.println("son线程:" + i);
      }
  }
}
public class Demo3 {
   public static void main(String[] args) {
       Father father = new Father();
      Thread thread =  new Thread(father);
      thread.start();
  }
}

 

5.生产者消费者模式【重点难点】

生活中例子:

卖家:BYD汽车厂商

买家: 咱们75名学生

学虎想买一辆 比亚迪汉 , 告知汽车厂商我要买车。这个学虎会进入到等待状态

等到比亚迪厂家造完车以后,再通知学虎来提车。如果比亚迪厂家有现车,学虎就直接提车。

如果产品需要生产的话,消费者进入到阻塞状态 wait

如果产品不需要生产的话,消费者直接购买

这些会牵涉到咱们刚讲过的wait和notify方法!!!

只不过业务逻辑又复杂了啊

package com.qfedu.d_procus;

//为啥要新建这类Goods?
//还记得上午讲的Message类
//因为两个线程要通信。这个Goods就是通信的桥梁!!!
//goods.wait() 消费者等待
//goods.notoify() 生产者唤醒消费者
class Goods {
   private String name;//商品名字
   private double price;//商品价格
   private boolean isProduct;//
   //isProduct是否有这个商品, true 没有这个产品需要生产
   //false 有这个产品,不需要生产
   //有参构造


   public Goods(String name, double price, boolean isProduct) {
       this.name = name;
       this.price = price;
       this.isProduct = isProduct;
  }

   public String getName() {
       return name;
  }

   public void setName(String name) {
       this.name = name;
  }

   public double getPrice() {
       return price;
  }

   public void setPrice(double price) {
       this.price = price;
  }

   public boolean isProduct() {
       return isProduct;
  }

   public void setProduct(boolean product) {
       isProduct = product;
  }

   @Override
   public String toString() {
       return "Goods{" +
               "name='" + name + '\'' +
               ", price=" + price +
               ", isProduct=" + isProduct +
               '}';
  }
}
//接下来搞两个线程?一个消费者线程 一个是生产者线程
class Customer implements Runnable {
   //为啥要定义这个goods变量? 因为两个线程要共享同一个资源!!!

   private Goods goods;

   public Customer(Goods goods) {
       this.goods = goods;
  }

   @Override
   public void run() {
       //写业务逻辑,业务比较麻烦
       while (true) {//一直消费
           synchronized (goods) {
               //goods.isProduct true 需要生产,没有商品 false 不需要生产
               if (!goods.isProduct()) {
                   //不需要生产的,消费者直接购买
                   System.out.println("消费者购买了:" + goods.getName() + ",价格为:" + goods.getPrice());
                   //购买完以后 商品没有了 将isProduct这个变量修改为true
                   goods.setProduct(true);
                   //大家有没有想过这个问题?消费者在购买的时候,生产者等待
                   //唤醒生产者去生产车了
                   goods.notify();//可以先防一下,等会再看!!!


              } else {
                   //需要生产的!!!,没商品(咋办)
                   //消费者进入到阻塞状态!!!
                   try {
                       goods.wait();
                  } catch (InterruptedException e) {
                       e.printStackTrace();
                  }

              }

          }
      }

  }
}
class Producter implements  Runnable {
   //为啥要定义这个goods变量?
   private Goods goods;
   public Producter(Goods goods) {
       this.goods = goods;
  }
   @Override
   public void run() {
       int count = 0;
       //生产者,业务逻辑比较麻烦
       while (true) {//你一直消费,我一直生产
           synchronized (goods) {
               if (goods.isProduct()) {//true 需要生产的!!
                   //造车,就是赋值 对goods对象进行赋值
                   //奇数造一种车, 偶数造另外一种车
                   if (count % 2 == 0) {//偶数
                       goods.setName("奥迪A8");
                       goods.setPrice(78.9);
                  } else {//奇数
                       goods.setName("五菱大光");
                       goods.setPrice(12.9);
                  }
                   //生产一辆车,一定要记得有车了
                   //标记就改为 false 就证明不需要再生产了
                   goods.setProduct(false);
                   System.out.println("生产者生产了:" + goods.getName() + ",价格为:" + goods.getPrice());

                   count++;
                   //生产完以后,唤醒消费者。让消费者去提车
                   goods.notify();

              } else {
                   //不需要生产
                   //不需要生产 有货,生产着歇着,阻塞
                   try {
                       goods.wait();
                  } catch (InterruptedException e) {
                       e.printStackTrace();
                  }
              }
          }
      }
  }
}
public class Demo1 {
   public static void main(String[] args) {
       Goods goods = new Goods("东风41", 67.8, false);
       Producter producter = new Producter(goods);
       new Thread(producter).start();

       Customer customer = new Customer(goods);
       new Thread(customer).start();
       /**
        * 谁先抢到线程?消费者?还是生产者?
        * //如果是消费者抢到执行权,不用说!!!直接打印消费者购买了东风
        * //如果生产者抢到执行权,生产者wait,那就意味着必须去执行消费者线程
        * 消费者购买了:东风41,价格为:67.8
        * //此时isProduct是true 需要时生产
        * 还是消费者和生产者抢这个执行权
        * //假如生产者抢到了 就会打印生产者生产了啥。
        * //假如消费者抢到了执行权,消费者进入倒阻塞状态!!!
        * //消费者进入倒阻塞,那么肯定生产者得执行了
        * 生产者生产了:奥迪A8,价格为:78.9
        *
        * 还是两个线程抢这个执行权
        * 消费者购买了:奥迪A8,价格为:78.9
        * 生产者生产了:五菱大光,价格为:12.9
        * 消费者购买了:五菱大光,价格为:12.9
        * 生产者生产了:奥迪A8,价格为:78.9
        * 消费者购买了:奥迪A8,价格为:78.9
        * 生产者生产了:五菱大光,价格为:12.9
        * 消费者购买了:五菱大光,价格为:12.9
        * 生产者生产了:奥迪A8,价格为:78.9
        * 消费者购买了:奥迪A8,价格为:78.9
        */
  }
}
 
posted @ 2022-08-11 21:02  早睡晚起身体好  阅读(67)  评论(0)    收藏  举报