线程的几种状态及其经典的生产者-消费者模型

五种状态:

  • 创建初始化状态
    • 创建一个线程类,new一个实例出来,线程就进入初始化状态
  • 就绪(Runnable):只缺CPU
    • 可运行状态只说明有资格运行,CPU调度程序没挑中你,就只能在可运行状态
    • 调用线程start()方法,进入线程Runnable
    • 当前线程sleep方法结束,或者其他线程join结束,或者某个线程拿到对象锁,这些均可使线程进入可运行状态
    • 当前线程时间片用完了,调用当前线程的yield ()方法,礼让,当前线程可进入可运行状态
    • 锁池里的线程拿到对象锁后,进入可运行状态
  • 运行:有CPU+其他资源
    • CPU调度程序从可运行池里选择一个就绪线程,这些线程进入运行状态Running的唯一方法
  • 阻塞:缺CPU,有其他资源
    • 当前线程调用sleep()方法,当前线程可进入阻塞状态
    • 运行在当前线程的其他线程调用join方法,当前线程进入阻塞状态。
    • 当用户输入,当前线程进入阻塞状态
  • 等待队列(属于阻塞的一种)
    • 调用obj的wait()方法,notify方法前,必须获取对象锁,也就是必须在synchronization代码段内。
    • 实现步骤:线程1在运行状态时获取对象A的锁,然后调用了对象A的wait()方法,并释放锁,即可进入等待队列里,其他线程B获取到对象A的锁,进入同步代码块,后调用对象A的notifyAll方法,唤醒所有在等待池里面线程/或者notify,然后唤醒的线程进入锁池里面等待获取对象锁。当线程B释放锁的时候,锁池里的线程竞争锁,获取到锁的线程则进入可运行状态等待cpu调度时间片。
  • 锁池状态
    • 前线程想调用对象A的同步方法时,发现对象A的锁被别的线程占有,此时当前线程进入锁池状态。简言之,锁池里面放的都是想争夺对象锁的线程。
    • 当一个线程1被另外一个线程2唤醒时,1线程进入锁池状态,去争夺对象锁。
    • 锁池是在同步的环境下才有的概念,一个对象对应一个锁池。
  • 死亡:正常死亡/杀死
    • 当前线程run()完成时,或者方法完成时,即可认为它死去,这个线程对象可能是活的,但它已经不是单独执行的线程了。线程一旦结束就不可能重新进入可运行状态,除非程序程序启动,已经死亡的线程调用start()方法,会抛出非法异常

在这里插入图片描述

多个线程并发访问同一个资源时(对象、方法、代码块),经常会出现一些“”不安全“”的线程

如何解决多线程并发情况争夺资源安全问题?

  • 加锁:保证多个线程 串行执行,不会出现多个线程争夺资源问题
synchronized

synchronized**(同步锁也是可重入锁,锁上枷锁):给共享的资源加锁**

  • 加锁的形式:

    • 对象锁:给非静态方法加锁、代码块加锁,锁的是对象,而非引用地址
    • 类锁:静态方法加锁、锁的是类
  • 解锁:解锁时机

    • 报异常,synchronizaed自动解锁
    • 调用await()方法 。
    • 正常解锁:正常执行完毕,自动解锁。
  • 注意事项:

    • wait()、notify、notifyAll这几个Object的方法必须在Synchronized锁的代码块上使用
    • synchronized实现同步的基础:java中每一个对象都是可以作为锁,具体有三种形式
      • 对于普通同步方法,锁是当前实例对象
      • 对于静态同步方法,锁的是当前类的class对象
      • 对于同步方法块,锁是synchonized括号里配置的对象。
    • 当一条线程试图访问同步代码块时,它首先必须得到锁,退出或者抛出异常时释放锁,也就是锁如果一个实例对象的非静态同步方法获取锁后,该实例对象的其非他静态同步方法必须等待获取锁的方法释放锁后才能获取锁,可是别的实例对象的非静态方法因为跟该实现对象的非静态同步方法用的是不同的锁。所有代打该实例对象已获取锁的非静态同步方法释放锁就可以获取他们自己的锁。
    • 所有的静态同步方法用的也是同一把锁-类对象本身(类锁),所以静态同步方法和非静态同步方法,用的是不同的锁,所以二者不存在竞争,但是一旦一个静态同步方法获取锁后,其他的静态同步方法必须等待该静态同步方法释放锁后才可以获取锁(即不同对象的静态同步方法使用的是同一把锁。)
    • 一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中一个synchronized方法了,其他线程都只能等待,换句话说,某一时刻内,只能有唯一一个线程去访问这些synchronized方法,锁的是当前对象this,被锁定后,其他线程都不能进入到当前对象的其他synchronized方法。
  • 死锁

    • 概念

      • 死锁是指两个或两个以上的进程(线程)在运行过程中因争夺资源形成的一种僵局(Deadly-Embrac),若无外力作用,这些进程(线程)都无法前进–一个线程A要执行完成需要获取锁s和锁h,另外一个线程B要执行完成需要获取锁h和锁s,当A获取锁s时准备要获取H锁,但此时线程B已经获取完锁,小要获取锁s但锁s又被线程A获取了,此时就会变成死锁
    • 条件

      • 互斥条件:进程要求堆所分配的资源进行排它性控制,即在一段时间内资源仅为某一进程所占有(类似于锁已经被拿需要等待释放才可以占有该资源)

    请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保存不放

    不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。

    环路等待条件:在发生死锁时,必然存中一个进程-资源的环形链

