zoukankan      html  css  js  c++  java
  • Java线程和线程池

    一、Java线程

      几个概念:

      进程:一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。

      线程: 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。 

      多线程:在一个进程中创建多个线程,用多线程只有一个目的,那就是更好的利用cpu的资源,如磁盘IO,网络,计算等。

      并行与并发:

      •   并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时,如Hadoop中MapReduce的并行计算等;
      •   并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时,而是cpo对于多个线程进行cpu资源的调度;

      线程的创建方式

    new Thread(new Runnable() {
    
        @Override
        public void run() {
            // TODO Auto-generated method stub
        }
    }).start();

      线程的状态

        线程在Running的过程中可能会遇到阻塞(Blocked)情况

      1. 调用join()和sleep()方法,sleep()时间结束或被打断,join()中断,IO完成都会回到Runnable状态,等待JVM的调度。
      2. 调用wait(),使该线程处于等待池(wait blocked pool),直到notify()/notifyAll(),线程被唤醒被放到锁定池(lock blocked pool ),释放同步锁使线程回到可运行状态(Runnable)
      3. 对Running状态的线程加同步锁(Synchronized)使其进入(lock blocked pool ),同步锁被释放进入可运行状态(Runnable)。

        此外,在runnable状态的线程是处于被调度的线程,此时的调度顺序是不一定的。Thread类中的yield方法可以让一个running状态的线程转入runnable。

      线程安全:多线程在并发情况下对于共用的属性进行操作时,会出现线程不安全现象,因此,我们需要对操作公共属性的代码或者方法进行同步锁,以保证属性的线程安全。通常,我们使用synchronized关键字来实现同步锁:

      synchronized对于代码块的使用:

    public class Thread1 implements Runnable {
       Object lock;
       public void run() {  
           synchronized(lock){
             ..do something
           }
       }
    }

      synchronized对于方法的使用:

    public class Thread1 implements Runnable {
       public synchronized void run() {  
            ..do something
       }
    }

      使用wait和notify来实现:

    /**
       * 生产者生产出来的产品交给店员
       */
      public synchronized void produce()
      {
          if(this.product >= MAX_PRODUCT)
          {
              try
              {
                  wait();  
                  System.out.println("产品已满,请稍候再生产");
              }
              catch(InterruptedException e)
              {
                  e.printStackTrace();
              }
              return;
          }
    
          this.product++;
          System.out.println("生产者生产第" + this.product + "个产品.");
          notifyAll();   //通知等待区的消费者可以取出产品了
      }
    
      /**
       * 消费者从店员取产品
       */
      public synchronized void consume()
      {
          if(this.product <= MIN_PRODUCT)
          {
              try 
              {
                  wait(); 
                  System.out.println("缺货,稍候再取");
              } 
              catch (InterruptedException e) 
              {
                  e.printStackTrace();
              }
              return;
          }
    
          System.out.println("消费者取走了第" + this.product + "个产品.");
          this.product--;
          notifyAll();   //通知等待去的生产者可以生产产品了
      }

      如果创建多线程应用,每次都new Thread对象的话,会有很多的弊端,如:

      a. 每次new Thread新建对象性能差。
      b. 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
      c. 缺乏更多功能,如定时执行、定期执行、线程中断。

      因此,java提供线程池的方式,用于创建线程任务,并对线程任务进行管理,如获取执行状态,销毁线程等等。

    线程管理类

      Executor(顶级接口)

        并发编程的一种编程方式是把任务拆分为一系列的小任务,即Runnable,然后将这些任务提交给一个Executor执行,Executor.execute(Runnalbe) 。Executor在执行时使用其内部的线程池来完成操作。execute没有返回值。

      Executor的子接口有:

        ExecutorService、ScheduledExecutorService

      Executor的已知实现类:

        AbstractExecutorService、ScheduledThreadPoolExecutor、ThreadPoolExecutor。

      ExecutorService(接口)

        ExecutorService接口继承了Executor接口,是Executor的扩展子接口;

        ExecutorService中的submit接收的实现Runable接口的对象或者callable接口的对象;

        Executor中的execute方法没有返回值,而submit方法有future返回值;

        future的使用:

          1.如果任务已经执行完成,就可以通过 Future.get() 方法获得执行结果。需要注意的是,Future.get() 方法是一个阻塞式的方法,如果调用时任务还没有完成,会等待直到任务执行结束。

          2.可以通过Future.cancel()取消pending中的任务;

          3.当调用 shutDown 方法时,线程池会停止接受新的任务,但会完成正在 pending 中的任务;

       

      Executors(类)

        Executors 是一个工具类,类似于 Collections。提供工厂方法来创建不同类型的线程池,如:

          newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
          newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
          newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
          newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

      CompletionService(类)

        使用ExecutorService类的时候,我们常维护一个list保存submit的callable task所返回的Future对象。然后在主线程中遍历这个list并调用Future的get()方法取到Task的返回值。

            其实除了使用ExecutorService外,还可通过CompletionService包装ExecutorService,然后调用其take()方法去取Future对象。

              区别:CompletionService和ExecutorService的主要的区别在于submit的task不一定是按照加入自己维护的list顺序完成的。

          ExecutorService中从list中遍历的每个Future对象并不一定处于完成状态,这时调用get()方法就会被阻塞住,如果系统是设计成每个线程完成后就能根据其结果继续做后面的事,这样对于处于list后面的但是先完成的线程就会增加了额外的等待时间。

                而CompletionService的实现是维护一个保存Future对象的BlockingQueue。只有当这个Future对象状态是结束的时候,才会加入到这个Queue中,take()方法其实就是Producer-Consumer中的Consumer。它会从Queue中取出Future对象,如果Queue是空的,就会阻塞在那里,直到有完成的Future对象加入到Queue中。所以,先完成的必定先被取出。这样就减少了不必要的等待时间。

      总结:异步计算的线程按照职责分为3类:

        1. 异步计算的发起线程(控制线程):负责异步计算任务的分解和发起,把分解好的任务交给异步计算的work线程去执行,发起异步计算后,发起线程可以获得Futrue的集合,从而可以跟踪异步计算结果

            2. 异步计算work线程:负责具体的计算任务

            3. 异步计算结果收集线程:从发起线程那里获得Future的集合,并负责监控Future的状态,根据Future的状态来处理异步计算的结果。

    参考文章:

    Java中的多线程你只要看这一篇就够了

    Java并发编程 - Executor,Executors,ExecutorService, CompletionServie,Future,Callable

  • 相关阅读:
    android studio 开发环境配置
    解决 php-cgi 启动时提示缺少 msvcr110.dll 的问题
    PHP5.5 + IIS + Win7的配置
    PHP版本VC6和VC9、Non Thread Safe和Thread Safe的区别
    [6]Telerik TreeView 复选框
    详解.NET IL代码(一)
    第三章 续:时间控件(TimePicker)
    第二章 时间控件(DateTime Picker)
    jquery常用方法
    RESTClient
  • 原文地址:https://www.cnblogs.com/malcolmfeng/p/9870709.html
Copyright © 2011-2022 走看看