关于java:多线程的一些知识

33次阅读

共计 6854 个字符,预计需要花费 18 分钟才能阅读完成。

  • 线程和过程

    过程:一个在内存中独立运行的程序,每一个过程都有本人独立的内存空间,一个过程能够创立多个线程。是操作系统分配资源的根本单位。

    线程:由过程创立,用于执行工作,一个过程起码有一个线程,能够有多个线程,线程共享过程中的数据。每个线程有本人的程序计数器、虚拟机栈和本地办法栈,所以零碎在生产一条线程或者在多条线程中切换工作时开销比过程小的多,所以线程也被称为轻量级过程。是处理器任务调度和执行的根本单位

  • 线程的三种创立形式

    • 重写 thread 的 run 办法

      Thrad thread = new Thread(){
              @Override
              public void run(){System.out.println(1)
              }
      };
      thread.start();
  • 实现 runnable

    Thread thread = new Thread(new Runnable(){
             @Override
             public void run(){System.out.println(2)
             }
    });
    thread.start();
  • 实现 callback

    Callable<Integer> callable = new Callable<Integer>(){
          @Override
        public Integer call() throw Exception{return 3;}
    };
    FutureTask<Integer> task = new FutureTask<>(callable);
    new Thread(task).start();
    while(task.isDone()){
      try{System.out.println(task.get());
      }catch(InterruptedException e){e.printStackTrace();
      }catch(ExecutionException e){e.printStackTrace();
      }
    }
  • 线程的状态
  • NEW:创立还未执行,即还没有调用 start()办法
  • RUNNABLE:执行状态,调用 start()办法之后进入该状态
  • BLOCKED:阻塞状态,调用 start()办法之后拿不锁时的状态
  • WAITING:期待状态,调用 start()办法拿到锁之后执行时发现没有资源,开释锁给其余线程,进入该状态,等到有资源之后,继续执行。
  • TIMED_WAITING:超时期待,调用 start()办法拿到锁之后执行发现没有资源,开释锁给其余线程,进入超时期待状态,然而当超过设置的等待时间之后,即便拿不到资源也会继续执行。
  • TERMINATED:线程执行完结状态
  • wait 和 sleep 的区别

    • wait

      1. 暂停以后线程,开释锁
      2. 属于 Object 对象
      3. wait 只能在同步块中应用
      4. 能够随时唤醒
    • Sleep

      1. 暂停以后线程,不开释锁
      2. 属于线程 Thread
      3. 能够在任何场景下应用
      4. 只有 sleep 到设置的工夫才会被从新唤醒
  • 为什么应用多线程

    进步程序的解决效率。创立线程是一个开销很大,应用线程池能够防止线程创立和销毁带来的性能开销,防止大量线程因抢占系统资源而导致阻塞,可能提供对线程的简略治理,定时执行、距离执行等。

  • 什么是线程池

    线程池的根本思维就是一种对象池,就是在程序启动的时候开拓一块空间,而后用于寄存线程(未死亡),外面的线程执行调度有线程池来解决。当须要应用线程解决时,就从池中获取一个对象执行工作,执行完工作之后,再将线程池放回到线程池中,这样就防止了新建线程带来的开销,能够服务曾经创立好的线程,达到节俭资源的目标。

  • 线程池的次要组件

    • 线程池管理器 -ThreadPool

      用于创立线程和治理线程,包含创立线程池、销毁线程池、增加工作

    • 工作线程 -WorkThread

      线程池中的线程,用于执行工作,没有工作时处于期待状态

    • 工作接口 -Task

      每个工作必须实现的接口,以供线程调度工作执行,它规定了工作的入口、工作的收尾工作以及工作的执行状态

    • 工作队列 -taskQueue

      没有被调度的工作放入到该中央,期待工作线程调度执行,起到缓冲的作用。

  • 创立线程池的办法

    • newCachedThreadPool

      创立的是一个可缓存的线程池,如果线程池长度超过解决须要,则发出闲暇线程,若无,则新建线程。

      public static void main(String[] args) {ExecutorService threadPool = Executors.newCachedThreadPool();
              for (int i = 0; i < 1000; i++) {
                  final int index = i;
                  try {Thread.sleep(index * 1000);
                  } catch (Exception e) {e.printStackTrace();
                  }
                  threadPool.execute(new Runnable() {
                      @Override
                      public void run() {System.out.println(Thread.currentThread().getName()+":"+index);
                      }
                  });
              }
          }
  • newFixedThreadPool

    创立的是一个定长的线程池,在创立时指定线程数。超出的线程会在队列外面期待。

    public static void main(String[] args) {ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
            for (int i = 0; i < 10; i++) {
                final int index = i;
                fixedThreadPool.execute(new Runnable() {
                    @Override
                    public void run() {
                        try {System.out.println(Thread.currentThread().getName() + ":" + index);
                            // 三个线程并发
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {e.printStackTrace();
                        }
                    }
                });
            }
        }
  • newScheduledThreadPool

    创立的是一个定长的线程池,反对定时周期性的执行工作

    public static void main(String[] args) {ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
            // 示意提早 1 秒后每 3 秒执行一次
            scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {System.out.println(Thread.currentThread().getName() + ": delay 1 seconds, and excute every 3 seconds");
                }
            }, 1, 3, TimeUnit.SECONDS);
        }
  • newSingleThreadExecutor

    创立只有 1 个线程的线程池,只会用惟一的工作线程执行工作,保障所有工作依照队列里的程序 (FIFO,LIFO, 优先级) 进行解决。

    public static void main(String[] args) {ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
            for (int i = 0; i < 10; i++) {
                final int index = i;
                singleThreadPool.execute(new Runnable() {
                    @Override
                    public void run() {
                        try {System.out.println(Thread.currentThread().getName() + ":" + index);
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {e.printStackTrace();
                        }
                    }
                });
            }
        }
  • 创立线程池的要害类 ThreadPoolExecutor

    通过剖析下面四种办法的源码能够看到,其实他们都是通过 ThreadPoolExecutor 创立的线程池,可见 ThreadPoolExecutor 是创立线程池的一个要害类,他在 JDK 中的 UML 类关系图如图所示:

咱们能够通过 ThreadPoolExecutor 来创立一个线程池

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) 

