多线程梳理

多线程

线程基本概念

线程概念

  • 作为进程里面最小的执行单元它就叫做线程,一个进程的不同执行路径就叫做线程。是CPU时间片调度的最小单位。

启动方式

  • new Thread().start();(继承Thread)
  • new Thread(Runnable).start();(实现Runnable接口)
  • new Thread(new FutureTask<>(Callable)).start();(使用Callable和Future创建线程)

常用方法

  • sleep(long millis)

    • 线程休眠,当前线程暂停一段时间让给别的线程去执行
  • yield()

    • 当前线程正在执行的时候停止下来进入等待队列
  • join()

    • t1、t2两个线程,t1中执行t2.join(),t2则会被切换到执行,然后t1就等待t2运行完后再运行t1

线程状态

  • New新创建状态

    • 线程被创建,还没有被调用start方法,在线程运行之前还有一些基础工作要做
  • Runable可运行状态

    • 一旦调用start方法,线程就处于Runnable状态,一个可运行的线程可能正在运行也可能没有运行,这取决于操作系统给线程提供运行的时间
  • Blocked阻塞状态

    • 表示线程被锁阻塞,它暂时不参与活动
  • Waiting等待状态

    • 线程暂时不参与活动,并且不运行任何代码,这消耗最少的资源,直到线程调度器重新激活它
  • Timed waiting超时等待状态

    • 和等待状态不同的是,它是可以在指定的时间自行返回的
  • Terminated终止状态

    • 表示当前线程已经执行完毕。导致线程终止有两种情况:1.run方法执行完毕正常退出;2.因为一个没有捕获的异常而终止了run方法。
  • 状态间的转换(生命周期)


    线程状态.png

synchronized

  • synchronized(Object)不能使用String常量、Integer、Long等

  • 线程同步

    • 锁的是对象不是代码

    • 锁升级

      • 偏向锁(记录这个线程的ID)
      • 自旋锁(如果线程争用,就升级为自旋锁<线程数量少>)
      • 重量级锁10次(线程数量多)
  • 同步方法

    • 一个方法使用synchronized关键字声明

      • public synchronized void method(){}
        
  • 同步代码块

    • 代码块使用synchronized关键字修饰

      • synchronized(Object){}
        

volatile

  • 保证线程的可见性

    • 线程的可见性在CPU的级别是用缓存一致性来保证的
  • 保证指令的有序性(禁止指令重新排序)

    • 禁止指令重排序CPU级别是禁止不了的,那是内部运行的过程,提高效率的。在虚拟机级别加了volatile后,这个指令重排续可以禁止。
    • volatile关键自禁止指令重排的两个含义:
      1.当程序执行到volatile变量操作时,在其前面的操作已经全部执行完毕,并且结果会对后面的操作可见,在其后面的操作还没有进行
      2.在进行指令优化时,在volatile变量之前的语句不能在volatile变量后面执行
  • java中的原子性、可见性、有序性

    • 原子性:对于基本数据类型变量的读取和赋值操作是原子性操作,即这些操作是不可被中断。(操作已经是最小单位了,不可再拆分)(volatile不保证原子性)

      • x = 3 ; //语句1(将3写入工作内存)
        y = x ; //语句2(先读取x的值,再将x的值写入工作内存)
        x++;    //语句3(先读取x的值,对x的值进行加1,再向工作内存写入新值)
        
        上面3个语句中只有语句1是原子性操作。
      • java.util.concurrent.atomic包中有很多类使用了高效的机器级指令(不是锁)来保证其他操作的原子性。如AtomicInteger类提供了以原子方式将一个证书自增或自减的方法incrementAndGet和decrementAndGet
    • 可见性:指线程之间的可见性,一个线程修改的状态对另外一个线程是可见的。被volatile修饰的共享变量在修改后会立马更新到主存,而普通的共享变量则不能保证立马被更新到主存。

    • 有序性:java内存模型中允许编译器和处理器对指令进行重排序,重拍序不会影响到单线程执行的正确性,会影响到多线程并发执行的正确性。(除了volatile外,synchronized和Lock都可以保证有序性;不过后面两个是通过同时只有一个线程执行同步代码来保证的)

  • volatile的使用

    • 通常来说使用volatile必须具备以下两个条件(说白了就是需要具备原子性):
      1.对变量的写操作不会依赖当前值
      2.该变量没有包含在具有其他变量的不变式中

    • 常用场景一:状态标志

      • volatile boolean statusTag;
        
    • 常用场景二:双重检查模式(DCL)

      • public class Singleton {
        
        private Singleton() {
        }
        
        private volatile static Singleton instance = null;
        
        public static Singleton getInstance() {
          if (instance == null) {
              synchronized (Singleton.class) {
                  if (instance == null) {
                      instance = new Singleton();
                  }
              }
          }
          return instance;
         }
        }
        

