thread

2019-01-17  本文已影响0人  c7d122ec46c0

java多线程

线程的基础

线程进程区别

  1. 进程是操作系统的分配和调度系统内存资源、cpu时间片等<font color="red">资源</font>的基本单位,为正在运行的应用程序提供运行环境。
  2. 线程程序内部有并发性的顺序代码流,是cpu<font color="red">调度</font>资源的最小单元

Java线程模型

20160506143812820.jpg

Linux,windows 操作系统下都是使用内核线程 - Kernel Thread

内核线程

内核线程就是内核的分身,一个分身可以处理一件特定事情。内核线程的使用是廉价的,唯一使用的资源就是内核栈和上下文切换时保存寄存器的空间。支持多线程的内核叫做多线程内核(Multi-Threads kernel )。

  1. 内核态: CPU可以访问内存所有数据, 包括外围设备, 例如硬盘, 网卡. CPU也可以将自己从一个程序切换到另一个程序
  2. 用户态: 只能受限的访问内存, 且不允许访问外围设备. 占用CPU的能力被剥夺, CPU资源可以被其他程序获取
    线程的阻塞和唤醒需要CPU从用户态转为核心态,频繁的阻塞和唤醒对CPU来说是一件负担很重的工作

轻量级进程(LWP)是建立在内核之上并由内核支持的用户线程,它是内核线程的高度抽象,每一个轻量级进程都与一个特定的内核线程关联。内核线程只能由内核管理并像普通进程一样被调度。

线程的状态


教科书上线程的状态

[图片上传失败...(image-7717e8-1547719360944)]

  1. Ready 代表当前的调度实例在可执行队列中,随时可以被切换到占用处理器的运行状态。
  2. Running代表当前的调度实例正在占用处理器运行中。
  3. Blocked(waiting)代表当前的调度实例在等待相应的资源。

linux线程的状态:

java 线程的状态

 public enum State {
        /**
         * 线程被new出来
         */
        NEW,
        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         * 线程的runnable状态是指线程正在被虚拟机执行,但是它可能正在等待操作系统的资源比如处理器
         */
        RUNNABLE,
        BLOCKED,
        /**
         * 线程处于WAITING状态是在调用Object.wait,Thread.join,LockSupport#park()后
         */
        WAITING,
        /**
          * Thread.sleep,Object.wait带时间参数,Thread.join 带时间参数,
      * LockSupport.parkNanos,LockSupport.parkUntil时处于TIMED_WAITING
         */
        TIMED_WAITING,
        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
        TERMINATED;
    }

How does the thread state of Java map to linux or windows ? If the state of Java is runnable, what is on Linux or windows ?

A thread can be in only one state at a given point in time.
These states are virtual machine states which do not reflect
any operating system thread states.

java线程的状态和操作系统线程没有映射关系(来自java doc)

NEW

线程的创建方式

  1. 线程的创建与运行
    • java中有三种线程创建方式,实现Runnable接口的run方法,继承Thread类并重写run方法,使用FutureTask方式,JAVA 8可以使用CompletableFuture

    • Thread

      • 好处:获取当前线程方便,直接this
      • 坏处:不能继承其他类了,任务与代码没区分,无返回值
    • Runnable接口

      • 好处:可继承其他类,多任务
      • 坏处:无返回值
    • callable接口

      • 好处:有返回值
    • CompletableFuture的优势
      提供了异步程序执行的另一种方式:回调,不需要像future.get()通过阻塞线程来获取异步结果或者通过isDone来检测异步线程是否完成来执行后续程序。
      能够管理多个异步流程,并根据需要选择已经结束的异步流程返回结果。

RUNNABLE

        /**
         * 线程在等待监视器锁的状态,线程进入同步代码块或同步方法,或者调用wait()方              法后再次进
         * 入同步代码块或同步方法
         */

为什么只有runnable状态 没有区分running 和 ready状态

现在的时分(time-sharing)多任务(multi-task)操作系统架构通常都是用所谓的“时间分片(time quantum or time slice)”方式进行抢占式(preemptive)轮转调度(round-robin式)。
这个时间分片通常是很小的,一个线程一次最多只能在 CPU上运行比如10ms-20ms 的时间(此时处于 running 状态),也即大概只有 0.01 秒这一量级,时间片用后就要被切换下来放入调度队列的末尾等待再次调度。(也即回到 ready 状态)
由于线程切换的如此的快,因此把这两个统一为runnable状态
传统概念中的阻塞状态也可以映射到runnable状态

线程的阻塞

隐式锁(Synchronized)

相对JDK提供的concurrent包中的实现Lock接口的锁工具类,是jvm所实现的锁

隐式锁的使用

    //对普通方法同步
    public synchronized void sayGoodbye() {
        System.out.println("say good bye");
    }
    //对静态方法同步
    public synchronized static void sayHi() {
        System.out.println("say hi");
    }
    //对方法块同步
    public void sayHello() {
        synchronized (LockTest.class) {
            System.out.println("say hello");
        }
    }