通过构造函数咱们能够看到创立线程池所须要的几个参数

  • corePoolSize

    线程池的根本大小, 当提交一个工作到线程池时,线程池会创立一个线程来执行工作,即便其余闲暇的根本线程可能执行新工作也会创立线程,等到须要执行的工作数大于线程池根本大小时就不再创立。如果调用了线程池的 prestartAllCoreThreads 办法,线程池会提前创立并启动所有根本线程。

  • maximumPoolSize

    线程池最大大小 ,线程池容许创立的最大线程数。如果队列满了,并且已创立的线程数小于最大线程数,则线程池会再创立新的线程执行工作。值得注意的是如果应用了 无界的工作队列 这个参数就没什么成果。

  • keepAliveTime

    线程流动放弃工夫,线程池的工作线程闲暇后,放弃存活的工夫。所以如果工作很多,并且每个工作执行的工夫比拟短,能够调大这个工夫,进步线程的利用率。

  • TimeUnit

    线程流动放弃工夫的单位 ,放弃存活的工夫的工夫单位,可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒) 和毫微秒(NANOSECONDS, 千分之一微秒)。

  • workQueue

    工作队列,用于保留期待执行的工作的阻塞队列。

    BlockingQueue workQueue = null;
    workQueue = new ArrayBlockingQueue<>(5);// 基于数组的先进先出队列,有界
    workQueue = new LinkedBlockingQueue<>();// 基于链表的先进先出队列,无界
    workQueue = new SynchronousQueue<>();// 无缓冲的期待队列,无界
依据 `java.util.concurrent.ThreadPoolExecutor#execute` 源码能够看出线程池的解决流程