可重入锁

  • 可重入锁就是在同一线程中,外层函数获取了锁之后,内层函数依然可以获得相同的锁。synchronized、ReentrantLock都是可重入锁

重入锁与条件对象(ReentrantLock、Condition)

  • ReentrantLock是Java SE5.0引入的,支持重进入的锁,它表示该锁能够支持一个线程资源的重复加锁。
  • Lock mLock = new ReentrantLock();
    mLock.lock();
    try{
     ...
    }finally{
      mLock.unlock;
    }
    //需要注意这里是在finally里进行锁的释放,因为防止在临界区内发生异常,后没有释放导致其他线程的永远堵塞。
    
  • 锁的条件对象Condition,一个锁对象拥有多个相关的条件对象,可以用newCondition方法获取一个条件对象,得到条件对象后调用await方法,当前线程就被阻塞了并放弃了锁。一旦调用了await方法,它就会进入该条件的等待集并处于阻塞状态,直到另一个线程调用了同一个条件的signalAll/signal方法时为止,就会解除了因为这一条件而等待的所有线程/某一线程的阻塞,之后这些线程能够在当前线程退出同步方法后,通过竞争实现对对象的访问。(Condition condition = new ReentrantLock().newCondition();//获取Condition对象)

java内存模型

  • java内存模型的抽象示意图


    java内存模型抽象示意图.png
    • java中的堆内存采用存储对象实例,堆内存是被所有线程共享的运行时的内存区域

    • 本地内存是java内存模型的一个抽象概念,并不是真是存在,它涵盖了缓存、写缓冲区、寄存器等区域

    • java内存模型控制线程之间的通信,它决定一个线程对主存共享变量的写入合适对另外一个线程可见。线程A与线程B之间若要通信,则必须经历下面两个步骤:

      1.线程A把线程A本地内存中更新过的共享变量刷新到主存中去。
      2.线程B到主存中去读取线程A之前已更新过的共享变量。

  • 线程执行互斥代码的过程

    • 获得互斥锁
    • 清空工作内存
    • 从主内存拷贝变量的最新副本到工作内存
    • 执行代码
    • 将更改后的共享变量的值刷新到主内存
    • 释放互斥锁
  • volatile可见性

    • 写操作

      • 对volatile变量执行写操作时,会在写操作后加入一条store屏障指令
      • 改变线程工作内存中的volatile变量副本的值
      • 将改变后的副本的值从工作内存刷新到主内存
    • 读操作

      • 对volatile变量执行读操作时,会在读操作前加入一条load屏障指令
      • 从主内存中读取volatile变量的最新值到线程的工作内存中
      • 从工作内存中读取volatile变量的副本

线程池

在java1.5中提供了Executor框架用于把任务的提交和执行解藕,任务的提交交给Runnable或者Callable,而Executor框架用来处理任务。Executor中最核心的成员就是ThreadPoolExecutor,它是线程池的核心是实现类。

ThreadPoolExecutor构造方法中的参数

  • corePoolSize:核心线程数。默认情况下线程池是空的,只有任务提交是才会创建线程。如果当前线程数少于corePoolSize,则创建新的线程来处理任务;如果当前运行的线程数等于或多于corePoolSize则不在创建信的线程。如果调用线程池的prestartAllcoreThread方法,则线程池会提前创建并启动所有的核心线程来等待任务
  • maxmunPoolSize:线程池允许创建的最大线程数。如果任务队列满了并且线程数少于maxmunPoolSize时,则线程池仍旧会创建信的线程来处理任务。
  • keepAliveTime:非核心线程闲置的超时时间。超过这个时间,非核心线程就会被回收。如果任务很多,并且每个任务的执行时间很短,则可以调大keepAliveTime来提高线程的利用率。另外,如果设置allowCoreThreadTimeOut属性为true,keepAliveTime也会应用到核心线程
  • TimeUnit:keepAliveTime参数额时间单位。可选择的单位有天(DAYS)、小时(HOURS)、分钟(MINUTES)、秒(SECONDS)、毫秒(MILLISECONDS)等
  • workQueue:任务队列。如果当前线程数大于corePoolSize,则将任务添加到此任务队列中。该任务队列是BlockingQueue类型的,就是阻塞队列。
  • ThreadFactory:线程工厂。可以用线程工厂给每个创建出来的线程设置名字。一般情况下无须设置该参数。
  • RejectedExecutionHandler:饱和策略。这是当任务队列和线程池都满了时所采取的应对策略,默认是AbortPolicy,表示无法处理新任务,并抛出RejectedExecutionException异常。另外还有三种异常:
    1.CallerRunsPolicy:用调用者所在的线程来处理任务。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。
    2.DiscardPolicy:不能执行的任务,并将该任务删除。
    3.DiscardOldestPolicy:丢弃队列最近的任务,并执行当前的任务。

线程池的处理流程图

线程池的处理流程.png

线程池的执行示意图

线程池的执行示意图.png

线程池的种类

  • 同过直接或间接地配置ThreadPoolExecutor的参数可以创建不同类型的ThreadPoolExecutor,其中有4种比较常用。

  • FixedThreadPool:可重用固定线程池

    • Executor类种提供了创建FixedThreadPool的方法:
      public static ExecutorService newFixedThreadPool(int nThreads) {
          return new ThreadPoolExecutor(nThreads, nThreads,
                                        0L, TimeUnit.MILLISECONDS,
                                        new LinkedBlockingQueue<Runnable>());
      }
      
    • FixedThreadPool只有核心线程,并且数量固定,没有非核心线程,任务队列采用了无界阻塞队列LinkedBlockingQueue。
  • CachedThreadPool:是一个根据需要创建线程的线程池

    • 创建方法:
      public static ExecutorService newCachedThreadPool() {
         return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                       60L, TimeUnit.SECONDS,
                                       new SynchronousQueue<Runnable>());
      }
      
    • corePoolSize为0,maximumPoolSize设置为integer.MAX_VALUE,CacheThreadPool没有核心线程,非核心线程是无界的,空闲等待为60s,使用了SynchronousQueue阻塞队列(不存储元素的阻塞队列,每个插入操作必须等待另一个线程的移除操作,任何一个移除操作都等待另一个线程的插入操作)。CacheThreadPool比较适合于大量的需要立即处理并且耗时比较少的任务。
  • SingleThreadExecutor:是使用单个工作线程的线程池

    • 创建方法:
      public static ExecutorService newSingleThreadExecutor() {
          return new FinalizableDelegatedExecutorService
              (new ThreadPoolExecutor(1, 1,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>()));
      }
      
    • SingleThreadExecutor能确保所有的任务在一个线程中按照顺序逐一执行
  • ScheduledThreadPool:是一个能实现定时和周期性任务的线程池

    • 创建方法:
      //class Executors
      public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
          return new ScheduledThreadPoolExecutor(corePoolSize);
      }
      
      //class ScheduledThreadPoolExecutor
      public ScheduledThreadPoolExecutor(int corePoolSize) {
         super(corePoolSize, Integer.MAX_VALUE,
               DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
               new DelayedWorkQueue());
      }
      
      • 主要用于在给定延时之后运行任务或者定期处理任务
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,914评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,935评论 2 383
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,531评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,309评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,381评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,730评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,882评论 3 404
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,643评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,095评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,448评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,566评论 1 339
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,253评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,829评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,715评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,945评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,248评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,440评论 2 348

推荐阅读更多精彩内容