并发编程(五)Thread分析Sychronized初探
Thread类是怎么创建线程的
java中运行一个线程有好多方式,比如实现一个Runnable接口然后传递给Thread构造函数,还是交给线程池来创建线程执行等等。但是最后都是创建new Thread对象,最后通过thread.start方法来启动一个线程。
但是Java中Thread类只是一个普通的Java类,它是怎么启动一个线程的呢?java中的线程是内核级的线程,java中的thread对象只是内核级线程的一个映射。在创建一个Thread对象的时候,会调用静态方法进行方法映射:
JVM线程调度:依赖JVM内部实现,主要是Native thread scheduling,是依赖操作系统的,所以java也不能完全是跨平台独立的,对线程调度处理非常敏感的业务开发必须关注底层操作系统的线程调度差异。
Green Thread Schedule 或者叫用户级线程(User Level Thread,ULT):操作系统内核不知道应用线程的存在。
Native thread scheduling 或者 内核级线程(Kernel Level Thread ,KLT):它们是依赖于内核的,即无论是用户进程中的线程,还是系统进程中的线程,它们的创建、撤消、切换都由内核实现
线程的状态
之前知道的线程状态并没有把阻塞和等待完全分区开,从源码的角度看更能清晰的认识线程的状态。
RUNNABLE: 在Java中运行的线程但是还没有得到系统资源,就是还没有得到CPU的时间片,因为Java中的线程是内核级的线程。
BLOCKED: 是线程未获得监视器锁时的状态,在sychronized竞争锁并没有得到锁的时候。
WAITING: 是线程在调用wait,join,park时进入等待的状态。
状态的流转:
Java锁体系
Sychronized和Lock
Synchronized加锁方式
- 同步实例方法,锁是当前实例对象
- 同步类方法,锁是当前类对象
- 同步代码块,锁是括号里面的对象
Sleep和wait方法都会释放时间片,但是只有wait方法会释放锁,Sleep不会释放锁。
我们通过汇编看下Java中的Sychronized做了什么事。以下面的代码为例:
int i=0;
String lock="";
public static void main(String[] args) {
System.out.println(LocalDate.now().getYear());
}
public synchronized void sychronMetho(){
i++;
}
public void sychronTest(){
synchronized (lock){
i++;
}
}
在idea中可以通过设置External Tools 可以直接得到class的汇编语言。设置的方式:https://www.cnblogs.com/krock/p/14655982.html
得到的汇编结果:
public synchronized void sychronMetho(); descriptor: ()V flags: ACC_PUBLIC, ACC_SYNCHRONIZED // 方法调用标识 Code: stack=3, locals=1, args_size=1 0: aload_0 1: dup 2: getfield #2 // Field i:I 5: iconst_1 6: iadd 7: putfield #2 // Field i:I 10: return LineNumberTable: line 33: 0 line 34: 10 LocalVariableTable: Start Length Slot Name Signature 0 11 0 this Lcom/indigo/main/TestMain; public void sychronTest(); descriptor: ()V flags: ACC_PUBLIC //方法调用标识 Code: stack=3, locals=3, args_size=1 0: aload_0 1: getfield #4 // Field lock:Ljava/lang/String; 4: dup 5: astore_1 6: monitorenter //进去临界区 7: aload_0 8: dup 9: getfield #2 // Field i:I 12: iconst_1 13: iadd 14: putfield #2 // Field i:I 17: aload_1 18: monitorexit //离开临界区 19: goto 27 22: astore_2 23: aload_1 24: monitorexit //考虑出现异常的时候离开临界区 25: aload_2 26: athrow 27: return
从汇编语言可以知道,sychronized在jvm底层,1)加在方法上的话,整个方法作为临界区,会在方法的访问标识中增加:ACC_SYNCHRONIZED。2)如果是加在代码块上,会在进入临界区之前加上 monitorenter 指令,使用monitorexit退出临界区。
也可以参考官方文档对于Sychronized做出的解释:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.11.8
这就是自动加锁和解锁的原理。底层依赖于操作系统中mutex信号量,依赖内核库中pthread_mutex_lock/pthread_mutex_unlock函数库。用它的话java要从用户态切换到内核态,所以说它是重量级锁。
再往下探究,就要说到sychronized基于monitor机制的原理,这部分牵涉到很多知识,下面单独写一篇介绍。