synchronized的实现简单说明

  1. 从字节码角度分析
 0 ldc #6 <thread/ByteCodeDemo>
  2 dup
  3 astore_1
  4 monitorenter
  5 getstatic #2 <java/lang/System.out>
  8 ldc #7 <say hello>
 10 invokevirtual #4 <java/io/PrintStream.println>
 13 aload_1
 14 monitorexit
 15 goto 23 (+8)
 18 astore_2
 19 aload_1
 20 monitorexit
 21 aload_2
 22 athrow
 23 return

synchronized代码块是由monitorenter和monitorexit两个指令实现的。关于这两个字节码虚拟机规范是这么说的

monitorenter:任何对象都有一个 monitor(这里 monitor 指的就是锁) 与之关联(规范上说,对象与其 monitor 之间的关系有很多实现,如 monitor 可以和对象一起创建销毁,也可以线程尝试获取对象的所有权时自动生成)。当且仅当一个 monitor 被持有后,它将处于锁定状态。线程执行到 monitorenter 指令时,将会尝试获取 objectref 所对应的 monitor 的所有权,那么:如果 objectref 的 monitor 的进入计数器为 0,那线程可以成功进入 monitor,以及将计数器值设置为 1。当前线程就是 monitor 的所有者。如果当前线程已经拥有 objectref 的 monitor 的所有权,那它可以重入这个 monitor,重入时需将进入计数器的值加 1。如果其他线程已经拥有 objectref 的 monitor 的所有权,那当前线程将被阻塞,直到 monitor 的进入计数器值变为 0 时,重新尝试获取 monitor 的所有权。

monitorexit:objectref必须为reference类型数据。执行monitorexit指令的线程必须是objectref对应的monitor的所有者。指令执行时,线程把monitor的进入计数器值减1,如果减1后计数器值为0,那线程退出monitor,不再是这个monitor的拥有者。其他被这个monitor阻塞的线程可以尝试去获取这个monitor的所有权。

0 getstatic #2 <java/lang/System.out>
3 ldc #5 <say hi>
5 invokevirtual #4 <java/io/PrintStream.println>
8 return

对静态方法同步和方法块同步并没有 monitor 相关指令,而是多了 invokevirtual 指令。 invokevirtual 指令是用 来调用实例方法,依据实例的类型进行分派
Java 虚拟机规范上描述该指令:如果调用的是同步方法,那么与 objectref 相关的同步锁将会进入或者重入,就如同当前线程中执行了 monitorenter 指令一般。

虚拟机可以从方法常量池中的方法表结构(method_info structure)中的 ACC_SYNCHRONIZED 访问标志区分一个方法是否是同步方法。当调用方法时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否设置,如果设置了,执行线程先持有同步锁,然后执行方法,最后在方法完成时释放锁。

  1. synchronized锁的位置

    • 锁存放在对象头中

    对象实例由对象头、实例数据组成,其中对象头包括markword和类型指针,如果是数组,还包括数组长度。

    markword的结构,定义在markOop.hpp文件:

    //  64 bits:
    //  --------
    //  unused:25 hash:31 -->| unused:1   age:4    biased_lock:1 lock:2 (normal object)
    //  JavaThread*:54 epoch:2 unused:1   age:4    biased_lock:1 lock:2 (biased object)
    //  PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
    //  size:64 ----------------------------------------------------->| (CMS free block)
    //
    //  unused:25 hash:31 -->| cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && normal object)
    //  JavaThread*:54 epoch:2 cms_free:1 age:4    biased_lock:1 lock:2 (COOPs && biased object)
    //  narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->| (COOPs && CMS promoted object)
    //  unused:21 size:35 -->| cms_free:1 unused:7 ------------------>| (COOPs && CMS free block)
    
