再谈java线程

线程状态

  • 描述

    	当线程被创建并启动之后,它既不是已启动就进入到了执行状态,也不是一直处于执行状态。在线程的声明周期中有六中状态。
    	java api中java.lang.Thread.State这个枚举给出了线程的六种状态
         线程状态。 线程可以处于以下状态之一: 
    
    线程状态 导致状态发生条件
    NEW(新建) 线程刚被创建,但是还没有启动,还没有调用start方法
    RUNNABLE(可运行) 线程可以在java虚拟机中运行的状态,可以是正在运行自己的代码,也可能没有,这取决于操作系统处理器
    BLOCKED(锁阻塞) 当一个线程试图获取一个对象锁,而该对象被其他线程所持有,则该线程进入到blocked状态;当该线程持有锁时,该线程就进入到runnable状态
    WAITING(无限等待) 一个线程在等待另一个线程执行一个动作(新建),该线程就进入到waiting状态,进入这种waiting状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能唤醒
    TIMED_WAITING(计时等待) 同waiting状态,有几个方法有超时参数,调用他们将进入timed waiting状态,这一状态一直保持到超时期满或者是收到了唤醒通知。带有超时参数的常用方法有Thread.sleep(),Object.wait().
    TERMINATED(被终止) 因为run方法正常退出而死亡,或者因为没有捕获的异常终止run方法而死亡。

    TIMED_WAITING(计时等待)

    TIMED_WAITING在java api中描述为;一个正在限时等待另一个线程执行一个(唤醒)动作的线程处于这一状态,其实当我们调用sleep方法之后,当前正在执行的线程就进入到了计时等待状态。
        
    //1.进入到thread waiting状态的是一种常见的操作是调用sleep方法,单独的线程也可以调用,不一定非要有协作关系
    //2.为了让其他线程有机会执行到,一般建议将Thread.sleep()调用放到run方法内,这样才能保证线程执行过程中会睡眠
    //sleep与锁无关,线程睡眠到期会自动苏醒,并返回到runnable状态。sleep里面的参数指定的时间是线程不会不会运行的最短时间,因此,sleep方法不能保证该线程睡眠到期后就会立刻执行
    

    BLOCKED(锁阻塞)

    BLOCKED状态在java api中描述为;一个正在阻塞等待一个监视器锁(锁对象)的线程处于这一状态。
    比如;线程a与线程b代码中同时使用一把锁,如果线程a获取到锁对象,线程a就进入runnable状态,反之线程b就进入到blacked状态。
    

    WAITING(无限等待)

    waiting状态在java api中的描述为;一个正在无线等待另一个线程执行一个特别的(唤醒)动作的线程处于这一状态。
    	一个调用了某个对象 锁.wait()方法的线程,会等待另一个线程调用此对象的 锁.notify()或者锁.notifyAll()方法
    	其实waiting状态它并不是一个线程的操作,它体现的是多个线程之间的'通信',可以理解为多个线程之间的协作关系,多个线程争取锁,同时相互之间又存在协作关系
    