读写锁ReadWriteLock

一般项目中都是读多写少,但在并发情况下读也有可能会出现,比如脏读等待,因此读锁就有了存在的意义。

public interface ReadWriteLock {
    /**
     * Returns the lock used for reading.
     *
     * @return the lock used for reading
     */
    Lock readLock();

    /**
     * Returns the lock used for writing.
     *
     * @return the lock used for writing
     */
    Lock writeLock();
}
  • 读锁:readLock():加了读锁的资源,可以在没有写锁的情况下,可以被多个线程共享,即如果一个线程A对资源加了读锁,则其他线程也可以加读锁进行读取资源,但如果需要申请加写锁,则需要等待该资源的读锁被释放才可以对其加写锁。
  • 写锁:资源被线程加了写锁(独占锁),则禁止其他线程对其申请读锁和写锁,需要申请锁的线程均陷于阻塞等待。
public class Test {
  static   ReentrantReadWriteLock reentrantReadWriteLock=new ReentrantReadWriteLock();
 private  void readMeathod(){
       reentrantReadWriteLock.readLock().lock();
       try {
           for (int i = 0; i < 9999; i++) {
               System.out.println(Thread.currentThread().getName() + "读操作");
           }
           System.out.println(Thread.currentThread().getName() + "读完毕");
       }finally {
           reentrantReadWriteLock.readLock().unlock();
       }
 }
 private  void writeMethod(){
    reentrantReadWriteLock.writeLock().lock();

     try {

             for (int i = 0; i < 9999; i++) {
                 System.out.println(Thread.currentThread().getName() + "写操作");
             }
             System.out.println(Thread.currentThread().getName() + "写完毕");

     }finally {
         reentrantReadWriteLock.writeLock().unlock();
     }
 }
    public static void main(String[] args) throws InterruptedException {
     Test test=new Test();
        new Thread(()->{
            test.readMeathod();
            test.writeMethod();
        },"t1").start();
        new Thread(()->{
            test.readMeathod();
            test.writeMethod();
        },"t2").start();

    }
}