企业微信截图_15476924643731.png
  1. hotSpot 如何具体实现

    1. 源码中锁的入口

    在HotSpot的中有两处地方对monitorenter指令进行解析:一个是在bytecodeInterpreter.cpp#1816 ,另一个是在templateTable_x86_64.cpp#3667

    JVM中的字节码解释器(bytecodeInterpreter),用C++实现了每条JVM指令(如monitorenterinvokevirtual等),其优点是实现相对简单且容易理解,缺点是执行慢。后者是模板解释器(templateInterpreter),其对每个指令都写了一段对应的汇编代码,启动时将每个指令与对应汇编代码入口绑定,可以说是效率做到了极致。两者的原理是一致的,大家分析的时候可以基于字节码解释器的源码进行分析。

    1. 为什么要做优化

      monitor的本质是依赖于底层操作系统的Mutex Lock实现,操作系统实现线程之间的切换需要从用户态到内核态的切换,切换成本非常高

    2. 有哪些优化

      Java SE1.6为了减少获得锁和释放锁所带来的性能消耗,引入了“偏向锁”和“轻量级锁”,所以在Java SE1.6里锁一共有四种状态,无锁状态,偏向锁状态,轻量级锁状态和重量级锁状态,它会随着竞争情况逐渐升级。

      锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率。

      锁粗化(Lock Coarsening):将多个连续的锁扩展成一个范围更大的锁,用以减少频繁互斥同步导致的性能损耗。
      
      锁消除(Lock Elimination):JVM及时编译器在运行时,通过逃逸分析,如果判断一段代码中,堆上的所有数据不会逃逸出去从来被其他线程访问到,就可以去除这些锁。
      
      轻量级锁(Lightweight Locking):JDK1.6引入。在没有多线程竞争的情况下避免重量级互斥锁,只需要依靠一条CAS原子指令就可以完成锁的获取及释放。
      
      偏向锁(Biased Locking):JDK1.6引入。目的是消除数据再无竞争情况下的同步原语。使用CAS记录获取它的线程。下一次同一个线程进入则偏向该线程,无需任何同步操作。
      
      适应性自旋(Adaptive Spinning):为了避免线程频繁挂起、恢复的状态切换消耗。产生了忙循环(循环时间固定),即自旋。JDK1.6引入了自适应自旋。自旋时间根据之前锁自旋时间和线程状态,动态变化,用以期望能减少阻塞的时间。
      
    3. 加锁的流程

第一步,检查MarkWord里面是不是放的自己的ThreadId ,如果是,表示当前线程是处于 “偏向锁”.跳过轻量级锁直接执行同步体。


584866-20170419194339446-1408410540.png

第二步,如果MarkWord不是自己的ThreadId,锁升级,这时候,用CAS来执行切换,新的线程根据MarkWord里面现有的ThreadId,通知之前线程暂停,之前线程将Markword的内容置为空。
第三步,两个线程都把对象的HashCode复制到自己新建的用于存储锁的记录空间,接着开始通过CAS操作,把共享对象的MarKword的内容修改为自己新建的记录空间的地址的方式竞争MarkWord.
第四步,第三步中成功执行CAS的获得资源,失败的则进入自旋.
第五步,自旋的线程在自旋过程中,成功获得资源(即之前获的资源的线程执行完成并释放了共享资源),则整个状态依然处于轻量级锁的状态,如果自旋失败 第六步,进入重量级锁的状态,这个时候,自旋的线程进行阻塞,等待之前线程执行完成并唤醒自己


584866-20170419191951321-2145960409.png

WAITING

        /**
         * Thread.sleep,Object.wait带时间参数,Thread.join 带时间参数,
         * LockSupport.parkNanos,LockSupport.parkUntil时处于TIMED_WAITING
         */
ObjectMonitor() {
    _header       = NULL;//markOop对象头
    _count        = 0;
    _waiters      = 0,//等待线程数
    _recursions   = 0;//重入次数
    _object       = NULL;//监视器锁寄生的对象。锁不是平白出现的,而是寄托存储于对象中。
    _owner        = NULL;//指向获得ObjectMonitor对象的线程或基础锁
    _WaitSet      = NULL;//处于wait状态的线程,会被加入到wait set ObjectWaiter 类型;
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ;//处于锁block状态的线程,会被加入到entry set;
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;// _owner is (Thread *) vs SP/BasicLock
    _previous_owner_tid = 0;// 监视器前一个拥有者线程的ID
  }
class ObjectWaiter : public StackObj {  
 public:  
  enum TStates { TS_UNDEF, TS_READY, TS_RUN, TS_WAIT, TS_ENTER, TS_CXQ } ;  
  enum Sorted  { PREPEND, APPEND, SORTED } ;  
  ObjectWaiter * volatile _next;  
  ObjectWaiter * volatile _prev;  
  Thread*       _thread;  // ObjectWaiter 对应的线程的
  ParkEvent *   _event;   // 线程的ParkEvent
  volatile int  _notified ;  
  volatile TStates TState ;  
  Sorted        _Sorted ;           // List placement disposition  
  bool          _active ;           // Contention monitoring is enabled  

};  

在HotSpot虚拟机中,最终采用ObjectMonitor类实现monitor
ObjectMonitor的获取方法
ObjectMonitor * m = omAlloc (Self) ;//获取一个可用的ObjectMonitor

_WaitSet:
主要存放所有wait的线程的对象,也就是说如果有线程处于wait状态,将被挂入这个队列
_EntryList:
所有在等待获取锁的线程的对象,也就是说如果有线程处于等待获取锁的状态的时候,将被挂入这个队列。

   synchronized (objectLock) {
                while (list.size() == 1) {
                    try {
                        objectLock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.info("生产下");
                list.add(1);
                objectLock.notifyAll();// 通知所有在此对象上等待的线程
            }
81 invokevirtual #15 <java/lang/Object.notifyAll>
84 aload_1
85 monitorexit
 public static void main(String[] args) {
        Thread t2 = new Thread();
        try {
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

TIMED_WAITING

待续

最后的状态图

企业微信截图_15477095399150.png
上一篇下一篇

猜你喜欢

热点阅读