```java
#获取线程数
int c = ctl.get();
#1. 线程数量未达到 corePoolSize,则新建一个线程 (外围线程) 执行工作
if (workerCountOf(c) < corePoolSize) {if (addWorker(command, true))
        return;
    c = ctl.get();}
#2. 线程数量达到了 corePools,则将工作移入队列期待
if (isRunning(c) && workQueue.offer(command)) {int recheck = ctl.get();
    if (! isRunning(recheck) && remove(command))
        reject(command);
    else if (workerCountOf(recheck) == 0)
        addWorker(null, false);
}
#3. 队列已满,新建线程 (非核心线程) 执行工作
else if (!addWorker(command, false))
    #4. 队列已满,总线程数又达到了 maximumPoolSize,就会由 (RejectedExecutionHandler) 抛出异样
    reject(command);      
```
  • threadFactory

    线程工厂,用于设置创立线程的工厂,能够通过线程工厂给每个创立进去的线程设置更有意义的名字,Debug 和定位问题时十分又帮忙。

  • RejectedExecutionHandler

    回绝策略,当队列和线程池都满了,阐明线程池处于饱和状态,那么必须采取一种策略解决提交的新工作。这个策略默认状况下是 AbortPolicy,示意无奈解决新工作时抛出异样。

    以下是 JDK1.5 提供的四种策略:

    ​ AbortPolicy:抛弃工作并抛出 RejectedExecutionException 异样。

    ​ DiscardPolicy:抛弃工作,然而不抛出异样。

    ​ DisCardOldSetPolicy:抛弃队列最后面的工作,而后提交新来的工作。

    ​ CallerRunPolicy:由调用线程(提交工作的线程,主线程)解决该工作。

  • 线程池的状态

  1. 线程池的初始化状态是 RUNNING,可能接管新工作,以及对已增加的工作进行解决。
  2. 线程池处在 SHUTDOWN 状态时,不接管新工作,但能解决已增加的工作。调用线程池的 shutdown()接口时,线程池由 RUNNING -> SHUTDOWN。
  3. 线程池处在 STOP 状态时,不接管新工作,不解决已增加的工作,并且会中断正在解决的工作。调用线程池的 shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN) -> STOP。
  4. 当所有的工作已终止,ctl 记录的”工作数量”为 0,线程池会变为 TIDYING 状态。当线程池变为 TIDYING 状态时,会执行钩子函数 terminated()。terminated()在 ThreadPoolExecutor 类中是空的,若用户想在线程池变为 TIDYING 时,进行相应的解决;能够通过重载 terminated()函数来实现。
  5. 当线程池在 SHUTDOWN 状态下,阻塞队列为空并且线程池中执行的工作也为空时,就会由 SHUTDOWN -> TIDYING。
  6. 当线程池在 STOP 状态下,线程池中执行的工作为空时,就会由 STOP -> TIDYING。线程池彻底终止,就变成 TERMINATED 状态。线程池处在 TIDYING 状态时,执行完 terminated()之后,就会由 TIDYING -> TERMINATED。
  • 为什么阿里 java 开发手册不举荐应用 Executors 创立线程池,而是举荐手动创立线程池

    newFixedThreadPool 和 newSingleThreadExecutor 次要是因为沉积的申请解决队列可能会消耗十分大的内存,甚至 OOM。

    #newSingleThreadExecutor
    public static ExecutorService newSingleThreadExecutor() {
            return new FinalizableDelegatedExecutorService
                (new ThreadPoolExecutor(1, 1,
                                        0L, TimeUnit.MILLISECONDS,
                                        new LinkedBlockingQueue<Runnable>()));
        }
     
    #newFixedThreadPool
    public static ExecutorService newFixedThreadPool(int nThreads) {
            return new ThreadPoolExecutor(nThreads, nThreads,
                                          0L, TimeUnit.MILLISECONDS,
                                          new LinkedBlockingQueue<Runnable>());
        }

由源码能够看到这两个线程池都是应用 LinkedBlockingQueue 作为工作队列的,这个是无界的队列,即便你限度了线程数,然而工作还是会被放到队列中进行沉积,最终导致 OOM。

newCachedThreadPool 和 newScheduledThreadPool 次要是因为最大线程数是 Integer.MAX_VALUE,可能会创立数量十分多的线程,甚至 OOM。

#newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
  
#ScheduledThreadPoolExecutor
 public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

由源码能够看到 maximumPoolSize 为 Integer.MAX_VALUE,这样如果工作很多的时候会导致床和十分多的线程。

正文完
 0