synchronizaed一般结合线程的几个方法即效果:

  • Sleep(long mills):一定是当前线程调用此方法,当前线程进入阻塞,但不释放对象锁,睡眠时间过了线程自动苏醒进入可运行状态,作用:给其他线程执行机会

  • yield() :一定是当前线程调用,当前线程自动放弃获取cpu时间片,由运行状态变为可运行状态,让cpu调度程序选择线程,作用相同优先级的线程轮流执行,但不保证一定是轮流执行,是概率问题。实际中无法保证yield达到让步的目的,因为该让步线程有可能再次被cpu选择执行。

  • join()/join(long millis),当前线程里调用其他线程的join方法,当前线程阻塞,当不释放对象锁,直到线程执行完毕或者时间到,当前线程进入可运行状态

  • wait() :当前线程调用,当前线程释放对象锁,进入等待队列,依靠对象调用notify/notifyAll()方法才可以被唤醒/或者时间到自然唤醒。

  • notify() :唤醒在此对象监视器上等待队列里的当个线程,选择是任意的,notifyAll()唤醒通过该对象调用wait()方法进入等待池的所有线程。

    生成者消费者模式

    1.使用wait、notifyAll 和synchronized来实现生成者和消费者

    2.使用阻塞队列,内部采用的是同步方法

    wait()调用后当前线程会释放锁进入等待池,等待被唤醒后进入对象锁池,最终获取到锁的对象进入可运行状态等待cpu调度获取时间片

    采用if-else效率比采用while语句效率要低

    /**
     * 商品类
     */
    public class Product {
        private int no;
        private String name;
    }
    
    /**
     * @Author:苏牧夕
     * @Date:2020/3/16 19:07
     * @Version 1.0
     *共享资源类
     */
    public class Resources {
        private volatile LinkedList<Product> resources = new LinkedList<>();
    
        public boolean add(Product val) {
            return resources.add(val);
        }
    
        public Product get() {
            return resources.poll();
        }
    
        public int size() {
            return resources.size();
        }
    }
    /*
    *生成者
    */
    public class Productor {
        private Resources resources;
    
        public Productor(Resources resources) {
            this.resources = resources;
        }
    
        //synchronizeds在这里锁的是这个对象实例this(是对象锁),而非类锁
        public synchronized void put(Product product){
    //        try {
                //这里经典写法是用while写法,
                // 而非if-else,但其实if-else也可以实现
                //下面解析原因,首先线程获取到对象锁后进入到这里判断当前库存释放小于最大库存量
                //如果小于则进行添加,并且唤醒所有在等待池的线程(等待池必然有消费者线程,
                // 因为消费者和生成者用的是不同的对象锁,所以此时消费者那边有线程可以进行消费产品),
                //如果已经达到最大容量则让当前线程睡眠(会进入等待阻塞池),等待消费者消费产品
                //解析一下为什么不会出现超库存的原因,线程进入等待的位置是在else里面,假如线程通过cpu调度获取时间片,可以进行相关处理了
                //此时因为上一次判断已经进入到else,
                // 被唤醒并且获取到处理机会的线程会直接走完else然后释放锁,因此是不会出现超库存问题
    //         if (resources.size()<100){
    //             resources.add(product);
    //             TimeUnit.MILLISECONDS.sleep(10);
    //             System.out.println("生产成功,产品号"+product.getNo());
    //             notifyAll();
    //         }else{
    //             wait();
    //             System.out.println("生产失败,产品号"+product.getNo());
    //         }
    //        } catch (InterruptedException e) {
    //            e.printStackTrace();
    //        }
            /**
             * 经典的while写法
             */
            while (resources.size()>=100){
                try {
                    System.out.println("生产失败,产品号"+product.getNo());
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            resources.add(product);
            try {
                TimeUnit.MILLISECONDS.sleep(10);
                System.out.println("生产成功,产品号"+product.getNo());
                notifyAll();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
        }
    }
    /*
    *消费者
     */
    public class Comsumer1 {
        private Resources resources;
    
        public Comsumer1(Resources resources) {
            this.resources = resources;
        }
        public synchronized void get() {
            try {
                if (resources.size()>0) {
                    System.out.println("消费产品:"+resources.get().toString());
                    notifyAll();
                } else {
                    wait();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //为什么这里用while不用if取代的原因:
            // 如果用if下次唤醒就继续往下走,就会进行消费,但是有可能这时候库存已经被其他消除消耗殆尽了
            //就会出现异常,用while的原因目的是为了唤醒后重写进行判断释放符号要求
            //if和if-else是两种不同的情况。
    //        while (resources.size()==0){
    //
    //            try {
    //                //如果没有库存则让当前线程进入等待池
    //                System.out.println("当前暂时没有库存");
    //                wait();
    //            } catch (InterruptedException e) {
    //                e.printStackTrace();
    //            }
    //        }
    //        //有库存则进行消费
    //        System.out.println("消费产品:"+resources.get().toString());
    //        //消费完唤醒所以下次,生成者就会进行生产
    //        notifyAll();
        }
    }
    public class Test {
      public static void main(String[] args) throws InterruptedException {
        Resources r1=new Resources();
        //问题:为什么不使创建多个消费者类或者多个生产者类,而是使用一个类采用多条线程进行操作
        // 原因是因为多个生成者或者消费者实例,就有多个对象锁,而synchronized在非静态方法的时候锁的是实例对象
        //如果生产消费者的生产/消费方法使用的是静态方法,则可以使用多个生产消费者
        Comsumer1 c1 =new Comsumer1(r1);
        Productor p1=new Productor(r1);
          for (int i = 0; i < 4; i++) {
              int finalI = i;
              new Thread(() -> {
                  while (true) {
                      p1.put(new Product(finalI,"name"+ finalI));
                  }
              }, "product" + i).start();
          }
          for (int i = 0; i < 2; i++) {
              new Thread(()->{
                  while (true){
                      c1.get();
                  }
              }, "consumer" + i).start();
          }
      }
    }
    
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值