等待唤醒机制

  • 线程间通信

    • 概念

      多个线程在处理同一个资源,但是处理的动作(线程的任务)却又不同。
      
    • 为什么要处理线程之间的通信;

      多个线程并发在执行时,在默认情况下cpu是随机切换线程的,当我们需要多个线程共同来完成一件任务时,并且我们希望他们有规律的执行,那么多线程之间就需要一些协调通信,以此来帮助我们达到多线程共同操作一份数据。
      
    • 如果保证线程之间通信有效利利用资源;

      多个线程在处理同一资源的时候,并且任务还不相同,需要线程通信来帮助我们解决线程之间对同一个变量的使用或者操作。
      就是多个线程在操作同一份数据时,避免同一个变量的争夺,也就是我们需要通过一定的手段使各个线程有效的利用资源。这种手段就是---》等待唤醒机制
      
    • 什么是等待唤醒机制?

      这是多个线程间的一种协作机制。
      就是一个线程进行规定协作后,就进入到了等待状态'wait()',等待其他线程执行完他们的指定代码后,再将其唤醒'notify()';
      在有多个线程进行等待时,如果需要,可以使用'notifyAll()'来唤醒所有的等待线程。
      wait/notify就是线程间的一种协作机制。
          
          等待唤醒中的方法;
          等待唤醒机制就是用来解决线程间通信问题的。可以使用到的方法有三个如下
          1.wait();线程不在活动,不在参与调度,进入到waiting设置中,因此不会浪费cpu资源,也不会去竞争锁了,这时的线程状态就是waiting,他还要等着别的线程执行一个特别的动作,就是唤醒通知'notify()',在这个对象上等待的线程从wait set中释放出来,重新进入到调度队列(ready queue)中。
          2.notify();选取通知对象的wait set中的一个线程释放。
          3.notifyAll();释放所通知对象的wait set中的全部线程。
          
      //备注;哪怕只同时了一个等待线程,被通知的线程也不能立即回复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁了,所以它需要再次尝试着去获取锁(很可能面临其他线程的竞争),成功后才能当初调用wait方法之后的地方恢复执行。 
          
      //总结;如果能获取到锁,线程就从waiting状态转变成runnable状态,否则,从wait set中,又进入set中,线程就从waiting状态转变成blocked状态。
          
      '调用wait和notify方法的注意细节:'
          1.wait方法和notify方法必须有同一个锁对象调用。因为,对应的锁对象可以通过notify唤醒使用同一个锁对象的wait方法后的线程。
          2.wait与notify方法是属于Object类中的方法。因为锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。
          3.wait与notify方法必须在同步代码块中或者同步方法中使用。因为必须通过锁对象调用这两个方法来实现等待与唤醒。
      
      包子铺线程生产包子,吃货线程消费包子,当包子没的时候状态为false,吃货线程需要等待,包子铺线程生产包子包子状态为true,通知吃货线程并解除吃货状态,因为已经有包子,,包子铺线程进入等待状态,接下来,吃货线程能够进一步取决于锁的获取状态情况,如果吃货线程取得到了锁,就执行吃包子动作,包子吃完了包子状态为false,通知包子铺线程解除包子铺线程等待状态,此时吃货线程就进入到等待状态,包子铺线程能否进一步执行则取决于锁的获取情况。
      

线程池

  • 概念

    	一个容纳多个线程的容器,其中的线程可以反复的使用,省去了频繁的创建线程对象的操作,无需反复创建线程而消耗过多的资源。
        
        由于线程池中有很多操作都是与优化系统资源有关的,
    
  • 线程池的工作原理

  • 线程的好处

    1.降低了资源消耗,减少了线程的创建于销毁的次数,每个工作线程都可以被重复利用,可执行多个任务
    2.提高了响应速度。当任务到达时,任务可以不需要等到线程的创建就能立即执行。
    3.提高了线程的客观理性,可以根据系统的承受能力,调整线程中工作线程的数目,防止因为消耗过多的内存,而导致服务器的崩溃 (每个线程需要大约1mb,线程开的越多,消耗的内存也就越大,死机的风险也就更高)。
    
  • 线程池的使用(JDK 5.0)

    • 描述

      java.util.concurrent.Executor ,但是严格意义讲,Executor它并不是一个线程池,他只是执行线程的一个工具,真正的线程接口时 java.util.concurrent.ExecutorSerivice(线程接口) 。
          
      在java.util.concurrent.Excutors 线程工程类提供了一些静态工厂,生产一些常用的线程池,官方建议使用Executors来创建线程池对象。
          
      void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
      <T> Future<T> submit(Callable<T> task):执行任务,有返回值,一般又来执行Callable
      void shutdown() :关闭连接池
      
    • Executors 创建线程的常用方法

      static ExecutorService newFixedThreadPool(int nThreads);返回的就是线程池对象(创建的是有界的线程池,也就是池中的线程个数可以指定最大数量)
      Future<?> submit(Runnable task); 获取线程池中的某一个线程对象,并执行。
          Future接口;用来记录线程记录任务执行完毕后产生的结果。线程的创建于使用。
      
    • 使用线程池中线程对象的步骤

      1. 创建线程池对象。

        (使用线程池的工厂类Executors里面提供的静态方法newFixedThreadPool生产一个指定的数量的线程池)

      2. 创建Runnable接口子类对象。(task)

        (定义一个类,实现Runnable接口,重写run方法设置线程任务,new实现类对象)

      3. 提交Runnable接口子类对象。(take task)

        (调用ExecutorService中的方法submit,传递线程任务(实现类对象),开启新线程,执行run方法)

      4. 关闭线程池。(一般不做)

        (调用ExecutorService类中方法shutdown销毁线程池)

        (如果线程池关闭就不能再次获取否则报异常)

      注意;submit方法结束后,程序并不会终止,是因为线程池控制了线程的关闭将使用完的线程又归还到了线程池中。


posted @ 2020-12-11 22:12  MikiKawai  阅读(112)  评论(0编辑  收藏  举报