线程与进程的区别
线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。另外,也正是因为共享资源,所以线程中执行时一般都要进行同步和互斥。总的来说,进程和线程的主要差别在于它们是不同的操作系统资源管理方式。
进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位(根本区别)
线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程,线程也被称为轻量级进程(包含关系)
同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的。(内存分配)
一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。(影响关系)
每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行。(执行过程)
线程状态,start,run,wait,notify,yiled,sleep,join等方法的作用以及区别
线程有6种状态:
-
start() 实例方法
启动一个线程用的是thread.start()方法,如果直接调用run方法是同步调用,相当于一个普通的方法调用。
start()方法使线程开始执行,JVM会自动调用线程的run方法。new出来线程,调用start()方法即处于RUNNABLE(可运行)状态了。处于RUNNABLE状态的线程可能正在Java虚拟机中运行,也可能正在等待处理器的资源,因为一个线程必须获得CPU的资源后,才可以运行其run()方法中的内容,否则排队等待。
-
run()
线程在被调度时执行的操作,子线程要执行的代码放入 run() 方法
-
wait()
一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。而当前线程排队等候其他线程调用 notify() 或 notifyAll() 方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行.
-
notify()
一旦执行此方法,就会唤醒被 wait 的一个线程。如果有多个线程被 wait,就唤醒优先级高的那个。
-
notifyAll()
一旦执行此方法,就会唤醒所有被 wait 的线程。
-
yiled()
此方法的作用是放弃当前的CPU资源,将它让给其他的任务去占用CPU的时间。但是放弃的时间不确定,有可能刚刚放弃就马上获得CPU时间片。yield()也不会释放锁标志。
sleep 方法使当前运行中的线程睡眠一段时间,进入超时等待状态,这段时间的长短是由程序设定的,yield方法使当前线程让出CPU占有权,但让出的时间是不可设定的。
yield()也不会释放锁标志。
yield()只是使当前线程重新回到可运行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
yield()只能使同优先级或更高优先级的线程有执行的机会。
-
sleep()
方法sleep()的作用是在指定的毫秒数内让当前"正在执行的线程"休眠(暂停执行)。 这个"正在执行的线程"是指Thread.currentThread()返回的线程。但是sleep不会释放锁。
sleep(long)使当前线程进入超时等待(TIMED_WAITING)状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;
Thread.sleep(0)的作用是什么?
让当前线程睡眠0ms,CPU不一定会再继续执行其后面的代码,当前线程立即进入就绪状态,等待CPU重新调度,此线程有可能被调度成功,也有可能不被调度成功。
由于Java采用抢占式的线程调度算法,因此可能会出现某条线程常常获取到CPU控制权的情况,为了让某些优先级比较低的线程也能获取到CPU控制权,可以使用Thread.sleep(0)手动触发一次操作系统分配时间片的操作,这也是平衡CPU控制权的一种操作。
适用场景:大循环中,调用Thread.Sleep(0),可让其他线程下执行,不会出现整个应用响应时间长而出现假死现象。
-
join()
join方法的主要作用就是同步,它可以使得线程之间的并行执行变为串行执行。在A线程中调用了B线程的join()方法时,表示只有当B线程执行完毕时,A线程才能继续执行。
join方法中如果传入参数,则表示这样的意思:如果A线程中掉用B线程的join(10),则表示A线程会等待B线程执行10毫秒,10毫秒过后,A、B线程并行执行。需要注意的是,jdk规定,join(0)的意思不是A线程等待B线程0秒,而是A线程等待B线程无限时间,直到B线程执行完毕,即join(0)等价于join()。
join方法必须在线程start方法调用之后调用才有意义。这个也很容易理解:如果一个线程都没有start,那它也就无法同步了。
https://www.cnblogs.com/qlqwjy/p/10070622.html
其中wait(),notify(),notifyAll() 三个方法是定义在 java.lang.Object 类中的。
wait,notify阻塞唤醒确切过程?在哪阻塞,在哪唤醒?为什么要出现在同步代码块中,为什么要处于while循环中?
线程中断,守护线程
如何终止一个线程:使用Thread.interrupt()方法进行线程的终端操作,Thread.interrupt()方法是一个实例方法,它通知目标线程中断,也是设置中断标志位。中断标志位表示当前线程已经被中断了。
针对于线程6种状态的中断:
-
NEW和TERMINATED
线程的new状态表示还未调用start方法,还未真正启动。线程的terminated状态表示线程已经运行终止。这两个状态下调用中断方法来中断线程的时候,Java认为毫无意义,所以并不会设置线程的中断标识位,什么事也不会发生。
-
RUNNABLE
如果线程处于运行状态,那么该线程的状态就是RUNNABLE,但是不一定所有处于RUNNABLE状态的线程都能获得CPU运行,在某个时间段,只能由一个线程占用CPU,那么其余的线程虽然状态是RUNNABLE,但是都没有处于运行状态。而我们处于RUNNABLE状态的线程在遭遇中断操作的时候只会设置该线程的中断标志位,并不会让线程实际中断,想要发现本线程已经被要求中断了则需要用程序去判断。
-
BLOCKED
当线程处于BLOCKED状态说明该线程由于竞争某个对象的锁失败而被挂在了该对象的阻塞队列上了。那么此时发起中断操作不会对该线程产生任何影响,依然只是设置中断标志位。
-
WAITING/TIMED_WAITING
这两种状态本质上是同一种状态,只不过TIMED_WAITING在等待一段时间后会自动释放自己,而WAITING则是无限期等待,需要其他线程调用notify方法释放自己。但是他们都是线程在运行的过程中由于缺少某些条件而被挂起在某个对象的等待队列上。当这些线程遇到中断操作的时候,会抛出一个InterruptedException异常,并清空中断标志位。
用户线程:Java虚拟机在它所有非守护线程已经离开后自动离开。
守护线程:守护线程则是用来服务用户线程的,如果没有其他用户线程在运行,那么就没有可服务对象,也就没有理由继续下去。
setDaemon(boolean on)方法可以方便的设置线程的Daemon模式,true为Daemon模式,false为User模式。setDaemon(boolean on)方法必须在线程启动之前调用,当线程正在运行时调用会产生异常。isDaemon方法将测试该线程是否为守护线程。值得一提的是,当你在一个守护线程中产生了其他线程,那么这些新产生的线程不用设置Daemon属性,都将是守护线程,用户线程同样。
线程同步和线程异步
什么是线程同步和线程异步
线程同步:是多个线程同时访问同一资源,等待资源访问结束,浪费时间,效率不高
线程异步:访问资源时,如果有空闲时间,则可在空闲等待同时访问其他资源,实现多线程机制
异步处理就是,你现在问我问题,我可以不回答你,等到我有时间了再处理你这个问题,同步就是要立即处理这个问题,直到信息处理完成后才返回消息;异步信息收到后将在后台处理一段时间,而早在信息处理结束前的过程中就返回消息了。
怎样区分同步和异步
一个进程启动的多个不想干进程,他们之间的相互关系为异步;同步必须执行到底后才能执行其他操作,异步可同时执行
同步的好处与弊端
好处:解决了线程的安全问题
弊端:每次都要判断锁,降低了效率
但是在安全与效率之间,首先考虑的是安全
同步的前提
1、多个线程执行的时候需要同步,如果是单线程则不需要同步。
2、多个线程在执行的过程中是不是使用同一把锁。如果是,就是同步;否则就是不同步
Java乐观锁机制,CAS思想?缺点?是否原子性?如何保证?
CAS思想:
全称为:Compare and swap(比较与交换),用来解决多线程并发情况下使用锁造成性能开销的一种机制;
原理思想:CAS(V,A,B),V为内存地址,A为预期原值,B为新值。如果内存地址V的值等于A,那么将V修改成B;否则,说明V被其他线程处理过。
那么,是否有可能在即将修改V的值为B的时候被其他线程修改了V的值了?答案是不可以的,因为CAS操作实际上由CPU提供支持,是原语(原语是操作系统或计算机网络用语范畴。是由若干条指令组成的,用于完成一定功能的一个过程,具有不可分割性,即原语的执行必须是连续的,在执行过程中不允许被中断)。
CAS的缺点:
CAS虽然很高效的解决了原子操作问题,但是CAS仍然存在三大问题。
循环时间长开销很大 : 如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能会给CPU带来很大的开销。
只能保证一个共享变量的原子操作。
ABA问题。如果内存地址V初次读取的值是A,并且在准备赋值的时候检查到它的值仍然为A,那我们就能说它的值没有被其他线程改变过了吗?如果在这段期间它的值曾经被改成了B,后来又被改回为A,那CAS操作就会误认为它从来没有被改变过。这个漏洞称为CAS操作的“ABA”问题。Java并发包为了解决这个问题,提供了一个带有标记的原子引用类“AtomicStampedReference”,它可以通过控制变量值的版本来保证CAS的正确性。因此,在使用CAS前要考虑清楚“ABA”问题是否会影响程序并发的正确性,如果需要解决ABA问题,改用传统的互斥同步可能会比原子类更高效。
synchronized使用方法?底层实现?
-
修饰方法
Synchronized修饰一个方法很简单,就是在方法的前面加synchronized,synchronized修饰方法和修饰一个代码块类似,只是作用范围不一样,修饰代码块是大括号括起来的范围,而修饰方法范围是整个函数。
例如:
方法一
public synchronized void method() { // todo }
方法二
public void method() { synchronized(this) { // todo } }
写法一修饰的是一个方法,写法二修饰的是一个代码块,但写法一与写法二是等价的,都是锁定了整个方法时的内容。
synchronized关键字不能继承。
-
修饰一个代码块
1)一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞
2)当两个并发线程(thread1和thread2)访问同一个对象(syncThread)中的synchronized代码块时,在同一时刻只能有一个线程得到执行,另一个线程受阻塞,必须等待当前线程执行完这个代码块以后才能执行该代码块。
3)指定要给某个对象加锁
-
修饰一个静态的方法
Synchronized也可修饰一个静态方法,用法如下:
public synchronized static void method() { // todo }
静态方法是属于类的而不属于对象的。同样的,synchronized修饰的静态方法锁定的是这个类的所有对象。
syncThread1和syncThread2是SyncThread的两个对象,但在thread1和thread2并发执行时却保持了线程同步。这是因为run中调用了静态方法method,而静态方法是属于类的,所以syncThread1和syncThread2相当于用了同一把锁。
-
修饰一个类
Synchronized还可作用于一个类,用法如下:
class ClassName { public void method() { synchronized(ClassName.class) { // todo } } }
给class加锁和上例的给静态方法加锁是一样的,所有对象公用一把锁
ReenTrantLock使用方法?底层实现?和synchronized区别?
ReentrantLock 类提供了最基本的加锁和解锁方法:
public void lock();
public void unlock();
公平模式与非公平模式:
除了默认的非公平锁构造方法外,ReentrantLock 还提供了一个带有 boolean 参数的构造方法:
public ReentrantLock(boolean fair);
如果传入参数为 true,则会创建公平锁,所谓的公平锁,就是保证了先进入等待的线程一定先获取到锁
可以通过 isFair 方法查询 ReentrantLock 对象是否是公平锁:
public final boolean isFair();
非阻塞式锁、时间限制锁与可中断锁:
ReentrantLock 提供了 tryLock 方法与 lockInterruptibly 方法用来实现非阻塞式锁、时间限制锁与可中断锁
public boolean tryLock() // 尝试获取锁,立即返回获取结果
public boolean tryLock(long timeout, TimeUnit unit) // 尝试获取锁,最多等待 timeout 时长
public void lockInterruptibly() // 可中断锁,调用线程 interrupt 方法,则锁方法抛出InterruptedException
以上三种锁方式,synchronized 都是无法实现的,正如我们上一篇日志中所提到,interrupt 方法是不会中止正在等待获取 synchronized 锁的线程的
Condition 与线程等待:
Object 类提供了只能在 synchronized 代码块中使用的 wait、notify、notifyAll,ReentrantLock 也拥有类似但更为强大的等待和唤醒机制,这就是通过 Condition 对象唤醒的
Condition
通过 newCondition 方法,可以创建出 Condition 对象
public Condition newCondition();
Condition 接口提供了如下的方法:
void await(); // 可被中断的等待
boolean await(long time, TimeUnit unit); // 最多等待 time 时长的可中断等待
long awaitNanos(long nanosTimeout); // 最多等待 nanosTimeout 毫秒的可中断等待
boolean awaitUntil(Date deadline); // 等待直到指定时间的可中断等待
void awaitUninterruptibly(); // 不可中断的等待
void signal(); // 唤醒一个线程
void signalAll(); // 唤醒所有等待中的线程
上面的五个等待方法中,除了 awaitUninterruptibly 方法,其他四个都可以被 interrupt 方法中断,而 signal 和 signalAll 方法可以中断上述所有等待方法
但是,signal 和 signalAll 方法只能唤醒通过当前 Condition 对象调用过等待方法的线程
基于上述特性,我们可以精准的控制让某个指定的线程被唤醒,而 Object 的 notify、notifyAll 方法的唤醒则是随机的,同一个 ReentrantLock 每次调用 newCondition 方法都将获得不同的 Condition 对象
强大而丰富的查询接口:
除了上述强大的加锁与等待、唤醒接口外,ReentrantLock 还提供了丰富而强大的查询接口,让你了解到锁相关的各种情况:
int getHoldCount(); // 获取当前线程持有该锁的次数
boolean isHeldByCurrentThread(); // 判断当前线程是否持有该锁
boolean isLocked(); // 获取锁状态是否为加锁状态
boolean isFair(); // 当前锁是否是公平锁
Thread getOwner(); // 获取持有锁的线程
boolean hasQueuedThreads(); // 判断当前锁是否有线程在等待
boolean hasQueuedThread(Thread thread); // 判断指定线程是否在等待该锁
int getQueueLength(); // 获取正在等待该锁的线程数量
boolean hasWaiters(Condition condition); // 判断是否有线程等待在该 Condition 对象上
int getWaitQueueLength(Condition condition); // 获取等待在该 Condition 对象的线程数
公平锁和非公平锁区别?为什么公平锁效率低?
公平锁和非公平锁分别使用了FairSync与NonfairSync实现
公平锁:
优点:所有的线程都能得到资源,不会饿死在队列中。
缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大。
非公平锁:
优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必取唤醒所有线程,会减少唤起线程的数量。
缺点:你们可能也发现了,这样可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死。
线程A1和线程A2分别使用公平锁和非公平锁,它们将在同一时间释放锁,而A1因为使用公平锁需要在释放锁之后唤醒在它后面排队等候的线程B1,而A2线程可能在它释放锁之前,B2、C2或者D2就来申请锁了,可能在B1还没有运行起来,B2、C2或者D2已经运行完开始释放锁了(线程B1恢复运行状态需要时间),所以非公平锁比公平锁效率高
锁优化。自旋锁、自适应自旋锁、锁消除、锁粗化、偏向锁、轻量级锁、重量级锁解释
Java内存模型
volatile作用?底层实现?禁止重排序的场景?单例模式中volatile的作用?
volatile
关键字 除了防止 JVM 的指令重排 ,还有一个重要的作用就是保证变量的可见性。
AQS思想,以及基于AQS实现的lock, CountDownLatch、CyclicBarrier、Semaphore介绍
AQS的核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态,如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制
线程池构造函数7大参数,线程处理任务过程,线程拒绝策略
7大参数:
corePoolSize 核心线程大小 线程池中最小的线程数量,即使处理空闲状态,也不会被销毁,除非设置了allowCoreThreadTimeOut。
CPU密集型:核心线程数 = CPU核数 + 1 IO密集型:核心线程数 = CPU核数 * 2+1 注:IO密集型(某大厂实践经验) 核心线程数 = CPU核数 / (1-阻塞系数) 例如阻塞系数 0.8,CPU核数为4,则核心线程数为20
- maximumPoolSize 线程池最大线程数量 一个任务被提交后,首先会被缓存到工作队列中,等工作队列满了,则会创建一个新线程,处理从工作队列中的取出一个任务。
- keepAliveTime 空闲线程存活时间 当线程数量大于corePoolSize时,一个处于空闲状态的线程,在指定的时间后会被销毁。
- unit 空间线程存活时间单位 keepAliveTime的计量单位
- workQueue 工作队列,jdk中提供了四种工作队列。 新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。 ①ArrayBlockingQueue 基于数组的有界阻塞队列,按FIFO排序。 ②LinkedBlockingQuene 基于链表的无界阻塞队列(其实最大容量为Interger.MAX),按照FIFO排序。 ④PriorityBlockingQueue 具有优先级的无界阻塞队列,优先级通过参数Comparator实现。
- threadFactory 线程工厂 创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等等
- handler 拒绝策略 当工作队列中的任务已满并且线程池中的线程数量也达到最大,这时如果有新任务提交进来,拒绝策略就是解决这个问题的,jdk中提供了4种拒绝策略:
①CallerRunsPolicy 该策略下,在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务。
②AbortPolicy 该策略下,直接丢弃任务,并抛出RejectedExecutionException异常。
③DiscardPolicy 该策略下,直接丢弃任务,什么都不做。 ④DiscardOldestPolicy 该策略下,抛弃最早进入队列的那个任务,然后尝试把这次拒绝的任务放入队列。
参考:https://www.cnblogs.com/shijianchuzhenzhi/p/12964678.html
Execuors类实现的几种线程池类型,阿里为啥不让用?
Java通过Executors提供四种线程池,分别为
- CachedThreadPool线程池 : Executors.newCachedThreadPoo()
CachedThreadPool线程池:弹性数量
创建方式:Executors.newCachedThreadPool()
底层:返回ThreadPoolExecutor实例
核心线程数:0
最大线程数:Integer.MAX_VALUE(取决系统和JVM的配置)
空闲存活时间:60秒
工作队列类型:SynchronousQueuenew ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
描述:
当有新任务到来,则插入到SynchronousQueue(工作队列)中,由于SynchronousQueue是同步队列,因此会在池中寻找可用线程(空闲线程)来执行; 若有可用线程,则执行该任务, 若没有可用线程,则创建一个线程来执行该任务; 若池中线程空闲时间超过指定大小,则该线程会被销毁。适用:执行若干数量的短期异步的任务
- FixedThreadPool线程池 : Executors.newFixedThreadPool()
FixedThreadPool线程池:固定数量
创建方式:Executors.newFixedThreadPool(nThreads)
底层:返回ThreadPoolExecutor实例
核心线程数:nThreads
最大线程数:nThreads
空闲存活时间:0秒(存活时间无限)
工作队列类型:LinkedBlockingQueue(无界工作队列)new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
描述:
创建可容纳固定数量线程的线程池,线程池中的每个线程的存活时间是无限的,当线程池满了不再添加新线程; 如果池中的所有线程均在繁忙状态,对于新任务会进入阻塞队列中(LinkedBlockingQueue(无界工作阻塞队列))适用:执行长期的任务,性能较好
- SingleThread线程池 : Executors.newSingleThreadExecutor()
SingleThread线程池:单例线程池(只维护了一个线程)
创建方式:Executors.newSingleThreadExecutor() 底层:返回FinalizableDelegatedExecutorService实例 核心线程数:1 最大线程数:1 空闲存活时间:0秒(存活时间无限) 工作队列类型:LinkedBlockingQueue(无界工作队列) 底层:返回FinalizableDelegatedExecutorService实例
new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable()));
描述:
创建只有一个线程的线程池,且线程的存活时间是无限的;当该线程正繁忙时,对于新任务会进入阻塞队列中(无界的阻塞队列)适用:定时及周期性任务,任务逐个执行的场景
- ScheduledThread线程池 : Executors.newScheduledThreadPool()
ScheduledThreadPool线程池:可调度任务的线程池 创建方式:Executors.newScheduledThreadPool(nThreads) 核心线程数:nThreads 最大线程数:Integer.MAX_VALUE(最大线程数不设上限) 工作队列:DelayedWorkQueue 底层:返回ScheduledThreadPoolExecutor实例
new ScheduledThreadPoolExecutor(corePoolSize)
描述:
支持定时及周期性任务执行; 如果所有线程均处于繁忙状态,对于新任务会进入DelayedWorkQueue队列中,这是一种按照"存活时间"排序的队列结构适用:周期性执行任务的场景
阿里不让用Execuors类的线程池?
Executors各个方法的弊端:
newFixedThreadPool和newSingleThreadExecutor: 主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
newCachedThreadPool和newScheduledThreadPool: 主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。
导致原因:
第一种,newFixedThreadPool和newSingleThreadExecutor分别获得 FixedThreadPool 类型的线程池 和 SingleThreadExecutor 类型的线程池。
因为,创建了一个无界队列LinkedBlockingQueuesize,是一个最大值为Integer.MAX_VALUE的线程阻塞队列,当添加任务的速度大于线程池处理任务的速度,可能会在队列堆积大量的请求,消耗很大的内存,甚至导致OOM。
第二种,newCachedThreadPool 和 newScheduledThreadPool创建的分别是CachedThreadPool 类型和 ScheduledThreadPool类型的线程池。
CachedThreadPool是一个会根据需要创建新线程的线程池 ,ScheduledThreadPoolExecutor可以用来在给定延时后执行异步任务或者周期性执行任务。
创建的线程池允许的最大线程数是Integer.MAX_VALUE,空闲线程存活时间为0,当添加任务的速度大于线程池处理任务的速度,可能会创建大量的线程,消耗资源,甚至导致OOM。
参考:https://www.cnblogs.com/ibigboy/p/11298004.html
Synchronized的底层实现原理
对于synchronized关键字而言,javac在编译时,会生成对应的monitorenter和monitorexit指令分别对应synchronized同步块的进入和退出
对代码块的同步
monitorenter
每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:
如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.
如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。
monitorexit
执行monitorexit的线程必须是objectref所对应的monitor的所有者。
指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。
通过对这两个指令的描述,我们应该能很清楚的看出Synchronized的实现原理,Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。
对同步方法的同步
方法的同步并没有通过指令monitorenter和monitorexit来完成(理论上其实也可以通过这两条指令来实现),不过相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。