• 多线程编程(1)


    1. 进程与线程

      通常,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开一个Word就启动了一个Word进程。大多时候一个进程需要同时干很多件事情,比如Word,它可以同时进行打字、拼写检查、打印等事情。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)。即一个程序至少有一个进程,一个进程至少有一个线程

      我们大学学操作系统的时候,都知道进程是资源分配的基本单位,线程是执行和调度的基本单位,线程本身不拥有资源,资源来自于它的进程。也就是说,进程在执行过程中有自己独立的内存空间,与其他进程相互隔离,因此进程间的通信需要另辟蹊径,比如常见的有管道通信、消息队列、信号量以及套接字等方法,同时,一个进程中有三大块——进程控制块(PCB)、数据段、代码段,这会导致进程间的会产生很大的开销。而线程与线程之间因为共享进程申请的内存区域,它们之间可以相互通信,因为线程的粒度小,使得线程的切换速度比进程快很多,可以极大地提高程序的运行效率,如下,是线程和进程的关系(图片摘自知乎)。

      线程分为两种:用户线程和守护线程。守护 线程,是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。守护线程是用来服务用户线程的,一旦用户线程全部运行结束,程序会终止,守护线程也会随之退出。

      在进入多线程之前,可以先看看线程的几种状态:

    • 1. 新建(NEW):新创建了一个线程对象。
    • 2. 就绪(RUNNABLE或READY) :线程正在参与竞争CPU的使用权。
    • 3. 运行(RUNNING):线程取到了CPU的使用权,正在执行。
    • 4. 阻塞(BLOCKED):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到满足条件(比如超时等待、唤醒)时,该线程重新回到就绪态,参与竞争CPU使用权。
    • 5. 等待(WAITING):线程无限等待某个对象的锁,或等待另一个线程结束的状态。
    • 6. 计时等待(TIME_WAITING):线程在某一段时间内等待某个对象的“锁”,或者主动休眠,亦或者等待一个线程结束,除非被中断,时间一到,马上回到就绪状态,被中断的方法则抛出异常。
    • 7. 终止(Terminated):即线程终止(线程的的代码被执行完毕)和执行过程出现异常或者被外界强制中断。

    状态的转换的具体转换如下图所示:

    2. Thread类和Runnable接口

      通过继承Thread类,实run方法即可实现一个线程类,常用的API如下:

     方法描述
    start() 从新建状态转化为就绪状态,开始参与CPU使用权的竞争。
    run() 直接调用该 Runnable 对象的 run 方法时直接取得CPU的使用权
    interrupt() 中断线程。在程序代码中搭配while (!Thread.interrupted()){..}使用。
    isDaemon() 判断当前线程是否是守护线程。
    setDaemon(boolean true) 将当前线程设置为守护线程,必须在调用start()之后才有效。
    setPriority(int priority) 更改线程的优先级。
    interrupt() 中断线程。
    isAlive() 测试线程是否处于活动状态。
    join(long millisec) 等待该线程终止的时间最长为 millis 毫秒。
    Thread.yield() 暂停当前正在执行的线程对象(让出当前线程的CPU,转为就绪状态),并执行其他线程。
    Thread.currentThread() 返回对当前正在执行的线程对象的引用。
    Thread.sleep(long millisec) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。

      由于java中的类是单继承的,而接口可以多继承。一个类实现多个接口的情况,因为接口只有抽象方法,具体方法只能由实现接口的类实现,在调用的时候始终只会调用实现类的方法(不存在歧义),因此在开发中通常使用Runnable。

    public class Thread1 implements Runnable {
        @Override
        public void run() {
            System.out.println("iii");
        }
    ​
        public static void main(String[] args) {
            Thread1 rt = new Thread1();
            Thread t = new Thread(rt);
            t.start();
        }
    }

      这里补充一下线程中断interrupt()函数,这个函数并不会中断某个线程,而是向该线程发送一个信号量,如果要使某个线程中断,则应该加上isInterrupt()函数去判断,然后再去做中断处理。如下代码:

    public class Thread3 implements Runnable{
        @Override
        public void run() {
            while(true){
                if(Thread.currentThread().isInterrupted()){
                    System.out.println("Something interrupted me.");
                    break;
                }
                else{
                    System.out.println("Thread is Going...");
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            Thread3 thread3 = new Thread3();
            Thread t = new Thread(thread3);
            t.start();
            Thread.sleep(3000);
            t.interrupt();
        }
    }

    3. 线程池

      当线程的在某一时刻大量的创建与销毁会消耗很多资源,我们可以提前创建好一些线程,将他们集中管理起来,形成一个线程池,需要使用的时候直接拿过来用,使用完后,放回线程池。

    Executor框架

      在 java.util.cocurrent 包下,通过该框架来控制线程的启动、执行和关闭,可以简化并发编程的操作,由Executors类的五个静态工厂方法创建,其常用方法如下。

    3.1 线程池的创建

    1. newFixedThreadPool:创建固定大小的线程池。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

      public class Executors {
          /*
          函数功能:创建一个固定长度的的线程池,用于保存任务的阻塞队列为无限制长度的LinkedBlockingQueue。 线程池中的线程将会一直存在除非线程池shutdown,即线程池中的线程没有受到存活时间的限制。
          */
          public static ExecutorService newFixedThreadPool(int nThreads) {
             /* 参数一 核心线程数大小(最小线程数),当线程数 < 参数一 ,会创建线程执行 runnable
              * 参数二 最大线程数, 当线程数 >= 参数二,会把runnable放入workQueue(参数5)中
              * 参数三 保持存活时间,空闲线程能保持的最大时间。
              * 参数四 时间单位
              * 参数五 保存任务的阻塞队列
              *public ThreadPoolExecutor(int corePoolSize,
                                    int maximumPoolSize,
                                    long keepAliveTime,
                                    TimeUnit unit,
                                    BlockingQueue<Runnable> workQueu
              */
                  return new ThreadPoolExecutor(nThreads1, nThreads2,
                                                0L, TimeUnit.MILLISECONDS,
                                                new LinkedBlockingQueue<Runnable>());
          }
          //...
      }
      ​
      ExecutorService es = Executors.newFixedThreadPool(20);
      //如果线程池中线程数过大或过小,都会影响性能
    2. newCachedThreadPool:创建一个可缓存空闲线程60秒的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。

      public class Executors {    
          public static ExecutorService newCachedThreadPool() {
                  return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                                60L, TimeUnit.SECONDS,
                                                new SynchronousQueue<Runnable>());
           }
          //...   
      }
      ​
      ExecutorService es = Executors.newCachedThreadPool();
      //缺点是在访问量突然很大的时候,会创建大量线程
    3. 创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。

      ExecutorService es = Executors.newSingleThreadExecutor();
      //等同于 ExecutorService es = Executors.newFixedThreadPool(1);
    4. newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

       public static void main(String[] args) {
              ScheduledExecutorService ses = Executors.newScheduledThreadPool(1);
              ses.scheduleWithFixedDelay(new Runnable() {
                  @Override
                  public void run() {
                      try {
                          Thread.sleep(3000);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                      System.out.println(new Date());
                  }
              }, 1000/*第一个周期开始的时间*/, 2000/*每个周期间隔的时间*/, TimeUnit.MILLISECONDS);
          }
    5. newSingleThreadScheduledExecutor:创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。

    3.2 线程池中线程的使用

      通过Executors类去获得的线程池都实现了ExecutorService这个接口。可以调用execute()或者submit()方法把相应的任务提交到线程池中去。

     1. execute(Runnable): 这个方法接收一个Runnable实例,并且异步的执行。

    public static void main(String[] args) {
            ExecutorService es = Executors.newCachedThreadPool();
    //        Future future =
            es.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("run the thread.");
                }
            });
            System.out.println("over");
     }
    /* output:
     * over
     * run the thread.
     */

    2. submit(Runnable): submit(Runnable)execute(Runnable)区别是前者可以返回一个Future对象,通过返回的Future对象,我们可以检查提交的任务是否执行完毕。

    public static void main(String[] args) throws ExecutionException, InterruptedException {
            ExecutorService es = Executors.newCachedThreadPool();
            Future future = es.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("run the thread.");
                }
            });
            future.get();  //future.get()方法会产生阻塞,直到上面的线程完成,即等待一秒钟
            System.out.println("over");
    }
    /* output:
     * run the thread.
     * over
     */

    3. submit(Callable): submit(Callable)submit(Runnable)类似,也会返回一个Future对象,但是参数Callable类中的call方法可以返回一个值,而Runable不行。

    public static void main(String[] args) throws ExecutionException, InterruptedException {
            ExecutorService es = Executors.newCachedThreadPool();
            Future future = es.submit(new Callable() {
                @Override
                public Object call() throws Exception {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    return "run the thread.";
                }
            });
            String rs = (String)future.get();  //future.get()方法会产生阻塞
            System.out.println("over and " + rs);
     }
    /* output:
     * over and run the thread.
     */

    4. invokeAny(Collection<? extends Callable<T>> tasks>): 方法输入接受一个Callable集合类型的参数,启动多个线程相互独立的去执行对应线程的任务,一旦有一个线程执行完毕,则返回,同时其他线程终止。

    public static void main(String[] args) throws ExecutionException, InterruptedException {
    ​
            ExecutorService es = Executors.newFixedThreadPool(3);
    ​
            Set<Callable<String>> callables = new HashSet<Callable<String>>();
    ​
            callables.add(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    Thread.sleep(2000);
                    return " first task";
                }
            });
    ​
            callables.add(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    Thread.sleep(1000);
                    return " second task";
                }
            });
    ​
            callables.add(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    Thread.sleep(3000);
                    return " third task";
                }
            });
    ​
            String rs = es.invokeAny(callables);
            System.out.println(rs);
    }
    ​
    /* output
     * second task
     */
    

    5. invokeAll(Collection<? extends Callable<T>> tasks>): 该方法则会并行的执行Callable集合类型的所有方法。

    public static void main(String[] args) throws ExecutionException, InterruptedException {
    ​
            ExecutorService es = Executors.newFixedThreadPool(3);
    ​
            Set<Callable<String>> callables = new HashSet<Callable<String>>();
    ​
            callables.add(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    Thread.sleep(2000);
                    return " first task";
                }
            });
    ​
            callables.add(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    Thread.sleep(1000);
                    return " second task";
                }
            });
    ​
            callables.add(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    Thread.sleep(3000);
                    return " third task";
                }
            });
    ​
            List<Future<String>> list= es.invokeAll(callables);
            for (Future<String> future:list) {
                String s = future.get();
                System.out.println(s);
            }
    }
    

    6. shotdown(): 调用该方法后,关闭线程池,已提交的方法会继续执行,执行结束后,线程池全部关闭,该方法是一个异步方法,一旦调用,立即返回。

    7.shotdownNow(): 调用该方法后,关闭线程池,已提交的方法也会被取消,线程池立即全部关闭,该方法是一个异步方法,一旦调用,立即返回。

    8. awaitTermination(timeout,unit): 调用该方法阻塞当前线程,使得线程池中的线程执行完毕,最长等待时间为timeout,此方法需要在调用shotdown/shotdownNow后才有效。

    public class ThreadSafe implements Runnable {
        private static int count = 0;
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                count++;
            }
        }
        public static void main(String[] args) throws InterruptedException {
            ExecutorService es = Executors.newFixedThreadPool(10);
            for (int i = 0; i < 20; i++) {
                es.execute(new ThreadSafe());
            }
            es.shutdown();  //不允许添加线程,异步关闭连接池
            es.awaitTermination(10L, TimeUnit.SECONDS); //等待连接池的线程任务完成
            System.out.println(count);
        }
    }
    /* output
    *  200
    */

    参考文献

    1. 庞永华. Java多线程与Socket:实战微服务框架[M].电子工业出版社.2019.3

    2. Executors类中创建线程池的几种方法的分析

     

     

  • 相关阅读:
    [debug] 解决在C++编写过程中的“找到一个或多个多重定义的符号”
    调试事件的收集
    [ida]查看某一函数在程序中被谁引用
    IDA+Windbg IDA+OD 连动调试插件
    一个简单的创建被调试进程的案例
    LOAD_DLL_DEBUG_EVENT 时读取 DllName
    【编译系统01】编译器
    [动态规划]石子合并问题
    xBIM 基础15 IFC导出Excel报表
    xBIM 基础14 使用LINQ实现最佳性能(优化查询)
  • 原文地址:https://www.cnblogs.com/helloworldcode/p/11715306.html
Copyright © 2020-2023  润新知