并发JUC从源码到搬砖

文章目录

并发JUC

1 并发编程

1.1 并发编程的优缺点

优点:
  • 利用多核CPU提高计算能力,提高程序运行速度
  • 可以将一个事情拆成多部分,交给不同的线程来分别运行,提高业务完成效率,提高程序性能。
缺点:
  • 上下文切换导致的原子性破坏
  • 内存泄漏
  • 线程安全
  • 线程死锁

1.2 并发编程三要素

并发编程三要素
  • 原子性:即一个微粒不可再分,一个或者多个操作要么全部执行成功要么全部执行失败
  • 有序性:程序按照代码块顺序执行
  • 可见性:其他线程可以对共享变量的修改,进行及时观测。
出现安全问题的原因:
  • 线程切换带来的原子性问题
  • 缓存导致的可见性问题
  • 编译优化带来的有序性问题
上下文切换

线程需要将CPU的使用权力交给其他线程时,会对自身线程任务保存,以便下次再切换回这个任务时可以再次加载这个任务状态

1.3 什么是多线程

多线程指的就是在一个程序包含多个执行流,可以用多个不同线程完成多个不同事情

2 进程与线程的介绍

2.1 进程与线程

进程
  • 进程是一个程序的代码从磁盘加载至内存运行的过程
  • 程序是由指令和数据组合而成的,指令想要运行,数据需要读写,就需要把指令加载至CPU,数据加载至内存。进程则是用来加载指令,管理内存,管理IO
  • 进程可以视为程序的实例,一个程序可以对应多个进程
线程
  • 一个进程可以分为一到多个线程
  • 一个线程就是一个指令流,指令流中的一条条指令交给CPU来执行
  • 在Java中,线程作为最小的调度单位,进程作为最小的资源分配单位,windows中进程不活动,只作为线程的容器
进程与线程的区别

线程具有许多传统进程所具有的特征,故又称为轻型进程(Light—Weight Process)或进程元;而把传统的进程称为重型进程(Heavy—Weight Process),它相当于只有一个线程的任务。在引入了线程的操作系统中,通常一个进程都有若干个线程,至少包含一个线程。

  • **根本区别:**进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位

  • **资源开销:**每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。

  • **包含关系:**如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。

  • **内存分配:**同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的

  • **影响关系:**一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。

  • **执行过程:**每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行

二者对比
  • 进程基本上相互独立,线程是进程的子集

  • 进程拥有共享的资源,内存空间,供进程子集使用

  • 进程之间的通信较为复杂

    • 本机进程之间的通信称为IPC

    • 不同进程之间的通信则需要使用网络,并且遵守通讯协议,例如HTTP

  • 进程之间通信较为简单,因为共享进程中的内存。

  • 线程更加轻量,线程上下文切换成本一般要比进程上下文切换低(上下文切换需要消耗时间)

2.2 并行和并发

并行

image-20220312194921322

两个线程或多个线程同时开始,同时执行

并发

image-20220312194931459

两个线程或多个线程同时开始,按照调度算法轮流执行

2.3 同步与异步

同步

需要等待结果返回,才能继续运行就是同步

**注:**在多线程中,同步还有一个意思是让线程步调一致

异步

不需要等待结果返回,就能继续运行就是异步

2.4 思考

多线程一定能提升程序运行效率吗?

1,单核CPU下,单核CPU下多线程并不能提升程序运行效率,只是能够在不同线程中进行切换,反而在某些情况下,多线程的运行效率是低于单线程的(因为上下文切换是需要耗费时间的)

2,多核CPU下,多核CPU可以并行运行多个线程,但是能否提高程序运行效率还是要分情况的

  • 有些任务,经过进行设计,任务拆分,可以提高运行效率,但是并不是所有的计算任务都能进行拆分

3,IO操作不占用CPU,这时的线程无需使用CPU,但却要一直等待IO结束,不能充分利用线程,但是可以用NIO,以及AIO进行优化

3 Java线程

img

3.1 创建和运行线程

Thread创建
public static Thread createThreadByThread(String name){
        Thread t = new Thread(){
            @Override
            public void run(){
                log.debug("running");
            }
        };
        if(name!=null){
            t.setName(name);
        }
        return t;
    }
Runnable创建
public static Thread createThreadByRunnable(String name){
        Runnable runnable = new Runnable() {
            public void run() {
                log.debug("running");
            }
        };
        Thread t = new Thread(runnable,name);
        return t;
    }
Lambda简化
public static Thread createThreadByLambda(String name){
        Runnable runnable = () ->{log.debug("running");};
        Thread t = new Thread(runnable,name);
        return t;
    }
FutureTask创建
public static FutureTask<Integer> createThreadFutureTask(){
        FutureTask<Integer> task = new FutureTask<Integer>(() -> {
            log.debug("hello");
            return 100;
        });
        return task;
    }

FutureTask和Callable接口结合,即可返回一个任意类型的值

FutureTask<Integer> task = createThreadFutureTask();
        Thread t3 = new Thread(task,"Genius4");
        t3.start();
         log.debug("result:{}",task.get());

因为FutureTask使用了runnable接口,所以可以将task作为Thread的初始化参数,并且可以使用task.get获取返回的值

Runnable和Callable的区别
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}
  • Runnable中的run无返回值;Callable接口中的call会返回返回值,可以配合FutureTask完成异步操作。

  • run方法只能抛出运行时异常无法捕获处理异常,而Callable可以获取异常信息。

Callable中的返回结果通过FutureTask.get()获得,此时主线程会进入阻塞状态。

3.2 Thread,Runnable源码原理

new Thread()
  • Thread() -> init() -> this.target = target(runnable)

1,若Thread中name 为空则会调用nextThreadNum()根据当前的线程数量来命名,其中threadInitNumber为全局变量且对nextThreadNum()方法加锁,防止线程错误。

Thread(Runnable target, AccessControlContext acc) {
        init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
    }

private static int threadInitNumber;
    private static synchronized int nextThreadNum() {
        return threadInitNumber++;
    }

2,通过多次init()方法的嵌套,将target赋值给当前的Thread target

 private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;

        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            /* Determine if it's an applet or not */

            /* If there is a security manager, ask the security manager
               what to do. */
            if (security != null) {
                g = security.getThreadGroup();
            }

            /* If the security doesn't have a strong opinion of the matter
               use the parent thread group. */
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }

        /* checkAccess regardless of whether or not threadgroup is
           explicitly passed in. */
        g.checkAccess();

        /*
         * Do we have the required permissions?
         */
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }

        g.addUnstarted();

        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }

3,通过run()方法调用target中的run方法

public void run() {
        if (target != null) {
            target.run();
        }
    }

3.3 Java进程查看

Windows进程查看命令
  • tasklist 查看所有的
  • taskkill /F /PID {pid} 杀死进程
  • jps 查看java进程
使用Jconsole来进行进程查看进程信息

image-20220313003942146
image-20220313003959070

3.4 线程运行原理

栈帧不多说了,CSDN有笔记

不同的线程有不同的线程栈,互不干扰。

线程上下文切换

因为某些原因,CPU不再执行当前的线程,而是去执行另一个线程:

  • 垃圾回收
  • 线程时间片用完
  • 有优先级更高级的线程需要执行
  • 线程调用了sleep,yield,wait,join,park,synchronized,lock等方法

当Context Switch发生时,需要操作系统保存线程当前状态,再java中对应的概念则是程序计数器

频繁的上下文切换影响性能

image-20220314112822680

如上图所示,当main线程时间片用完,则将CPU的操作权限让给t1,同时保存main线程中所有状态信息。

3.5 线程方法详解:start & run

run方法
@Slf4j(topic = "c.start_run")
public class start_run {
    public static void main(String[] args) {
        Thread t1 = new Thread("t1"){
            @Override
            public void run(){
                log.debug("running");
            }
        };
        Thread t2 = new Thread("t2"){
            @Override
            public void run(){
                log.debug("running2");
            }
        };
        t1.run();
        t2.run();
        log.debug("main running");
    }
}

image-20220314113735349

start方法
@Slf4j(topic = "c.start_run")
public class start_run {
    public static void main(String[] args) {
        Thread t1 = new Thread("t1"){
            @Override
            public void run(){
                log.debug("running");
            }
        };
        Thread t2 = new Thread("t2"){
            @Override
            public void run(){
                log.debug("running2");
            }
        };
        t1.start();
        t2.start();
        log.debug("main running");
    }
}

image-20220314113803454

可以很明显的看到,当调用run方法时,线程处理实际上是main线程,所以并没有性能提升。

myThread.start() -> 继承自Thread类的start()方法 -> start0() -> private native void start0() -> cpp底层去找当前线程类的run方法 -> myThread的run方法
image-20220314191902410

image-20220314191853071

由上例的输出可知,调用run方法线程并没有被调用,而只是调用java,main函数中的方法

start0底层源码

[Thread.c] start0() -> [Jvm.cpp] JVM_StartThread ->new JavaThread -> thread_entry ->vmSymbols run_method_name() -> “run”

找到openJdk中的Thread.c

/*
 * Copyright (c) 1994, 2012, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/*-
 *      Stuff for dealing with threads.
 *      originally in threadruntime.c, Sun Sep 22 12:09:39 1991
 */

#include "jni.h"
#include "jvm.h"

#include "java_lang_Thread.h"

#define THD "Ljava/lang/Thread;"
#define OBJ "Ljava/lang/Object;"
#define STE "Ljava/lang/StackTraceElement;"
#define STR "Ljava/lang/String;"

#define ARRAY_LENGTH(a) (sizeof(a)/sizeof(a[0]))

static JNINativeMethod methods[] = {
    {"start0",           "()V",        (void *)&JVM_StartThread},
    {"stop0",            "(" OBJ ")V", (void *)&JVM_StopThread},
    {"isAlive",          "()Z",        (void *)&JVM_IsThreadAlive},
    {"suspend0",         "()V",        (void *)&JVM_SuspendThread},
    {"resume0",          "()V",        (void *)&JVM_ResumeThread},
    {"setPriority0",     "(I)V",       (void *)&JVM_SetThreadPriority},
    {"yield",            "()V",        (void *)&JVM_Yield},
    {"sleep",            "(J)V",       (void *)&JVM_Sleep},
    {"currentThread",    "()" THD,     (void *)&JVM_CurrentThread},
    {"countStackFrames", "()I",        (void *)&JVM_CountStackFrames},
    {"interrupt0",       "()V",        (void *)&JVM_Interrupt},
    {"isInterrupted",    "(Z)Z",       (void *)&JVM_IsInterrupted},
    {"holdsLock",        "(" OBJ ")Z", (void *)&JVM_HoldsLock},
    {"getThreads",        "()[" THD,   (void *)&JVM_GetAllThreads},
    {"dumpThreads",      "([" THD ")[[" STE, (void *)&JVM_DumpThreads},
    {"setNativeName",    "(" STR ")V", (void *)&JVM_SetNativeThreadName},
};

#undef THD
#undef OBJ
#undef STE
#undef STR

JNIEXPORT void JNICALL
Java_java_lang_Thread_registerNatives(JNIEnv *env, jclass cls)
{
    (*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods));
}

对此我们可知start0的源码在Jvm.cpp文件中,JVM_StartThread源码如下

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
  JVMWrapper("JVM_StartThread");
  JavaThread *native_thread = NULL; //创建一个引用

  // We cannot hold the Threads_lock when we throw an exception,
  // due to rank ordering issues. Example:  we might need to grab the
  // Heap_lock while we construct the exception.
  bool throw_illegal_thread_state = false;

  // We must release the Threads_lock before we can post a jvmti event
  // in Thread::start.
  {
    // Ensure that the C++ Thread and OSThread structures aren't freed before
    // we operate.
    MutexLocker mu(Threads_lock); //加了一把线程锁

    // Since JDK 5 the java.lang.Thread threadStatus is used to prevent
    // re-starting an already started thread, so we should usually find
    // that the JavaThread is null. However for a JNI attached thread
    // there is a small window between the Thread object being created
    // (with its JavaThread set) and the update to its threadStatus, so we
    // have to check for this
    if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) {
      throw_illegal_thread_state = true; //抛异常
    } else {   //下面的是重点
      // We could also check the stillborn flag to see if this thread was already stopped, but
      // for historical reasons we let the thread detect that itself when it starts running
 	  // 计算创建线程所需要的大小
      jlong size =
             java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));
      // Allocate the C++ Thread structure and create the native thread.  The
      // stack size retrieved from java is signed, but the constructor takes
      // size_t (an unsigned type), so avoid passing negative values which would
      // result in really large stacks.
      size_t sz = size > 0 ? (size_t) size : 0;
      //生成一个JavaThread的对象
      native_thread = new JavaThread(&thread_entry, sz);

      // At this point it may be possible that no osthread was created for the
      // JavaThread due to lack of memory. Check for this situation and throw
      // an exception if necessary. Eventually we may want to change this so
      // that we only grab the lock if the thread was created successfully -
      // then we can also do this check and throw the exception in the
      // JavaThread constructor.
      if (native_thread->osthread() != NULL) {
        // Note: the current thread is not being used within "prepare".
        native_thread->prepare(jthread);
      }
    }
  }

这一句new JavaThread()至关重要

其中thread_entry是JavaThread的第一个参数,thread_entry源码如下

static void thread_entry(JavaThread* thread, TRAPS) {
  HandleMark hm(THREAD);
  Handle obj(THREAD, thread->threadObj());
  JavaValue result(T_VOID);
  JavaCalls::call_virtual(&result,
                          obj,
                          KlassHandle(THREAD, SystemDictionary::Thread_klass()),
                          vmSymbols::run_method_name(),
                          vmSymbols::void_method_signature(),
                          THREAD);
}

在thread_entry里面有一个run_method_name()方法

我们可以在vmSymbols中看到该方法对应的java名

image-20220314191245041

对应的就是java中的run方法

3.6 线程方法详解: sleep与yield

sleep 源码解析
  • 调用sleep会从running状态进入Timed Waiting状态
  • 睡眠后的线程未必直接运行
public static void main(String[] args)throws InterruptedException {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                log.debug("running");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    log.debug("wake up....");
                    e.printStackTrace();
                }
            }
        };
        t1.start();
        Thread.sleep(1000);
        System.out.println(t1.getState());
        t1.interrupt();
    }
  • TimeUnit替代Thread.sleep
Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                log.debug("running");
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    log.debug("wake up....");
                    e.printStackTrace();
                }
            }
        };

image-20220314231632577

调用Interrupt会打断sleeping 同时抛出异常

image-20220314212243181

yield 源码解析
JVM_ENTRY(void, JVM_Yield(JNIEnv *env, jclass threadClass))
  JVMWrapper("JVM_Yield");
  if (os::dont_yield()) return;
#ifndef USDT2
  HS_DTRACE_PROBE0(hotspot, thread__yield);
#else /* USDT2 */
  HOTSPOT_THREAD_YIELD();
#endif /* USDT2 */
  // When ConvertYieldToSleep is off (default), this matches the classic VM use of yield.
  // Critical for similar threading behaviour
  if (ConvertYieldToSleep) {
    os::sleep(thread, MinSleepInterval, false);
  } else {
    os::yield();
  }
os::YieldResult os::NakedYield() {
  // Use either SwitchToThread() or Sleep(0)
  // Consider passing back the return value from SwitchToThread().
  if (os::Kernel32Dll::SwitchToThreadAvailable()) {
    return SwitchToThread() ? os::YIELD_SWITCHED : os::YIELD_NONEREADY ;
  } else {
    Sleep(0);
  }
  return os::YIELD_UNKNOWN ;
}

void os::yield() {  os::NakedYield(); }

由上图可以看出,Thread.yield()调用过程为 yield()->JVM_Yield()->NakedYield()

同时在这里使用了SwitchToThread 和 Sleep(0) 两个方法

**Sleep(0):**只会将时间片让给优先级相同或者更高的线程

​ 对于C++中Sleep(timeout)方法可以这么理解:
当timeout = 0时:会将该线程让给比自己优先级更高或者相等的线程去运行

当timeout > 0时:会引发上下文切换,调用线程会从线程调度器的可运行队列移除一段时间

**SwitchToThread():**只要有可调度的线程,无论优先级,都会让其调度,并且返回非0值,若无线程可调度则返回False

在Java中Sleep和yield的区别
  • sleep会抛出异常,而yield不会抛出异常。
  • yield将CPU的使用权让给比自己优先级更高或者相同的线程,sleep是该线程从可运行队列移除一段时间进行上下文切换
  • sleep进入Time_wait状态,而yield进入就绪态。
  • sleep()方法比yield()方法(跟操作系统CPU调度相关)具有更好的可移植性。
线程优先级

当CPU较为繁忙,优先级高的线程会获得更多时间片,CPU较为空闲时,优先级几乎没作用,优先级仅仅只是一个提示

3.7 案例-防止CPU占用100%

new Thread(()->{
            while (true){
                try {
                    Thread.sleep(50);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        }).start();

可以用sleep让出cpu使用权,防止空转CPU,同时也可以使用wait或条件变量达到(需要加锁,适用于同步场景),sleep不需要加锁

3.8 Join

image-20220315201613121

image-20220315201620643

由上图可以得知,因为主方法和线程t1是并行的,所以在线程t1.sleep后,r就已经被主线程输出了。

能不能用sleep?为什么?

能,但是如果millis是变量那具体t1线程要什么sleep多少时间是不知道的

Join

**描述:**等待某一线程运行完,才开始运行(同步)

image-20220315205621335

Join原理
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;
            }
        }
    }

join采用的是保护性暂停设计模式的原理如果millis = 0 就会一直等待该线程执行结束后才会停止循环,底层调用的是wait方法,synchronzied为需要等待完成的this线程进行加锁,其他线程调用join方法时,会进入该同步代码块,并且调用wait方法,进入阻塞状态,同时this线程也会变为重量级锁。

JUC.a_JavaThread.join$1 object internals:
OFF  SZ                                        TYPE DESCRIPTION                             VALUE
  0   4                                             (object header: mark)                   0x026ccfe6 (fat lock: 0x026ccfe6)

3.9 interrupt 方法详解

打断sleep,wait,join线程

阻塞打断:

打断sleep的线程,抛出异常,并且在抛出异常后立即将线程中断标示位清楚,重新设置为false,抛出异常是为了让线程从阻塞状态醒过来,并且在线程结束前让程序员有足够的时间来处理中断请求

 public static void main(String[] args)throws InterruptedException {
        Thread t1 = new Thread(()->{
           log.debug("sleep...");
           try{
               Thread.sleep(5000);
           }catch (InterruptedException e){
               e.printStackTrace();
           }
        },"t1");
        t1.start();
        Thread.sleep(1000);
        log.debug("interrupt");
        t1.interrupt();
        log.debug("{}",t1.isInterrupted());
    }

image-20220315211854345

运行打断:

打断运行的线程,不会清空打断状态,返回true

 public static void main(String[] args)throws InterruptedException {
        Thread t2 = new Thread(()->{
            log.debug("running");
            while (true){

            }
        },"t2");
        t2.start();
        t2.interrupt();
        log.debug("t2 {}",t1.isInterrupted());
        Thread.sleep(1000);
        System.out.println(t2.getState());
    }

image-20220315214537725

3.10 两阶段终止模式

如何在T1线程中优雅的终止线程T2?

错误思路
  • 使用stop()方法停止线程

stop方法会杀死线程,这时如果线程锁住了共享资源,那么被杀死后就再也没有机会释放锁,其他线程永远无法获取锁

  • 使用System.exit(init) 方法停止线程

这个方法会让程序停止

正确思路

image-20220315215652051

private Thread monitor;

public TwoPhaseInterrput(){

}
public void start(){
    Runnable runnable = ()->{
        while(true){
            Thread current = Thread.currentThread();
            if(current.isInterrupted()){
                log.debug("料理后事");
                break;
            }
            try {
                log.debug("监控中");
                Thread.sleep(2000);
            }catch (InterruptedException e){
                current.interrupt(); //由于sleep会清除Interrupt标记,重新打断
            }
        }
    };
    monitor = new Thread(runnable,"t1");
    monitor.start();
}

public void stop(){
    Thread t2 = new Thread(){
        @SneakyThrows
        public void run(){
            monitor.interrupt();
            log.debug("stop");
        }
    };
    t2.start();
}

public static void main(String[] args) throws InterruptedException{
    TwoPhaseInterrput twoPhaseInterrput = new TwoPhaseInterrput();
    twoPhaseInterrput.start();
    twoPhaseInterrput.stop();
}

image-20220316130132899

在sleep中,打断标记会被清除,所以应该在catch中再打断一次

image-20220316130712873

image-20220316130719256

3.11 Park&UnPark

LockSupport.park();
private static void test2(){
    Thread t1 = new Thread(()->{
        log.debug("start..");
        Sleeper.sleep(2);
        log.debug("park...");
        LockSupport.park();
        log.debug("resume...");
    },"t1");
    t1.start();

    Sleeper.sleep(1);
    log.debug("unpark....");
    LockSupport.unpark(t1);
}
18:43:35.503 [t1] DEBUG JUC.a_JavaThread.parkThread - start..
18:43:36.507 [main] DEBUG JUC.a_JavaThread.parkThread - unpark....
18:43:37.518 [t1] DEBUG JUC.a_JavaThread.parkThread - park...
18:43:37.518 [t1] DEBUG JUC.a_JavaThread.parkThread - resume...
特点

与Object的wait&notify相比:

  • park和unpark无需在同步代码块中使用,wait,notify和notifyAll必须配合Object Monitor一起使用
  • park & unpark 是以线程单位来【阻塞】和【唤醒】线程,而notify只能随机唤醒一个线程,比notify更加精确
  • park & unpark 可以先 unpark,但是 wait & notify不能先notify

该方法会使线程停止,可以用interrupt打断park状态,但此时打断标记为ture,下一次调用park方法会根据打断标记是否为false来判断是否停止线程。

private static void test() throws InterruptedException{
        Thread t1 = new Thread(() -> {
            log.debug("park...");
            LockSupport.park();
            log.debug("unpark...");
            log.debug("打断状态:{}",Thread.currentThread().isInterrupted());
            LockSupport.park();
            log.debug("unpark...");
            log.debug("打断状态:{}",Thread.interrupted());
            LockSupport.park();
            log.debug("unpark");
        },"t1");
        t1.start();
        Thread.sleep(1000);
        t1.interrupt();
    }

image-20220316132557295

原理
park()
public static void park() {
    UNSAFE.park(false, 0L);
}
park(Object blocker)
public static void park(Object blocker) {
    Thread t = Thread.currentThread();
    setBlocker(t, blocker);
    UNSAFE.park(false, 0L);
    setBlocker(t, null);
}

方法分析:

  • 这里的blocker 是指被阻塞的对象,用于之后的线程监控和分析工具来定位
public native void park(boolean isAbsolute, long time)
  • isAbsolut = true:ms定时,isAbsolut=false:ns定时

在Linux系统下,是用的Posix线程库pthread种的mutex(互斥量),condition(条件变量) 来实现的。

mutex和condition保护了一个_counter的变量,当park时,这个变量被设置为0,当unpark时,这个变量被设置为1。

condition条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其他的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程,这些线程将重新锁定互斥锁并重新测试条件是否满足。

每个Java线程都有一个Parker实例,Parker类是这样定义的

Park类定义
/* In the future we'll want to think about eliminating Parker and using
 * ParkEvent instead.  There's considerable duplication between the two
 * services.
 */
/*
*将来我们会考虑除掉Parker,用ParkEvent代替。两者之间有相当多的重复服务。
*/
class Parker : public os::PlatformParker {
private:
  volatile int _counter ;
  Parker * FreeNext ;
  JavaThread * AssociatedWith ; // Current association

public:
  Parker() : PlatformParker() {
    _counter       = 0 ;
    FreeNext       = NULL ;
    AssociatedWith = NULL ;
  }
protected:
  ~Parker() { ShouldNotReachHere(); }
public:
  // For simplicity of interface with Java, all forms of park (indefinite,
  // relative, and absolute) are multiplexed into one call.
  void park(bool isAbsolute, jlong time);
  void unpark();

  // Lifecycle operators
  static Parker * Allocate (JavaThread * t) ;
  static void Release (Parker * e) ;
private:
  static Parker * volatile FreeList ;
  static volatile int ListLock ;

};

os:: PlatformParker
class PlatformParker : public CHeapObj<mtInternal> {
  protected:
    enum {
        REL_INDEX = 0,
        ABS_INDEX = 1
    };
    int _cur_index;  // which cond is in use: -1, 0, 1  
    pthread_mutex_t _mutex [1] ;
    pthread_cond_t  _cond  [2] ; // one for relative times and one for abs.

  public:       // TODO-FIXME: make dtor private
    ~PlatformParker() { guarantee (0, "invariant") ; }

  public:
    PlatformParker() {
      int status;
      status = pthread_cond_init (&_cond[REL_INDEX], os::Linux::condAttr());
      assert_status(status == 0, status, "cond_init rel");
      status = pthread_cond_init (&_cond[ABS_INDEX], NULL);
      assert_status(status == 0, status, "cond_init abs");
      status = pthread_mutex_init (_mutex, NULL);
      assert_status(status == 0, status, "mutex_init");
      _cur_index = -1; // mark as unused
    }
};

Park源码
void Parker::park(bool isAbsolute, jlong time) {
  
  //原子交换,如果_counter > 0,则将_counter置为0,直接返回,否则_counter为0
  if (Atomic::xchg(0, &_counter) > 0) return;
  //获取当前线程
  Thread* thread = Thread::current();
  assert(thread->is_Java_thread(), "Must be JavaThread");
  //下转型为java线程
  JavaThread *jt = (JavaThread *)thread;
 
 
  //如果当前线程设置了中断标志,调用park则直接返回,所以如果在park之前调用了
  //interrupt就会直接返回
  if (Thread::is_interrupted(thread, false)) {
    return;
  }
 
  // 高精度绝对时间变量
  timespec absTime;
  //如果time小于0,或者isAbsolute是true并且time等于0则直接返回
  if (time < 0 || (isAbsolute && time == 0) ) { // don't wait at all
    return;
  }
  //如果time大于0,则根据是否是高精度定时计算定时时间
  if (time > 0) {
    unpackTime(&absTime, isAbsolute, time);
  }
 
 
  //进入安全点避免死锁
  ThreadBlockInVM tbivm(jt);
 
 
  //如果当前线程设置了中断标志,或者获取mutex互斥锁失败则直接返回
  //由于Parker是每个线程都有的,所以_counter cond mutex都是每个线程都有的,
  //不是所有线程共享的所以加锁失败只有两种情况,第一park已经加锁这时只需要返回即可,
  //第二调用调用pthread_mutex_trylock出错。对于第一种情况就类似是unpark先调用的情况,所以
  //直接返回。
  if (Thread::is_interrupted(thread, false) || pthread_mutex_trylock(_mutex) != 0) {
    return;
  }
 
  int status ;
  //如果_counter大于0,说明unpark已经调用完成了将_counter置为了1,
  //现在只需将_counter置0,解锁,返回
  if (_counter > 0)  { // no wait needed
    _counter = 0;
    status = pthread_mutex_unlock(_mutex);
    assert (status == 0, "invariant");
    OrderAccess::fence();
    return;
  }
 
 
  OSThreadWaitState osts(thread->osthread(), false /* not Object.wait() */);
  jt->set_suspend_equivalent();
  // cleared by handle_special_suspend_equivalent_condition() or java_suspend_self()
 
  assert(_cur_index == -1, "invariant");
  //如果time等于0,说明是相对时间也就是isAbsolute是fasle(否则前面就直接返回了),则直接挂起
  if (time == 0) {
    _cur_index = REL_INDEX; // arbitrary choice when not timed
    status = pthread_cond_wait (&_cond[_cur_index], _mutex) ;
  } else { //如果time非0
    //判断isAbsolute是false还是true,false的话使用_cond[0],否则用_cond[1]
    _cur_index = isAbsolute ? ABS_INDEX : REL_INDEX;
    //使用条件变量使得当前线程挂起。
    status = os::Linux::safe_cond_timedwait (&_cond[_cur_index], _mutex, &absTime) ;
    //如果挂起失败则销毁当前的条件变量重新初始化。
    if (status != 0 && WorkAroundNPTLTimedWaitHang) {
      pthread_cond_destroy (&_cond[_cur_index]) ;
      pthread_cond_init    (&_cond[_cur_index], isAbsolute ? NULL : os::Linux::condAttr());
    }
  }
 
  //如果pthread_cond_wait成功则以下代码都是线程被唤醒后执行的。
  _cur_index = -1;
  assert_status(status == 0 || status == EINTR ||
                status == ETIME || status == ETIMEDOUT,
                status, "cond_timedwait");
 
#ifdef ASSERT
  pthread_sigmask(SIG_SETMASK, &oldsigs, NULL);
#endif
  //将_counter变量重新置为0
  _counter = 0 ;
  //解锁
  status = pthread_mutex_unlock(_mutex) ;
  assert_status(status == 0, status, "invariant") ;
  // 使用内存屏障使_counter对其它线程可见
  OrderAccess::fence();
 
  // 如果在park线程挂起的时候调用了stop或者suspend则还需要将线程挂起不能返回
  if (jt->handle_special_suspend_equivalent_condition()) {
    jt->java_suspend_self();
  }
}

实际上Parker类用Posix的mutex,condition来实现的阻塞唤醒。可以简单理解为mutex就是Java里的synchronized,condition就是Object里的wait/notify操作。

park方法里调用pthread_mutex_trylock方法,就相当于Java线程进入Java的同步代码块,然后再次判断_counter是否大于零,如果大于零则将_counter设置为零。最后调用pthread_mutex_unlock解锁,相当于Java执行完退出同步代码块。如果_counter不大于零,则继续往下执行pthread_cond_wait方法,实现当前线程的阻塞。

  • 当调用park时,先尝试直接是否能拿到_counter,即counter>0,如果成功则counter=0,返回
  • 否则,再判断等待时间,然后再调用pthread_cond_wait函数等待,如果等待返回,则把counter设置为0,unlock mutex返回

流程:

1, Atom原子判断 _count>0 (count=0往下)

2, 判断时间是否为绝对时间,以及时间是否合法 (合法往下)

3, 判断线程中途是否被打断,调用pthread_mutex_trylock进入同步代码块,并加锁,判断是否count>0 (没被打断,count=0 往下)

4, 如果count>0 将count=0 同时调用pthread_cond_unlock 解锁,退出同步代码块 (count=0 往下)

5, 调用pthread_cond_wait()设置线程状态为等待,将线程阻塞进入cond

ThreadBlockInVM tbivm(jt)
class ThreadBlockInVM : public ThreadStateTransition {
 public:
  ThreadBlockInVM(JavaThread *thread)
  : ThreadStateTransition(thread) {
    // Once we are blocked vm expects stack to be walkable    
    thread->frame_anchor()->make_walkable(thread);
   //把线程由运行状态转成阻塞状态
    trans_and_fence(_thread_in_vm, _thread_blocked);
  }
  ...
};
unpark
void Parker::unpark() {
  int s, status ;
  //加互斥锁
  status = pthread_mutex_lock(_mutex);
  assert (status == 0, "invariant") ;
  s = _counter;
  _counter = 1; //将_counter置1
  //如果_counter是0则说明调用了park或者没调用(初始为counter0)
  //这也说明park和unpark调用没有先后顺序。
  if (s < 1) {
    // 说明当前parker对应的线程挂起了,因为_cur_index初始是-1,并且等待条件变量的线程被唤醒
    //后也会将_cur_index重置-1
    if (_cur_index != -1) {
       //如果设置了WorkAroundNPTLTimedWaitHang先调用signal再调用unlock,否则相反
      //这两个先后顺序都可以,在hotspot在Linux下默认使用这种方式
      //即先调用signal再调用unlock
      if (WorkAroundNPTLTimedWaitHang) {
        status = pthread_cond_signal (&_cond[_cur_index]);
        assert (status == 0, "invariant");
        status = pthread_mutex_unlock(_mutex);
        assert (status == 0, "invariant");
      } else {
        status = pthread_mutex_unlock(_mutex);
        assert (status == 0, "invariant");
        status = pthread_cond_signal (&_cond[_cur_index]);
        assert (status == 0, "invariant");
      }
    } else { //如果_cur_index == -1说明线程没在等待条件变量,则直接解锁
      pthread_mutex_unlock(_mutex);
      assert (status == 0, "invariant") ;
    }
  } else {//如果_counter == 1,说明线程调用了一次或多次unpark但是没调用park,则直接解锁
    pthread_mutex_unlock(_mutex);
    assert (status == 0, "invariant") ;
  }

**unpark实际上就是将_counter设为1,**如果s<1&&_cur_index!=-1(默认cur_index=1)则认为线程有阻塞 ,便将线程唤醒,s>1则直接退出

流程图
调用park()

image-20220326232855163

  • 调用Unsafe.park方法
  • 判断_counter是否为0
  • 如果_counter为0则将线程放入 _cond中阻塞
  • 并且再次设置_counter=0
调用unpark

image-20220326233050805

  • 调用Unsafe.unpark方法
  • 将 _counter设置为1
  • 查看cond中 线程是否被阻塞
  • 如果被阻塞则唤醒运行 并且将_counter设为0
先调用unpark再调用park

image-20220326233538199

  • 将线程的counter设置为1
  • park查看线程的counter是否为1
  • 如果为1则设置为0
  • Thread-0继续运行

3.12 守护线程

守护某一个线程,当被守护的线程结束后,守护线程无条件终止,不会等待其处理完。
public static void main(String[] args) throws InterruptedException{
    Thread t1 = new Thread(() -> {
        while (!Thread.currentThread().isInterrupted()){
        }
        log.debug("end!");
    },"t1");
    t1.setDaemon(true);
    t1.start();
     Thread.sleep(1000);
     log.debug("main end!");

}

image-20220316134148187

注意:

  • 垃圾回收器线程就是一种守护线程
  • Tomcat中的Acceptor和Poller线程都是守护线程
守护线程和用户线程有什么区别呢?
  • **用户 (User) 线程:**运行在前台,执行具体的任务,如程序的主线程、连接网络的子线程等都是用户线程

  • **守护 (Daemon) 线程:**运行在后台,为其他前台线程服务。也可以说守护线程是 JVM 中非守护线程的 “佣人”。一旦所有用户线程都结束运行,守护线程会随 JVM 一起结束工作

main 函数所在的线程就是一个用户线程啊,main 函数启动的同时在 JVM 内部同时还启动了好多守护线程,比如垃圾回收线程。

比较明显的区别之一是用户线程结束,JVM 退出,不管这个时候有没有守护线程运行。而守护线程不会影响 JVM 的退出。

3.13 线程状态

五种状态

image-20220316135550576

初始态,就绪态,运行态,阻塞,终止

  • 【初始状态】仅在语言层面上创建了对象,还没和操作系统线程关联
  • 【就绪状态】线程已经被创建,可以由CPU调度执行
  • 【运行状态】获取了CPU时间片运行中的状态
    • 当时间片用完就会从【运行状态】转换为【可运行状态】,导致线程上下文切换
  • 【阻塞状态】
六种状态

image-20220316190519924

**注:**RUNNABLE涵盖了操作系统中的【运行状态,阻塞状态,可运行状态】

操作系统的阻塞状态,例如:操作系统线程执行BIO,此时的操作系统的线程状态是阻塞的,但是在Java中还是保持运行的。

Thread t0 = new Thread(() -> {
   log.debug("t1 running");
},"t0");
// NEW

Thread t1 = new Thread(() -> {
    log.debug("t1 running");
},"t1");
t1.start();
//TERMINATED

Thread t2 = new Thread(() -> {
    log.debug("t2 running");
    while(true){

    }
},"t2");
t2.start();
//RUNNABLE

Thread t3 = new Thread(() -> {
    synchronized (ThreadState.class){
        try {
            Thread.sleep(1000000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
},"t3");
t3.start();
//TIME_WAITING

Thread t4 = new Thread(() -> {
    synchronized (ThreadState.class){
        try {
            Thread.sleep(10000000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
},"t4");
t4.start();
//BLOCKED

Thread t5 = new Thread(() -> {
   try {
       t2.join();
   }catch (InterruptedException e){
       e.printStackTrace();
   }
},"t5");
t5.start();
//WAITING
public enum State {
    /**
     * Thread state for a thread which has not yet started.
     */
    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,

    /**
     * Thread state for a thread blocked waiting for a monitor lock.
     * A thread in the blocked state is waiting for a monitor lock
     * to enter a synchronized block/method or
     * reenter a synchronized block/method after calling
     * {@link Object#wait() Object.wait}.
     */
    BLOCKED,

    /**
     * Thread state for a waiting thread.
     * A thread is in the waiting state due to calling one of the
     * following methods:
     * <ul>
     *   <li>{@link Object#wait() Object.wait} with no timeout</li>
     *   <li>{@link #join() Thread.join} with no timeout</li>
     *   <li>{@link LockSupport#park() LockSupport.park}</li>
     * </ul>
     *
     * <p>A thread in the waiting state is waiting for another thread to
     * perform a particular action.
     *
     * For example, a thread that has called <tt>Object.wait()</tt>
     * on an object is waiting for another thread to call
     * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
     * that object. A thread that has called <tt>Thread.join()</tt>
     * is waiting for a specified thread to terminate.
     */
    WAITING,

    /**
     * Thread state for a waiting thread with a specified waiting time.
     * A thread is in the timed waiting state due to calling one of
     * the following methods with a specified positive waiting time:
     * <ul>
     *   <li>{@link #sleep Thread.sleep}</li>
     *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
     *   <li>{@link #join(long) Thread.join} with timeout</li>
     *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
     *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
     * </ul>
     */
    TIMED_WAITING,

    /**
     * Thread state for a terminated thread.
     * The thread has completed execution.
     */
    TERMINATED;
}

由java源码可以发现,Java API中对线程状态的描述有6种

Java计算线程状态
public static State toThreadState(int var0) {
        if ((var0 & 4) != 0) {
            return State.RUNNABLE;
        } else if ((var0 & 1024) != 0) {
            return State.BLOCKED;
        } else if ((var0 & 16) != 0) {
            return State.WAITING;
        } else if ((var0 & 32) != 0) {
            return State.TIMED_WAITING;
        } else if ((var0 & 2) != 0) {
            return State.TERMINATED;
        } else {
            return (var0 & 1) == 0 ? State.NEW : State.RUNNABLE;
        }
    }

JavaThread的状态对应Jvm的状态

// Java Thread Status for JVMTI and M&M use.
  // This thread status info is saved in threadStatus field of
  // java.lang.Thread java class.
  enum ThreadStatus {
    NEW                      = 0,
    RUNNABLE                 = JVMTI_THREAD_STATE_ALIVE +          // runnable / running
                               JVMTI_THREAD_STATE_RUNNABLE,
    SLEEPING                 = JVMTI_THREAD_STATE_ALIVE +          // Thread.sleep()
                               JVMTI_THREAD_STATE_WAITING +
                               JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT +
                               JVMTI_THREAD_STATE_SLEEPING,
    IN_OBJECT_WAIT           = JVMTI_THREAD_STATE_ALIVE +          // Object.wait()
                               JVMTI_THREAD_STATE_WAITING +
                               JVMTI_THREAD_STATE_WAITING_INDEFINITELY +
                               JVMTI_THREAD_STATE_IN_OBJECT_WAIT,
    IN_OBJECT_WAIT_TIMED     = JVMTI_THREAD_STATE_ALIVE +          // Object.wait(long)
                               JVMTI_THREAD_STATE_WAITING +
                               JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT +
                               JVMTI_THREAD_STATE_IN_OBJECT_WAIT,
    PARKED                   = JVMTI_THREAD_STATE_ALIVE +          // LockSupport.park()
                               JVMTI_THREAD_STATE_WAITING +
                               JVMTI_THREAD_STATE_WAITING_INDEFINITELY +
                               JVMTI_THREAD_STATE_PARKED,
    PARKED_TIMED             = JVMTI_THREAD_STATE_ALIVE +          // LockSupport.park(long)
                               JVMTI_THREAD_STATE_WAITING +
                               JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT +
                               JVMTI_THREAD_STATE_PARKED,
    BLOCKED_ON_MONITOR_ENTER = JVMTI_THREAD_STATE_ALIVE +          // (re-)entering a synchronization block
                               JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER,
    TERMINATED               = JVMTI_THREAD_STATE_TERMINATED
  };
Thread State Flags
ConstantValueDescription
JVMTI_THREAD_STATE_ALIVE0x0001Thread is alive. Zero if thread is new (not started) or terminated.
JVMTI_THREAD_STATE_TERMINATED0x0002Thread has completed execution.
JVMTI_THREAD_STATE_RUNNABLE0x0004Thread is runnable.
JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER0x0400Thread is waiting to enter a synchronization block/method or, after an Object.wait(), waiting to re-enter a synchronization block/method.
JVMTI_THREAD_STATE_WAITING0x0080Thread is waiting.
JVMTI_THREAD_STATE_WAITING_INDEFINITELY0x0010Thread is waiting without a timeout. For example, Object.wait().
JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT0x0020Thread is waiting with a maximum time to wait specified. For example, Object.wait(long).
JVMTI_THREAD_STATE_SLEEPING0x0040Thread is sleeping – Thread.sleep(long).
JVMTI_THREAD_STATE_IN_OBJECT_WAIT0x0100Thread is waiting on an object monitor – Object.wait.
JVMTI_THREAD_STATE_PARKED0x0200Thread is parked, for example: LockSupport.park, LockSupport.parkUtil and LockSupport.parkNanos.
JVMTI_THREAD_STATE_SUSPENDED0x100000Thread suspended. java.lang.Thread.suspend() or a JVM TI suspend function (such as [SuspendThread](file:///home/alanjin/gitspace/jdk10/hotspot/src/share/vm/prims/jvmti.xml#SuspendThread)) has been called on the thread. If this bit is set, the other bits refer to the thread state before suspension.
JVMTI_THREAD_STATE_INTERRUPTED0x200000Thread has been interrupted.
JVMTI_THREAD_STATE_IN_NATIVE0x400000Thread is in native code–that is, a native method is running which has not called back into the VM or Java programming language code. This flag is not set when running VM compiled Java programming language code nor is it set when running VM code or VM support code. Native VM interface functions, such as JNI and JVM TI functions, may be implemented as VM code.
JVMTI_THREAD_STATE_VENDOR_10x10000000Defined by VM vendor.
JVMTI_THREAD_STATE_VENDOR_20x20000000Defined by VM vendor.
JVMTI_THREAD_STATE_VENDOR_30x40000000Defined by VM vendor.

从c++层面看c++线程的状态

enum JavaThreadState {
  _thread_uninitialized     =  0, // should never happen (missing initialization)
  _thread_new               =  2, // just starting up, i.e., in process of being initialized
  _thread_new_trans         =  3, // corresponding transition state (not used, included for completness)
  _thread_in_native         =  4, // running in native code
  _thread_in_native_trans   =  5, // corresponding transition state
  _thread_in_vm             =  6, // running in VM
  _thread_in_vm_trans       =  7, // corresponding transition state
  _thread_in_Java           =  8, // running in Java or in stub code
  _thread_in_Java_trans     =  9, // corresponding transition state (not used, included for completness)
  _thread_blocked           = 10, // blocked in vm
  _thread_blocked_trans     = 11, // corresponding transition state
  _thread_max_state         = 12  // maximum thread state+1 - used for statistics allocation
};

_thread_new: 新创建的线程

_thread_in_Java: 在运行Java代码

_thread_in_vm: 在运行JVM本身的代码

_thread_in_native: 在运行native代码

_thread_blocked: 线程被阻塞了,包括等待一个锁,等待一个条件,sleep,执行一个阻塞的IO等

Java采用的线程调度算法

**两种调度模型:**分时调度模型和抢占式调度模型

java采用的是抢占式调度模型,即优先级越高的线程占用CPU

什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing )?

线程调度器是一个操作系统服务,它负责为 Runnable 状态的线程分配 CPU 时间。一旦我们创建一个线程并启动它,它的执行便依赖于线程调度器的实现。

时间分片是指将可用的 CPU 时间分配给可用的 Runnable 线程的过程。分配 CPU 时间可以基于线程优先级或者线程等待的时间。

线程调度并不受到 Java 虚拟机控制,所以由应用程序来控制它是更好的选择(也就是说不要让你的程序依赖于线程的优先级)。

4 共享模型之管程

4.1 上下文切换问题

当让两个线程分别对 static int a = 0 做 a++,a–;

public static void contextSwitch()throws InterruptedException{
    Thread t0 = new Thread(() -> {
        while (true){
            a = a++;
        }
    },"t0");
    t0.start();
    Thread t1 = new Thread(() -> {
        while (true){
            a = a--;
        }
    },"t1");
    t1.start();
    while(true){
        Thread.sleep(1000);
        System.out.println(a);
    }
}

那道理最后的结果a应该为0但是 却出现了这种情况

image-20220316231530549

image-20220316230546613

在线程1还没执行istroe_1时被线程2抢占,由于i++是先改变变量表中的值,在返回操作数栈的原本值,这个时候还没返回值线程2取走了原本变量表中的值然后进行操作就会导致原本为0的值改变。

4.2 临界区Critical Section

  • 一个程序运行多个线程本身是没有问题的

  • 问题出现在线程访问的共享资源

    多个线程对共享资源的读写操作发生指令交错,就会出现问题

  • 一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区

竞态条件

多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,就叫做竞态条件

4.3 synchronized 解决方案

为了避免临界区的竞态条件发生,有多种手段可以达到目的。

  • 阻塞式的解决方案:synchronized,Lock
  • 非阻塞式的解决方案:原子变量

synchronized俗称【对象锁】,它采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】,其他线程想获取该对象时会被阻塞住。这样就可以不用担心上下文切换,安全的执行临界区中的资源。y

synchronized代码格式

synchronized (//锁对象) {
       //代码块
}

示例:

public class synchronization {
    static int a =0;
    static Object lock = new Object();
    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            synchronized (lock) {
                for (int i = 0; i <1000 ; i++) {
                    a=a++;
                }

            }
        });
        t1.start();
        Thread t2 = new Thread(()->{
            synchronized (lock) {
                for (int i = 0; i <1000 ; i++) {
                    a=a++;
                }
            }
        });
        t2.start();
        System.out.println(t2.getState());
        System.out.println(t1.getState());
        System.out.println(a);
    }
}

输出:

RUNNABLE
RUNNABLE
0或1

【注】:这里结果为什么会有1呢,因为获取1的时候没有加锁线程获取对象锁并不是能一直执行下去,当线程的时间片使用完时,他还是会被踢出CPU房间,但是手中一直拿着房间钥匙,没有钥匙的人是无法进入运行且无法被 ,当下次轮到该线程时,即可进入线程继续工作,只有当代码块执行完时,才会把锁让出,唤醒阻塞线程。

image-20220319174358728

synchronized 实际是用对象锁保证了临界区内代码的原子性,临界区的代码是对外不可分的,不会被线程切换所打断。

**思考 **

  • 如果把synchronized(obj)放在for循环外,会怎么样?

​ 还是处于加锁状态,不会出现上下文切换问题

  • 如果t1 synchronized(obj1) 而 t2 synchronized(obj2) 会怎么样?

由于t1和t2获取的是不同的锁,是给不同的对象加锁,所以会出现上下文切换问题

  • 如果t1 synchronized(obj) 而 t2没有加锁会怎么样?

由于t2没有获取对象锁,所以不会被阻塞,还是会进入CPU运行,会出现上下文切换问题

synchronized 使用在方法上

用在static上面是锁住当前class的字节码文件

public synchronized void test(){

}
//等价于
public void test(){
    synchronized(this){
    
    }
}
public synchronized static void test(){

}
//等价于
public void test(){
    synchronized(Test.class){
    
    }
}

4.4 变量的线程安全分析

成员变量和静态变量是否线程安全?

  • 如果无共享,则线程十分安全
  • 如果有共享,根据状态是否能够改变来分两种情况

​ 如果只有只读操作,则安全

​ 如果有只读和只写操作,则线程不安全

  • 局部变量是线程安全的
  • 局部变量引用的对象不一定安全

​ 当局部变量中的引用对象被其他线程共享,也就是多个线程同时使用一个局部变量时,就会导致线程安全问题

局部变量线程安全分析
public static void test1(){
        int i = 10;
        i++;
    }

上面这个方法中的i是不会出现线程安全问题的

因为在Jvm虚拟机中,每调用一个方法就会生成一个栈帧,而每个线程中的栈帧是互不共享的,也就是说,每一个线程会在自己的java虚拟机栈中的局部变量表生成一个变量i

image-20220319221818683

案例:

成员变量

class ThreadUnsafe{
    ArrayList<String> list = new ArrayList<>();
    public void method1(int loopNumber){
        for (int i = 0; i <loopNumber ; i++) {
            method2();
            method3();
        }
    }

    private void method2(){list.add("1");}

    private void method3(){list.remove(0);}
}

public static void test2(){
        ThreadUnsafe test = new ThreadUnsafe();
        for (int i = 0; i <Thread_Number ; i++) {
            new Thread(()->{
                test.method1(Loop_Number);
            },"Thread"+i).start();
        }
    }
Exception in thread "Thread1" java.lang.IndexOutOfBoundsException: Index: 0, Size: 1
	at java.util.ArrayList.rangeCheck(ArrayList.java:653)
	at java.util.ArrayList.remove(ArrayList.java:492)
	at JUC.b_shareProblems.ThreadUnsafe.method3(ThreadSecurity.java:48)
	at JUC.b_shareProblems.ThreadUnsafe.method1(ThreadSecurity.java:42)
	at JUC.b_shareProblems.ThreadSecurity.lambda$test2$0(ThreadSecurity.java:18)
	at java.lang.Thread.run(Thread.java:748)

由于所有线程都只访问堆中的一个成员变量,导致资源共享,所以会出现线程安全问题而报错

image-20220319223343207

image-20220321200950432

局部变量

class ThreadSafe{
    public void method1(int loopNumber){
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i <loopNumber ; i++) {
            method2(list);
            method3(list);
        }
    }

    private void method2(ArrayList<String> list){list.add("1");}

    private void method3(ArrayList<String> list){list.remove(0);}
}

public static void test3(){
        ThreadSafe test = new ThreadSafe();
        for (int i = 0; i <Thread_Number ; i++) {
            new Thread(()->{
                test.method1(Loop_Number);
            },"Thread"+i).start();
        }
    }

image-20220319223453960

局部变量线程不安全案例

class ThreadSafe{
    public void method1(int loopNumber){
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i <loopNumber ; i++) {
            method2(list);
            method3(list);
        }
    }

    private void method2(ArrayList<String> list){list.add("1");}

    public void method3(ArrayList<String> list){list.remove(0);}
}

class ThreadsubSafeClass extends ThreadSafe{
    @Override
    public void method3(ArrayList<String> list){
        new Thread(()->{
            list.remove(0);
        }).start();
    }
}
public static void test4(){
        ThreadsubSafeClass test = new ThreadsubSafeClass();
        for (int i = 0; i <Thread_Number ; i++) {
            new Thread(()->{
                test.method1(Loop_Number);
            },"Thread"+i).start();
        }
    }
Exception in thread "Thread-18115" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
	at java.util.ArrayList.rangeCheck(ArrayList.java:653)
	at java.util.ArrayList.remove(ArrayList.java:492)
	at JUC.b_shareProblems.ThreadsubSafeClass.lambda$method3$0(ThreadSecurity.java:77)
	at java.lang.Thread.run(Thread.java:748)

可以看出 在子类ThreadsubSafeClass中,重写了父类的method3方法,并且开辟了一个新的线程,此时这个新的线程与test4方法开辟的线程共享一个list局部变量,这就会导致线程不安全问题。

【注】:方法修饰符,是可以在一定程度上防止线程安全问题的,比如private,final方法就可以让子类无法重写出现安全问题。

4.5 常见的线程安全类

  • String
  • Integer
  • StringBuffer
  • Random
  • Vector
  • Hashtable
  • java.util.comcurrent包下的类

这里的线程安全指的是,多个线程调用它们同一个实例方法是线程安全的,也就是说他们的每个方法是原子的,但是它们多个方法的组合不是原子的

不可变类线程安全性

String,Integer等都是不可变类,因为其内部的状态不可以改变,所以方法都是线程安全的

例如:String.class 有 final字符修饰 来表示String字符串不可变。

String.substring()方法 也是创建一个新的字符串变量,而不是修改自身值属性。

public String substring(int beginIndex) {
    if (beginIndex < 0) {
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    int subLen = value.length - beginIndex;
    if (subLen < 0) {
        throw new StringIndexOutOfBoundsException(subLen);
    }
    return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}

4.6 案例

银行转钱
package JUC.b_shareProblems;

import lombok.extern.slf4j.Slf4j;

import java.util.Random;

@Slf4j
public class bank {

    public static void main(String[] args) throws InterruptedException{
        Account a = new Account(1000);
        Account b = new Account(0);
        Thread t1 = new Thread(()->{
            for (int i = 0; i <1000 ; i++) {
                a.transfer(b,randomAmount());
            }
        });
        Thread t2 = new Thread(()->{
            for (int i = 0; i <1000 ; i++) {
                b.transfer(a,randomAmount());
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        log.debug("总金额:{}",a.getMoney()+b.getMoney());
    }

    static Random r = new Random();

    public static int randomAmount(){
        return r.nextInt(5)+1;}
}


class Account{
    int money;

    public Account(int money) {
        this.money = money;
    }

    public int getMoney() {
        return money;
    }

    public void setMoney(int money) {
        this.money = money;
    }

    public void transfer(Account target,int money){
        if(this.money > money){
            this.setMoney(this.money-money);
            target.setMoney(target.getMoney()+money);
        }
    }
}
21:28:02.747 [main] DEBUG JUC.b_shareProblems.bank - 总金额:938

由输出可以看出 原本应该总金额为1000,现在反而还减少了,说明由于线程安全问题,导致上下文切换出现问题。

如果加锁我们应该怎么加?

  • 对transfer方法加上synchronized?

如果我们对transfer方法加上synchronized修饰符,只会对类实例本身进行加锁(this),由于账户A和账户B是不同的类,所以无法放在线程A,B的上下文切换

  • 对Account.class加锁?

可以防止线程安全问题,但是会是的整个类被封锁,而导致性能下降

4.7 Monitor概念

Java对象头

Integer : 8(对象头)+4(值)+2(补位 要求8位数的倍数)

int: 4

image-20220321213336713

image-20220321213446004

image-20220321213554440

Monitor(锁)

Monitor是操作系统中的一个监管者

一个monitor对象包括这么几个关键字段:cxq(下图中的ContentionList),EntryList ,WaitSet,owner。

其中cxq ,EntryList ,WaitSet都是由ObjectWaiter的链表结构,owner指向持有锁的线程。

image

上锁流程
  • 当有一个临界区代码被上锁时,上锁对象obj中的MarkWord将改变为HeavyLock状态,并且指向一个Monitor锁
  • 当Thread-2 要使用临界区代码时,会查看被上锁的对象obj对应的Monitor锁,查看Owner是否为空,如果为空则成为Monitor的Owner并使用临界区代码
  • 当Thread-1来访问临界区代码时,会查看被上锁的对象obj对应的Monitor锁,查看Owner是否为空,此时Owner不为空的话,将Thread-1放入EntryList中,并且将状态设置为BLOCKED
  • 当Thread-3 来访问临界区代码时,重复上述步骤,并且放入Thread-1之后(链表结构)
  • 当Thread-2使用完临界区代码时,将唤醒EntryList中的BLOCKED队列并且进行锁竞争(非公平),来成为Monitor的Owner
  • 如果Thread-1之前获取过锁,但条件不满足则进入Wating状态
synchronized字节码解析
public class Monitor {
    static final Object lock = new Object();
    static int counter = 0;

    public static void main(String[] args) {
        synchronized (lock){
            counter++;
        }
    }
}

image-20220321221508374

在synchronized加锁的代码块中。

0: getstatic 获取lock代码的引用

1: astore_1 并将lock代码的引用加入导局部变量槽中

5: monitorenter 将lock对象MarkWord变为Monitor指针

14: aload_1 将lock引用加载到操作数栈中

15: monitorexit 将lock对象MarkWord重置,唤醒EntryList

19~23 代码是当synchronized中的代码块出现异常时,进行异常抛出并且重置锁对象

4.8 轻量级锁

**轻量级锁的使用场景:**如果一个对象有多个线程访问,但是线程访问时间错开(没有竞争),那么可以使用轻量级锁来优化。

public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            method1();
        });
        t1.start();
    }
    public static void method1(){
        synchronized (object){
            method2();
        }
    }

    public static void method2(){
        synchronized (object){
            System.out.println("LightWeightLock");
        }
    }
  • 创建锁记录(Lock Record)对象,每个线程的栈帧都会包含一个锁记录结构,内部可以存储锁对象的MarkWord

image-20220322204732367

  • 让锁记录中的Object reference 指向锁对象,并且让cas 替换 Object 的 Mark Word,将Mark Word的值存入锁记录

image-20220322211122103

  • 如果cas替换成功,对象头中存储了锁记录地址和状态00,表示有该线程给对象加锁,这时图示如下

image-20220322212151746

  • 如果cas失败,有两种情况

​ 1,如果是其它的线程已经持有了该Object的轻量级锁,这时表明有竞争,进入锁膨胀

​ 2,如果是自己执行了synchronized锁重入,那么再添加一条Lock record作为重入的计数

image-20220322212943855

  • 解锁过程,如果Thread-0中有记录为null的锁记录,则表示有重入,将锁记录移除,并将重入-1;

image-20220322212151746

  • 当退出锁过程中,Lock Record记录中不为Null,则利用cas将MarkWord对象恢复

    1,成功,解锁成功

    2,失败,说明锁膨胀为重量级锁,进入重量级锁解锁流程

4.9 锁膨胀

如果再尝试加轻量级锁的过程中,CAS操作无法成功,这时的一种情况就是有其他的线程为此对象加上了轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁

  • 当Thread-1 访问Object对象时,进行cas交换,发现Object对象已经被赋上轻量级锁,则进入锁膨胀

image-20220322220646392

  • Thread-1加锁失败,进入锁膨胀流程

​ 1,为Object对象申请Monitor,让Object指向重量级锁地址

​ 2,然后自己进入Monitor的EntryList BLOCKED

image-20220322221533904

  • 当Thread-0退出同步块解锁时,使用cas将MarkWord的值恢复给对象头,失败。这时会进入重量级解锁流程,即按照Monitor地址找到Monitor对象,设置Owner为Null,唤醒EntryList中的BLOCKED线程

4.10 自旋优化(多核CPU)

重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步块,释放了锁),这时当前线程就可以避免阻塞。(可以防止上下文切换,提高性能)

自旋成功的情况

image-20220322223354746

自旋失败的情况

image-20220323195519375

  • 在Java6之后自旋锁是自适应的,比如刚刚对象自旋成功的话,就会认为这次自旋成功的可能性会高,就多自旋几次;反之,就减少自旋甚至不自旋
  • 自适应自旋解决的是“锁竞争时间不确定”的问题
  • 自旋会占用CPU时间,单核CPU自旋等于浪费时间

4.11 偏向锁

轻量级锁在无实际锁竞争时,每次的锁重入都要进行无意义的cas操作,于是我们可以对其进行优化。

Java 6中引入了偏向锁来做进一步优化;只有第一次使用CAS将线程ID设置到对象的MarkWord头,之后发现这个线程ID是自己的就表示没有竞争,不用重新CAS,以后只要不发生竞争,这个对象就归该线程所有。

image-20220323200911602

偏向锁获取过程

匿名偏向状态,即MarkWord,ThreadId部分为0,表面处于偏向状态,但是为偏向任何锁。

  • 线程A进入同步代码块,首先检查markword 头部分是否为 01 即偏向锁或无锁状态,若是则进入下一步
  • 线程A查看biased_lock的数值,如果为0,则代表该对象禁用偏向锁,不CAS直接进行锁升级,如果为1,则进入偏向锁加锁环境
  • 此时线程A查看ThreadId的数值
  • 为自身ID,则不进行CAS,而是直接在栈帧中加一条自己的锁记录
  • 不为自身ID,ID为0,则代表无偏向,此时进行CAS,CAS成功则偏向成功
  • 不为自身ID,ID为其他值,则代表已经有偏向,此时进行CAS失败,升级为轻量级锁
偏向状态

image-20220323203531014

一个对象创建时:

  • (java 17)默认禁用偏向锁,如果默认开启偏向锁(默认开启),那么对象创建后,markword值为0x05即最后三位为101,这时它的Thread,epoch,age都设置为0
  • 偏向锁是默认延迟的,不会再程序启动时立即生效,若想避免延迟,可以加VM参数
-XX:BiasedLockingStarupDelay=0

原因是 JVM 内部的代码有很多地方用到了synchronized,如果直接开启偏向,产生竞争就要有锁升级,会带来额外的性能损耗,所以就有了延迟策略。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2dHEIoEu-1660116052640)(http://inews.gtimg.com/newsapp_bt/0/14383522992/641)]

**注:**当未开启无延迟的偏向锁时,一开始的锁对象为non-biasable; 调用synchronized会直接变成thin Lock

  • 如果没有开启偏向锁,那么对象创建后,markword的值为0x01即最后3位为001,这时他的hashcode,age都为0,第一次用到hashcode时才会赋值
  • **Java17移除原因:**偏向锁给 JVM 增加了巨大的复杂性,只有少数非常有经验的程序员才能理解整个过程,维护成本很高,大大阻碍了开发新特性的进程

当我们的项目本身是一个多线程高竞争的项目,我们再使用偏向锁会导致性能降低,此时我们便要禁用偏向锁

-XX:-UserBiasedLocking
偏向锁撤销

偏向锁的撤销,要等待全局安全点才能进行,到达全局安全点,持有偏向锁的线程B也停止了

  • 查看线程B是否还存活且还在执行同步代码块的代码
  • 线程B存活,则将偏向锁升级为轻量级锁,并且继续持有锁向下执行
  • 线程B不存活,则查看偏向锁是否允许重偏向
  • 允许重偏向,则设置锁为匿名状态,线程A进行CAS获取偏向锁
  • 不允许重偏向,则设置锁为无锁状态,进行锁升级,CAS竞争锁
1,撤销-调用hashcode

当对对象使用HashCode时,会禁用偏向锁,因为hashcode覆盖了线程的地址

为什么轻量级锁和重量级锁调用HashCode不会撤销锁状态呢?

  • 因为轻量级锁的markword是保存在加锁线程上的
  • 而重量级锁的markword是保存至monitor中的
2,撤销-其它线程使用对象

当有其它线程使用偏向锁对象时,会将偏向锁升级为轻量级锁

3,撤销-调用wait/notify
批量重偏向

Thread-1对Obj加上偏向锁,当Thread-2访问Obj时,会撤销偏向锁,转化为轻量级锁,但是当之后Obj由Thread-2多次访问时(阈值20次),JVM就会考虑是否加错偏向锁,于是对Obj的偏向指向Thread-2。

批量重偏向是指,当有30个对象被Thread-1加锁,那当Thread-2访问这30个对象时,前19个对象会被升级为轻量级锁,后11个对象(该类直接重偏向)编程偏向Thread-2的偏向锁

批量撤销

当撤销偏向锁的阈值超过40次后,jvm会怀疑人生,决定自己不应该偏向谁,于是就不偏向,把整个类的所有对象都设为不可偏向,新建的对象也是不可偏向

4.12 锁粗化

通常情况下,为了保证多线程间的有效并发,会要求每个线程持有锁的时间尽可能短,但是大某些情况下,一个程序对同一个锁不间断、高频地请求、同步与释放,会消耗掉一定的系统资源,因为锁的讲求、同步与释放本身会带来性能损耗,这样高频的锁请求就反而不利于系统性能的优化了,虽然单次同步操作的时间可能很短。锁粗化就是告诉我们任何事情都有个度,有些情况下我们反而希望把很多次锁的请求合并成一个请求,以降低短时间内大量锁请求、同步、释放带来的性能损耗。

public void doSomethingMethod(){
    synchronized(lock){
        //do some thing
    }
    //这是还有一些代码,做其它不需要同步的工作,但能很快执行完毕
    synchronized(lock){
        //do other thing
    }
}

上面的代码是有两块需要同步操作的,但在这两块需要同步操作的代码之间,需要做一些其它的工作,而这些工作只会花费很少的时间,那么我们就可以把这些工作代码放入锁内,将两个同步代码块合并成一个,以降低多次锁请求、同步、释放带来的系统性能消耗,合并后的代码如下:

public void doSomethingMethod(){
    synchronized(lock){
        //do some thing
        一些很快就能执行完的代码
        //do other thing
    }
}

另一种需要锁粗化的极端的情况是:

for(int i=0;i<size;i++){
    synchronized(lock){
    }
}

上面代码每次循环都会进行锁的请求、同步与释放,看起来貌似没什么问题,且在jdk内部会对这类代码锁的请求做一些优化,但是还不如把加锁代码写在循环体的外面,这样一次锁的请求就可以达到我们的要求,除非有特殊的需要:循环需要花很长时间,但其它线程等不起,要给它们执行的机会。

锁粗化后的代码如下:

synchronized(lock){
    for(int i=0;i<size;i++){
    }
}

4.13 锁消除

当你的锁对象处于一个无法被其他线程访问到的状态时,就会默认该加锁无意义,于是在JIT即时编译时便会使用锁消除,即默认你没有加锁,以提升性能效率(但是我没测试出来)

public static void test3(){
        long startTime = System.currentTimeMillis();
        for (int i = 0; i <num ; i++) {
            a++;
        }
        long endTime = System.currentTimeMillis();
        log.debug("test3 time {}ms",endTime-startTime);
    }

    public static void test4(){
        Object obj = new Object();
        long startTime = System.currentTimeMillis();
        for (int i = 0; i <num ; i++) {
            synchronized (obj){
                a++;
            }
        }
        long endTime = System.currentTimeMillis();
        log.debug("test4 time {}ms",endTime-startTime);
    }

如果没有使用锁消除性能差别十分之大

23:10:33.602 [main] DEBUG JUC.c_monitor.LockClear - test3 time 1ms
23:10:33.625 [main] DEBUG JUC.c_monitor.LockClear - test4 time 17ms

4.14 活跃性

死锁

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法继续运行。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

public class deadLock {
   static Object b = new Object();
   static Object a = new Object();


    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            synchronized (b){
                log.debug("lock b");
                Sleeper.sleep(1);
                synchronized (a){
                    log.debug("lock a");
                }
            }
        },"t1");
        t1.start();
        Thread t2 = new Thread(()->{
            synchronized (a){
                log.debug("lock a");
                Sleeper.sleep(2);
                synchronized (b){
                    log.debug("lock a");
                }
            }
        },"t2");
        t2.start();
    }
}
哲学家问题

image-20220328230132533

哲学家问题解决
package JUC.e_ReentrantLock;

import JUC.util.Sleeper;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.ReentrantLock;

public class famliyEating {

    public static void main(String[] args) {
        Chopstick c1 = new Chopstick("1");
        Chopstick c2 = new Chopstick("2");
        Chopstick c3 = new Chopstick("3");
        Chopstick c4 = new Chopstick("4");
        Chopstick c5 = new Chopstick("5");
        new Famliy("爸爸",c1,c2).start();
        new Famliy("妈妈",c2,c3).start();
        new Famliy("儿子",c3,c4).start();
        new Famliy("爷爷",c4,c5).start();
        new Famliy("奶奶",c5,c1).start();
    }

}

@Slf4j
class Famliy extends Thread{
    Chopstick left;
    Chopstick right;

    public Famliy(String name, Chopstick left, Chopstick right) {
        super(name);
        this.left = left;
        this.right = right;
    }



    public Chopstick getLeft() {
        return left;
    }

    public Chopstick getRight() {
        return right;
    }

    public void eat(){
        log.debug("[{}]正在吃饭",getName());
        Sleeper.sleep(1);
    }

    @Override
    public void run() {
        while (true){
            //Sleeper.sleep(1);
             if(left.tryLock()){
                 try {
                     //log.debug("[{}] 拿到左手筷子[{}]",getName(),getLeft().getName());
                     if(right.tryLock()){
                         try {
                             //log.debug("[{}] 拿到右手筷子[{}]",getName(),getRight().getName());
                             eat();
                         }finally {
                             //log.debug("[{}] 放下右手筷子[{}]",getName(),getRight().getName());
                             right.unlock();
                         }
                     }
                 }finally {
                     //log.debug("[{}] 放下左手筷子[{}]",getName(),getLeft().getName());
                     left.unlock();
                 }
             }

        }
    }
}

class Chopstick extends ReentrantLock {
    String name;

    public Chopstick(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}
活锁

活锁是指两个或两个以上的进程在执行过程中,由于资源共享,不断更改其他线程的结束条件,虽然状态可以改变,却没有实质的进展,导致线程一直运行下去

public class aliveLock {
    static int i = 10;

    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            while(i>0){
            Sleeper.sleep(1);
            i--;
            log.debug("i:{}",i);
           }
         },"t1");
        t1.start();
        Thread t2 = new Thread(()->{
            while(i<20){
            Sleeper.sleep(1);
            i++;
            log.debug("i:{}",i);
           }
         },"t2");
        t2.start();
    }
}
饥饿

由于线程优先级分配不合理,一个线程在无限地等待另外两个或多个线程相互传递使用并且用不会释放的资源。

4.15 从源码去看synchronized

monitor结构
class ObjectMonitor {
...
  ObjectMonitor() {
    _header       = NULL; //markOop对象头
    _count        = 0;    
    _waiters      = 0,   //等待线程数
    _recursions   = 0;   //重入次数
    _object       = NULL;  
    _owner        = NULL;  //获得ObjectMonitor对象的线程
    _WaitSet      = NULL;  //处于wait状态的线程,会被加入到waitSet
    _WaitSetLock  = 0 ; 
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ; //处于等待锁BLOCKED状态的线程
    _SpinFreq     = 0 ;   
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ; 
    _previous_owner_tid = 0; //监视器前一个拥有线程的ID
  }
}

synchronized上锁时分别调用了 monitorenter,monitorexit两个函数,由此我们通过这两个函数来探索synchronized

monitorenter(interpeterRuntime.cpp)
IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))
#ifdef ASSERT
  thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
  if (PrintBiasedLockingStatistics) {
    Atomic::inc(BiasedLocking::slow_path_entry_count_addr());
  }
  Handle h_obj(thread, elem->obj());
  if (UseBiasedLocking) {
    // Retry fast entry if bias is revoked to avoid unnecessary inflation
    ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);//偏向锁
  } else {
    ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);//轻量级锁 
  }
  assert(Universe::heap()->is_in_reserved_or_null(elem->obj()),
         "must be NULL or an object");
#ifdef ASSERT
  thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
IRT_END

当开启偏向锁时,进入fast_enter,偏向锁加锁流程

当未开启偏向锁,进入slow_enter,轻量级锁加锁流程

偏向锁环节
fast_enter
void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias, TRAPS) {
 if (UseBiasedLocking) { //判断是否开启了偏向锁
    if (!SafepointSynchronize::is_at_safepoint()) { //如果不处于全局安全点
      //通过`revoke_and_rebias`这个函数尝试获取偏向锁
      BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD);
      if (cond == BiasedLocking::BIAS_REVOKED_AND_REBIASED) {//如果是撤销与重偏向直接返回
        return;
      }
    } else {//如果在安全点,撤销偏向锁
      assert(!attempt_rebias, "can not rebias toward VM thread");
      BiasedLocking::revoke_at_safepoint(obj);
    }
    assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
 }

 slow_enter (obj, lock, THREAD) ;
}

1,判断是否开启偏向锁

2,判断该点是否为全局安全点

3,否,进行撤销并重新偏向,成功则返回,不是则轻量级锁获取

4,是,则撤销偏向锁,并且进行轻量级锁获取

revoke_at_safepoint
void BiasedLocking::revoke_at_safepoint(Handle h_obj) {
  assert(SafepointSynchronize::is_at_safepoint(), "must only be called while at safepoint");
  oop obj = h_obj();
  //更新撤销偏向锁计数,并返回偏向锁撤销次数和偏向次数
  HeuristicsResult heuristics = update_heuristics(obj, false);
  if (heuristics == HR_SINGLE_REVOKE) {//可偏向且未达到批量处理的阈值(下面会单独解释)
    revoke_bias(obj, false, false, NULL); //撤销偏向锁
  } else if ((heuristics == HR_BULK_REBIAS) || 
             (heuristics == HR_BULK_REVOKE)) {//如果是多次撤销或者多次偏向
    //批量撤销
    bulk_revoke_or_rebias_at_safepoint(obj, (heuristics == HR_BULK_REBIAS), false, NULL);
  }
  clean_up_cached_monitor_info();
}

JVM内部为每个类维护了一个偏向锁revoke计数器,对偏向锁撤销进行计数,当这个值达到指定阈值时,JVM会认为这个类的偏向锁有问题,需要重新偏向(rebias),对所有属于这个类的对象进行重偏向的操作成为 批量重偏向(bulk rebias)。在做bulk rebias时,会对这个类的epoch的值做递增,这个epoch会存储在对象头中的epoch字段。在判断这个对象是否获得偏向锁的条件是:markword的 biased_lock:1、lock:01、threadid和当前线程id相等、epoch字段和所属类的epoch值相同,如果epoch的值不一样,要么就是撤销偏向锁、要么就是rebias; 如果这个类的revoke计数器的值继续增加到一个阈值,那么jvm会认为这个类不适合偏向锁,就需要进行bulk revoke操作

轻量级锁环节
slow_enter
void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {
  markOop mark = obj->mark();
  assert(!mark->has_bias_pattern(), "should not see bias pattern here");

  if (mark->is_neutral()) { //如果当前对象为无锁状态
    lock->set_displaced_header(mark); //复制一份lock record 包含 markword
    if (mark == obj()->cas_set_mark((markOop) lock, mark)) { // 进行CAS并将lock record放入栈帧
      TEVENT(slow_enter: release stacklock);
      return;
    }
  } else if (mark->has_locker() &&
             THREAD->is_lock_owned((address)mark->locker())) { //如果当前对象已经有锁,且锁对象为自己,则进行锁重入
    assert(lock != mark->locker(), "must not re-lock the same lock");
    assert(lock != (BasicLock*)obj->mark(), "don't relock with same BasicLock");
    lock->set_displaced_header(NULL); //复制一份 null 的 lock record 放入栈帧
    return;
  }

  lock->set_displaced_header(markOopDesc::unused_mark()); //将mark word后三位设置为 GC 回收标志
    //进入锁膨胀
  ObjectSynchronizer::inflate(THREAD,
                              obj(),
                              inflate_cause_monitor_enter)->enter(THREAD);
}

轻量级锁的释放过程

monitorexit->slow_exit->fast_exit,这里跳过前两个步骤直接展示fast_exit

fast_exit
void ObjectSynchronizer::fast_exit(oop object, BasicLock* lock, TRAPS) {
  markOop mark = object->mark();
  assert(mark == markOopDesc::INFLATING() ||
         !mark->has_bias_pattern(), "should not see bias pattern here");

  markOop dhw = lock->displaced_header();
    //如果为重入锁则什么都不做
  if (dhw == NULL) {
#ifndef PRODUCT
    if (mark != markOopDesc::INFLATING()) {
      assert(!mark->is_neutral(), "invariant");
      assert(!mark->has_locker() ||
             THREAD->is_lock_owned((address)mark->locker()), "invariant");
      if (mark->has_monitor()) {
        ObjectMonitor * m = mark->monitor();
        assert(((oop)(m->object()))->mark() == mark, "invariant");
        assert(m->is_entered(THREAD), "invariant");
      }
    }
#endif
    return;
  }
//如果markword == displaced mark word即轻量级锁,cas替换对象头
  if (mark == (markOop) lock) {
    assert(dhw->is_neutral(), "invariant");
    if (object->cas_set_mark(dhw, mark) == mark) {
      TEVENT(fast_exit: release stack-lock);
      return;
    }
  }

  ObjectSynchronizer::inflate(THREAD,
                              object,
                              inflate_cause_vm_internal)->exit(true, THREAD);
}
重量级锁环节
inflate
ObjectMonitor* ObjectSynchronizer::inflate(Thread * Self,
                                                     oop object,
                                                     const InflateCause cause) {


  assert(Universe::verify_in_progress() ||
         !SafepointSynchronize::is_at_safepoint(), "invariant");

  EventJavaMonitorInflate event;

  for (;;) {
    const markOop mark = object->mark();
    assert(!mark->has_bias_pattern(), "invariant");

    // The mark can be in one of the following states:
    // *  Inflated     - just return
    // *  Stack-locked - coerce it to inflated
    // *  INFLATING    - busy wait for conversion to complete
    // *  Neutral      - aggressively inflate the object.
    // *  BIASED       - Illegal.  We should never see this

      
      //如果改对象已经为重量级锁,则直接返回
    if (mark->has_monitor()) {
      ObjectMonitor * inf = mark->monitor();
      assert(inf->header()->is_neutral(), "invariant");
      assert(inf->object() == object, "invariant");
      assert(ObjectSynchronizer::verify_objmon_isinpool(inf), "monitor is invalid");
      return inf;
    }

      //如果改对象正在膨胀,则自旋等待
    if (mark == markOopDesc::INFLATING()) {
      TEVENT(Inflate: spin while INFLATING);
        //该方法中进行自旋
      ReadStableMark(object);
      continue;
    }

      //如果已经拥有锁,即轻量级锁状态,则初始化monitor
    if (mark->has_locker()) {
        //创建monitor
      ObjectMonitor * m = omAlloc(Self);

      m->Recycle();
      m->_Responsible  = NULL;
        //设置重入次数为0
      m->_recursions   = 0;
      m->_SpinDuration = ObjectMonitor::Knob_SpinLimit;   // Consider: maintain by type/class

        //将mark头设置为正在膨胀,INFLAGTING(0)
      markOop cmp = object->cas_set_mark(markOopDesc::INFLATING(), mark);
      if (cmp != mark) {
        omRelease(Self, m, true);
        continue;       // Interference -- just retry
      }

        //栈中的displaced_mark_word
      markOop dmw = mark->displaced_mark_helper();
      assert(dmw->is_neutral(), "invariant");
        //设置monitor字段,把对象markword信息保存在 monitor中
      m->set_header(dmw);
        //把mark中的线程lock record设置为owner,所以这里owner指向的是线程中的lock record或者 线程本身
      m->set_owner(mark->locker());
        //设置 monitor对象为加锁对象
      m->set_object(object);

      guarantee(object->mark() == markOopDesc::INFLATING(), "invariant");
        //将锁对象设置为重量级锁
      object->release_set_mark(markOopDesc::encode(m));

      OM_PERFDATA_OP(Inflations, inc());
      TEVENT(Inflate: overwrite stacklock);
      if (log_is_enabled(Debug, monitorinflation)) {
        if (object->is_instance()) {
          ResourceMark rm;
          log_debug(monitorinflation)("Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s",
                                      p2i(object), p2i(object->mark()),
                                      object->klass()->external_name());
        }
      }
      if (event.should_commit()) {
        post_monitor_inflate_event(event, object, cause);
      }
      return m;
    }
      //无状态锁,也是直接初始化锁,进行锁膨胀

    assert(mark->is_neutral(), "invariant");
    ObjectMonitor * m = omAlloc(Self);
    m->Recycle();
    m->set_header(mark);
    m->set_owner(NULL);
    m->set_object(object);
    m->_recursions   = 0;
    m->_Responsible  = NULL;
    m->_SpinDuration = ObjectMonitor::Knob_SpinLimit;       // consider: keep metastats by type/class

      //将锁改为重量级锁,如果失败说明有另一个线程再inflate,则释放monitor
    if (object->cas_set_mark(markOopDesc::encode(m), mark) != mark) {
      m->set_object(NULL);
      m->set_owner(NULL);
      m->Recycle();
      omRelease(Self, m, true);
      m = NULL;
      continue;
 
    OM_PERFDATA_OP(Inflations, inc());
    TEVENT(Inflate: overwrite neutral);
    if (log_is_enabled(Debug, monitorinflation)) {
      if (object->is_instance()) {
        ResourceMark rm;
        log_debug(monitorinflation)("Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s",
                                    p2i(object), p2i(object->mark()),
                                    object->klass()->external_name());
      }
    }
    if (event.should_commit()) {
      post_monitor_inflate_event(event, object, cause);
    }
    return m;
  }
}

inflate中是一个for循环,主要是为了处理多线程同时调用inflate的情况。然后会根据锁对象的状态进行不同的处理:

1.已经是重量级状态,说明膨胀已经完成,直接返回

2.如果是轻量级锁则需要进行膨胀操作

3.如果是膨胀中状态,则进行忙等待

4.如果是无锁状态则需要进行膨胀操作5 wait notify

轻量级锁膨胀

1,调用omAlloc分配一个monitor对象在omAlloc方法中会先从线程私有的monitor集合omFreeList中分配对象,如果omFreeList中已经没有monitor对象,则从JVM全局的gFreeList中分配一批monitoromFreeList中。

2,初始化monitor对象

3,将状态设置为INFLATING

4,header字段为displaced mark word,owner字段为lock record,obj字段为锁对象

5,设置为重量级锁,指向第一步分配的monitor对象

无锁状态膨胀

1.调用omAlloc分配一个ObjectMonitor对象(以下简称monitor)

2.初始化monitor对象

3.设置monitor的header字段为 mark word,owner字段为null,obj字段为锁对象

4.设置锁对象头的mark word为重量级锁状态,指向第一步分配的monitor对象

INFLATING存在的原因

1,延迟拥有锁的线程A,释放锁的时间,因为将锁状态设置为INFLATING时,线程A会解锁失败,此时变化膨胀进入INFLATING自旋

2,防止hashcode闪烁(不太明白)

当锁膨胀完后,进入Enter环节

Enter
void ObjectMonitor::enter(TRAPS) {
  Thread * const Self = THREAD;

    //如果当前锁没有人获取,则尝试获取锁
  void * cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL);
  if (cur == NULL) {
    assert(_recursions == 0, "invariant");
    assert(_owner == Self, "invariant");
    return;
  }

    //锁重入,次数+1
  if (cur == Self) {
    _recursions++;
    return;
  }

    //如果被加锁之前为轻量级锁,且为轻量级锁第一次锁膨胀进入enter方法
  if (Self->is_lock_owned ((address)cur)) {
    assert(_recursions == 0, "internal state error");
      //锁重入+1
    _recursions = 1;
    _owner = Self;
    return;
  }

  assert(Self->_Stalled == 0, "invariant");
  Self->_Stalled = intptr_t(this);
    
    //再调用系统同步操作前,先尝试自旋,自旋成功返回
  if (Knob_SpinEarly && TrySpin (Self) > 0) {
    assert(_owner == Self, "invariant");
    assert(_recursions == 0, "invariant");
    assert(((oop)(object()))->mark() == markOopDesc::encode(this), "invariant");
    Self->_Stalled = 0;
    return;
  }

  assert(_owner != Self, "invariant");
  assert(_succ != Self, "invariant");
  assert(Self->is_Java_thread(), "invariant");
  JavaThread * jt = (JavaThread *) Self;
  assert(!SafepointSynchronize::is_at_safepoint(), "invariant");
  assert(jt->thread_state() != _thread_blocked, "invariant");
  assert(this->object() != NULL, "invariant");
  assert(_count >= 0, "invariant");

  Atomic::inc(&_count);

  EventJavaMonitorEnter event;

  { 
  //....
      

    for (;;) {
        //在该方法中调用系统同步方法
      jt->set_suspend_equivalent();
        //进入EnterI环节
      EnterI(THREAD);

      if (!ExitSuspendEquivalent(jt)) break;
      _recursions = 0;
      _succ = NULL;
      exit(false, Self);

      jt->java_suspend_self();
    }
    Self->set_current_pending_monitor(NULL);
  }
  //....
}
EnterI

EnterI的原理和ReentrantLock的原理及其相似

一个ObjectMonitor对象包括这么几个关键字段:cxq(下图中的ContentionList),EntryListWaitSetowner

其中cxq ,EntryList ,WaitSet都是由ObjectWaiter的链表结构,owner指向持有锁的线程。

**当线程释放锁时,**会从cxq或EntryList中挑选一个线程唤醒,被选中的线程叫做Heir presumptive即假定继承人_succ,就是图中的Ready Thread,假定继承人被唤醒后会尝试获得锁,但synchronized是非公平的,所以假定继承人不一定能获得锁(这也是它叫"假定"继承人的原因)

void ObjectMonitor::EnterI(TRAPS) {
  Thread * const Self = THREAD;
  assert(Self->is_Java_thread(), "invariant");
  assert(((JavaThread *) Self)->thread_state() == _thread_blocked, "invariant");

    //尝试获取锁,获取锁成功则返回
  if (TryLock (Self) > 0) {
    assert(_succ != Self, "invariant");
    assert(_owner == Self, "invariant");
    assert(_Responsible != Self, "invariant");
    return;
  }

  DeferredInitialize();

    //尝试自旋,自旋获取锁成功则返回
  if (TrySpin (Self) > 0) {
    assert(_owner == Self, "invariant");
    assert(_succ != Self, "invariant");
    assert(_Responsible != Self, "invariant");
    return;
  }
.
  assert(_succ != Self, "invariant");
  assert(_owner != Self, "invariant");
  assert(_Responsible != Self, "invariant");

    //将线程封装成ObjectWaiter,插入到_cxq中
  ObjectWaiter node(Self);
  Self->_ParkEvent->reset();
  node._prev   = (ObjectWaiter *) 0xBAD;
  node.TState  = ObjectWaiter::TS_CXQ;
cg
  ObjectWaiter * nxt;
  for (;;) {
      //先尝试将node插入到_cxq头部,如果未成功,则CAS一遍,减少插入次数和数量
    node._next = nxt = _cxq;
    if (Atomic::cmpxchg_ptr(&node, &_cxq, nxt) == nxt) break;
    if (TryLock (Self) > 0) {
      assert(_succ != Self, "invariant");
      assert(_owner == Self, "invariant");
      assert(_Responsible != Self, "invariant");
      return;
    }
  }

  //判断是否还有线程在等待,若没有则将_Responsible设置未自己
  if ((SyncFlags & 16) == 0 && nxt == NULL && _EntryList == NULL) {
    Atomic::cmpxchg_ptr(Self, &_Responsible, NULL);
  }
  TEVENT(Inflated enter - Contention);
  int nWakeups = 0;
  int recheckInterval = 1;

  for (;;) {

    if (TryLock(Self) > 0) break;
    assert(_owner != Self, "invariant");

      //如果_Responsible==Null则设置为自己
    if ((SyncFlags & 2) && _Responsible == NULL) {
      Atomic::cmpxchg_ptr(Self, &_Responsible, NULL);
    }

      //如果_Responsible为自己 则等待一定时间间隔
    if (_Responsible == Self || (SyncFlags & 1)) {
      TEVENT(Inflated enter - park TIMED);
      Self->_ParkEvent->park((jlong) recheckInterval);
      recheckInterval *= 8;
      if (recheckInterval > MAX_RECHECK_INTERVAL) {
        recheckInterval = MAX_RECHECK_INTERVAL;
      }
        //如果_Responsible不为自己,则直接park
    } else {
      TEVENT(Inflated enter - park UNTIMED);
      Self->_ParkEvent->park();
    }

      //尝试获取锁
    if (TryLock(Self) > 0) break;

    TEVENT(Inflated enter - Futile wakeup);
    OM_PERFDATA_OP(FutileWakeups, inc());
    ++nWakeups;

      //尝试自旋获取锁
    if ((Knob_SpinAfterFutile & 1) && TrySpin(Self) > 0) break;


    if ((Knob_ResetEvent & 1) && Self->_ParkEvent->fired()) {
      Self->_ParkEvent->reset();
      OrderAccess::fence();
    }
      //这一轮还没走出去则取消假定候选人资格,洗牌重新来
    if (_succ == Self) _succ = NULL;
    OrderAccess::fence();
  }

    //走出该循环代表已经获取锁
  assert(_owner == Self, "invariant");
  assert(object() != NULL, "invariant");

    // 将当前线程的node从cxq或EntryList中移除
  UnlinkAfterAcquire(Self, &node);
  if (_succ == Self) _succ = NULL;

  assert(_succ != Self, "invariant");
  if (_Responsible == Self) {
    _Responsible = NULL;
    OrderAccess::fence(); // Dekker pivot-point

  }

  if (SyncFlags & 8) {
    OrderAccess::fence();
  }
  return;
}

1,尝试获取锁和自旋

2,线程封装添加到_cxq队首,该过程也会尝试获取锁

3,进入循环park,中途也会尝试获取锁和自旋

4,被唤醒后,再次尝试获取锁

5,如果获取锁,则将node从_cxq或者entryList中移除,情况自己所在的 Responsible和 succ

注:

当竞争发生时,选取一个线程作为_Responsible_Responsible线程调用的是有时间限制的park方法,其目的是防止出现搁浅现象。

Exit
void ObjectMonitor::exit(bool not_suspended, TRAPS) {
  Thread * const Self = THREAD;
    
    //如果此时该线程不为持有锁的线程 会有以下两种情况
  if (THREAD != _owner) {
      //因为自己是轻量级锁,锁膨胀后还没来得及 Enter 导致线程没切换过来
    if (THREAD->is_lock_owned((address) _owner)) {
      assert(_recursions == 0, "invariant");
      _owner = THREAD;
      _recursions = 0;
    } else {
        //真的不是自己,就马上离开
      TEVENT(Exit - Throw IMSX);
      assert(false, "Non-balanced monitor enter/exit! Likely JNI locking");
      return;
    }
  }

    //锁重入情况,recursion--
  if (_recursions != 0) {
    _recursions--;        // this is simple recursive enter
    TEVENT(Inflated exit - recursive);
    return;
  }
    //_Responsible设置为Null
  if ((SyncFlags & 4) == 0) {
    _Responsible = NULL;
  }

#if INCLUDE_TRACE
  if (not_suspended && Tracing::is_event_enabled(TraceJavaMonitorEnterEvent)) {
    _previous_owner_tid = THREAD_TRACE_ID(Self);
  }
#endif

  for (;;) {
    assert(THREAD == _owner, "invariant");
    if (Knob_ExitPolicy == 0) {
        //code 1:先释放锁,非公平锁的优化,此时如果有线程进入可以直接获取锁
       OrderAccess::release_store_ptr(&_owner, NULL);  
      OrderAccess::storeload();
        //code 2: 如果没有等待的线程,或者已经有假定候选人了直接开溜
      if ((intptr_t(_EntryList)|intptr_t(_cxq)) == 0 || _succ != NULL) {
        TEVENT(Inflated exit - simple egress);
        return;
      }
        //此时进行后续步骤需要重新获取锁,如果获取锁失败则说明已经有外界线程占用锁,则退出
      if (Atomic::cmpxchg_ptr (THREAD, &_owner, NULL) != NULL) {
        return;
      }
      TEVENT(Exit - Reacquired);
    } else {
      if ((intptr_t(_EntryList)|intptr_t(_cxq)) == 0 || _succ != NULL) {
        OrderAccess::release_store_ptr(&_owner, NULL);   // drop the lock
        OrderAccess::storeload();
        if (_cxq == NULL || _succ != NULL) {
          TEVENT(Inflated exit - simple egress);
          return;
        }
        if (Atomic::cmpxchg_ptr (THREAD, &_owner, NULL) != NULL) {
          TEVENT(Inflated exit - reacquired succeeded);
          return;
        }
        TEVENT(Inflated exit - reacquired failed);
      } else {
        TEVENT(Inflated exit - complex egress);
      }
    }

    guarantee(_owner == THREAD, "invariant");

    ObjectWaiter * w = NULL;
    int QMode = Knob_QMode;
     // code 4:根据QMode的不同会有不同的唤醒策略,默认为0
    if (QMode == 2 && _cxq != NULL) {
          // QMode == 2 : cxq中的线程有更高优先级,直接唤醒cxq的队首线程
      w = _cxq;
      assert(w != NULL, "invariant");
      assert(w->TState == ObjectWaiter::TS_CXQ, "Invariant");
      ExitEpilog(Self, w);
      return;
    }

    if (QMode == 3 && _cxq != NULL) {
        // 将cxq中的元素插入到EntryList的末尾
      w = _cxq;
      for (;;) {
        assert(w != NULL, "Invariant");
        ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr(NULL, &_cxq, w);
        if (u == w) break;
        w = u;
      }
      assert(w != NULL, "invariant");

      ObjectWaiter * q = NULL;
      ObjectWaiter * p;
      for (p = w; p != NULL; p = p->_next) {
        guarantee(p->TState == ObjectWaiter::TS_CXQ, "Invariant");
        p->TState = ObjectWaiter::TS_ENTER;
        p->_prev = q;
        q = p;
      }

      ObjectWaiter * Tail;
      for (Tail = _EntryList; Tail != NULL && Tail->_next != NULL;
           Tail = Tail->_next)
      if (Tail == NULL) {
        _EntryList = w;
      } else {
        Tail->_next = w;
        w->_prev = Tail;
      }

    }

    if (QMode == 4 && _cxq != NULL) {
        // 将cxq插入到EntryList的队首
      w = _cxq;
      for (;;) {
        assert(w != NULL, "Invariant");
        ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr(NULL, &_cxq, w);
        if (u == w) break;
        w = u;
      }
      assert(w != NULL, "invariant");

      ObjectWaiter * q = NULL;
      ObjectWaiter * p;
      for (p = w; p != NULL; p = p->_next) {
        guarantee(p->TState == ObjectWaiter::TS_CXQ, "Invariant");
        p->TState = ObjectWaiter::TS_ENTER;
        p->_prev = q;
        q = p;
      }

      if (_EntryList != NULL) {
        q->_next = _EntryList;
        _EntryList->_prev = q;
      }
      _EntryList = w;

    }

    w = _EntryList;
    if (w != NULL) {
        //如果EntryList不为空则唤醒第一个元素
      assert(w->TState == ObjectWaiter::TS_ENTER, "invariant");
      ExitEpilog(Self, w);
      return;
    }

    w = _cxq;
    if (w == NULL) continue;
    for (;;) {
      assert(w != NULL, "Invariant");
      ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr(NULL, &_cxq, w);
      if (u == w) break;
      w = u;
    }
    TEVENT(Inflated exit - drain cxq into EntryList);

    assert(w != NULL, "invariant");
    assert(_EntryList == NULL, "invariant");
    if (QMode == 1) {
     // QMode == 1 : 将cxq中的元素转移到EntryList,并反转顺序
      ObjectWaiter * s = NULL;
      ObjectWaiter * t = w;
      ObjectWaiter * u = NULL;
      while (t != NULL) {
        guarantee(t->TState == ObjectWaiter::TS_CXQ, "invariant");
        t->TState = ObjectWaiter::TS_ENTER;
        u = t->_next;
        t->_prev = u;
        t->_next = s;
        s = t;
        t = u;
      }
      _EntryList  = s;
      assert(s != NULL, "invariant");
    } else {
      // QMode == 0 or QMode == 2
         // 将cxq中的元素转移到EntryList
      _EntryList = w;
      ObjectWaiter * q = NULL;
      ObjectWaiter * p;
      for (p = w; p != NULL; p = p->_next) {
        guarantee(p->TState == ObjectWaiter::TS_CXQ, "Invariant");
        p->TState = ObjectWaiter::TS_ENTER;
        p->_prev = q;
        q = p;
      }
    }
      
      // _succ不为null,说明已经有个继承人了,所以不需要当前线程去唤醒,减少上下文切换的比率
    if (_succ != NULL) continue;

      
    w = _EntryList;
       // 唤醒EntryList第一个元素
    if (w != NULL) {
      guarantee(w->TState == ObjectWaiter::TS_ENTER, "invariant");
      ExitEpilog(Self, w);
      return;
    }
  }
}

1,会先进行锁重入,锁膨胀一些判断,如果 recursion为0 且 为持锁线程 则进行以下步骤

2,先释放锁,给外界线程获取同步代码块的机会

3,如果此时_cxq为空 或者 已经有succ 了 则退出

4,如果此时_cxq不为空,且没有succ 则会重新获取锁(对 cxq和entryList操作)

5,根据QMode的不同,会执行不同的唤醒策略;

根据QMode的不同,有不同的处理方式:

  1. QMode = 2且cxq非空:取cxq队列队首的ObjectWaiter对象,调用ExitEpilog方法,该方法会唤醒ObjectWaiter对象的线程,然后立即返回,后面的代码不会执行了;
  2. QMode = 3且cxq非空:把cxq队列插入到EntryList的尾部;
  3. QMode = 4且cxq非空:把cxq队列插入到EntryList的头部;
  4. QMode = 0:暂时什么都不做,继续往下看;
    只有QMode=2的时候会提前返回,等于0、3、4的时候都会继续往下执行:

1.如果EntryList的首元素非空,就取出来调用ExitEpilog方法,该方法会唤醒ObjectWaiter对象的线程,然后立即返回;
2.如果EntryList的首元素为空,就将cxq的所有元素放入到EntryList中,然后再从EntryList中取出来队首元素执行ExitEpilog方法,然后立即返回;

5.1 原理之 wait / notify

image-20220324201947719

  • Owner线程方法自身条件不足时,调用wait方法,即可进入WaitSet变为WAITING状态
  • BLOCKED和WAITING的线程都处于阻塞状态,不占用CPU
  • BLOCKED线程会在Owner线程运行完时,给唤醒
  • WAITING线程会在Owner线程调用notify或者notifyAll时唤醒,但唤醒并不意味着立刻获得锁,仍需进入EntryList进行竞争
API介绍(只能在同步代码块使用)
  • obj.wait()让进入object监视器的线程到waitSet等待
  • obj.notify()在object上正在waitSet等待的线程中挑一个唤醒
  • obj.notifyAll() 让object上正在waitSet等待的线程全部唤醒

注:不管什么锁,如果对锁对象调用wait()方法,会直接使锁状态变为Fat Lock

public static void main(String[] args)throws InterruptedException{
    Thread t1 = new Thread(()->{
        synchronized (lock){
            System.out.println(ClassLayout.parseInstance(lock).toPrintable());
            try {
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });
    t1.start();
    Thread.sleep(1000);
    System.out.println(ClassLayout.parseInstance(lock).toPrintable());
}
java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   4        (object header: mark)     0x16324c0d (biased: 0x000b1926; epoch: 0; age: 1)
  4   4        (object header: class)    0x15691118
Instance size: 8 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   4        (object header: mark)     0x15b0ad0e (fat lock: 0x15b0ad0e)
  4   4        (object header: class)    0x15691118
Instance size: 8 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

为什么 wait(), notify()和 notifyAll()必须在同步方法或者同步块中被调用?

当一个线程需要调用对象的 wait()方法的时候,这个线程必须拥有该对象的锁,接着它就会释放这个对象锁并进入等待状态直到其他线程调用这个对象上的 notify()方法。同样的,当一个线程需要调用对象的 notify()方法时,它会释放这个对象的锁,以便其他在等待的线程就可以得到这个对象锁。由于所有的这些方法都需要线程持有对象的锁,这样就只能通过同步来实现,所以他们只能在同步方法或者同步块中被调用。

wait(long timeout):
  • **timeout ==0 : **无限制等待
  • timeout > 0: 在规定时间进行等待
wait(long timeout,int nanos): 虽说是纳秒实际上是假纳秒
public final void wait(long timeout, int nanos) throws InterruptedException {
    if (timeout < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (nanos < 0 || nanos > 999999) {
        throw new IllegalArgumentException(
                            "nanosecond timeout value out of range");
    }

    if (nanos > 0) {
        timeout++;
    }

    wait(timeout);
}

5.2 wait notify的正确姿势

sleep() 和 wait() 的区别
  1. sleep是Thread方法,wait是Object方法
  2. sleep不会释放锁,而wait会释放锁对象
  3. sleep无需强制和synchronized使用,但是wait需要和synchronized使用
  4. wait会使锁强制升级为fat lock,sleep不会
共同点:
  • 都是进入TIME_WAITING
step1:
 public static void step2(){
        new Thread(()->{
            synchronized (room){
                log.debug("开始工作");
                while (!cig){
                    log.debug("有烟没?[{}]",cig);
                    if(!cig) {
                        log.debug("没有烟,开摆!");
                        try {
                            Thread.sleep(2000);
//                        room.wait();
                        }catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    if(cig){
                        log.debug("有烟了,开冲!");
                    }
                }
            }
        },"烟鬼").start();

        for (int i = 0; i <5 ; i++) {
            new Thread(()->{
                synchronized (room){
                    log.debug("开始工作了");
                }
            },"其他人"+i).start();
        }

        Sleeper.sleep(1);
        new Thread(()->{
//            synchronized (room){
                cig=!cig;
                log.debug("烟来咯!");
//                room.notify();
//            }
        },"烟贩子").start();
    }
00:12:35.351 [烟鬼] DEBUG c.Wait - 开始工作
00:12:35.358 [烟鬼] DEBUG c.Wait - 有烟没?[false]
00:12:35.359 [烟鬼] DEBUG c.Wait - 没有烟,开摆!
00:12:36.362 [烟贩子] DEBUG c.Wait - 烟来咯!
00:12:37.372 [烟鬼] DEBUG c.Wait - 有烟了,开冲!
00:12:37.372 [其他人4] DEBUG c.Wait - 开始工作了
00:12:37.372 [其他人3] DEBUG c.Wait - 开始工作了
00:12:37.372 [其他人2] DEBUG c.Wait - 开始工作了
00:12:37.372 [其他人1] DEBUG c.Wait - 开始工作了
00:12:37.372 [其他人0] DEBUG c.Wait - 开始工作了
  • 可以看出上述代码使用sleep的话,会导致烟鬼一直摆烂占用线程,导致正常人无法工作,效率变低
  • 而且烟贩子不能加入到同步代码块中,否则也会阻塞。
  • 所以这里我们使用wait-notify来优化该程序
public static void step2(){
    new Thread(()->{
        synchronized (room){
            log.debug("开始工作");
            if(!cig) {
                log.debug("没有烟,开摆!");
                try {
                    room.wait();
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            if(cig){
                log.debug("有烟了,开冲!");
            }
        }
    },"烟鬼").start();

    Sleeper.sleep(1);

    for (int i = 0; i <5 ; i++) {
        new Thread(()->{
            synchronized (room){
                log.debug("开始工作了");
            }
        },"其他人"+i).start();
    }

    Sleeper.sleep(2);
    new Thread(()->{
        synchronized (room){
            cig=!cig;
            log.debug("烟来咯!");
            room.notify();
        }
    },"烟贩子").start();
}
00:06:17.739 [烟鬼] DEBUG c.Wait - 开始工作
00:06:17.744 [烟鬼] DEBUG c.Wait - 没有烟,开摆!
00:06:18.738 [其他人0] DEBUG c.Wait - 开始工作了
00:06:18.739 [其他人1] DEBUG c.Wait - 开始工作了
00:06:18.739 [其他人2] DEBUG c.Wait - 开始工作了
00:06:18.739 [其他人3] DEBUG c.Wait - 开始工作了
00:06:18.739 [其他人4] DEBUG c.Wait - 开始工作了
00:06:20.741 [烟贩子] DEBUG c.Wait - 烟来咯!
00:06:20.741 [烟鬼] DEBUG c.Wait - 有烟了,开冲!
  • 这样子就可以很好的解决烟鬼等烟的问题,让烟鬼把锁让出去,供其他线程使用
但是上述方法还是存在问题:如果这时候,不只有一个线程在等待条件,而是有多个线程正在等待条件,这时候notify会产生虚假唤醒的现象,也就是将当前条件不符合的线程给唤醒。
  • 如果使用notifyAll()可以解决问题吗?

不可以解决,但是会把所有线程唤醒,不符合该条件的线程都会进入阻塞队列导致,其他线程的虚假唤醒

  • 使用while来时刻检查+notifyAll()可以解决吗?

可以解决

同步模式之保护性暂停

定义

用在一个线程等待另一个线程的执行结果

要点
  • 有一个结果需要从一个线程传递到另一个线程,让他们关联同一个Object
  • 如果有结果源源不断的传输,则使用消息队列
  • JDK中,join和FutureTask的实现,采用的就是此模式‘
  • 因为要等待另一方的结果所以归为同步模式

image-20220326142527922

实现

GuardedObject类实现
class GuardedObject{
    Object response;
    public Object get(){
        while (response==null){
            synchronized (this){
                try {
                    this.wait();
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        }
        return response;
    }

    public void complete(Object response){
        synchronized (this){
            this.response = response;
            this.notify();
        }
    }
}
保护性暂停模式实现
public class securityStop {
    public static void main(String[] args) {
        GuardedObject guardedObject = new GuardedObject();
        new Thread(()->{
           log.debug("等待结果");
           List<String> list = (List)guardedObject.get();
            System.out.println(list);
        },"t1").start();
        new Thread(()->{
            log.debug("下载内容");
            List<String> list = new ArrayList<>();
            try {
             list =  Downloader.download();
            }catch (IOException e){
                e.printStackTrace();
            }
           guardedObject.complete(list);
            log.debug("下载完毕");
        },"t2").start();
    }
}
保护性暂停模式优化

如果下载时间特别长,就会导致等待线程一直等待,要给他设置一个等待时长

 public Object get(long timeOut){
        synchronized (this){
            long startTime = System.currentTimeMillis();
            long passTime = 0;
            while (response==null){
                if(passTime >= timeOut){
                    log.debug("等待超时");
                    break;
                }
                try {
                    this.wait(timeOut - passTime); // 防止虚假唤醒情况
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                passTime = System.currentTimeMillis()-startTime;
            }
        }
        return response;
    }
保护性暂停模式优化2

使用Futures方法 一一对应
image-20220326153814167

futureGuardedObject
class futureGuardedObject{
    private int id;
    private Object response;

    public futureGuardedObject(int id){
        this.id = id;
    }

    public int getId(){
        return id;
    }

    public Object get(long timeOut){
        synchronized (this){
            long startTime = System.currentTimeMillis();
            long passTime = 0;
            while (response==null){
                if(passTime >= timeOut){
                    log.debug("等待超时");
                    break;
                }
                try {
                    this.wait(timeOut - passTime); // 防止虚假唤醒情况
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                passTime = System.currentTimeMillis()-startTime;
            }
        }
        return response;
    }

    public void complete(Object response){
        synchronized (this){
            this.response = response;
            this.notify();
        }
    }

}
MailBox
class Mailboxes{
    private static Map<Integer,futureGuardedObject> boxes = new Hashtable<>();
    private static int id = 1;

    private static synchronized int generateId(){
        return id++;
    }

    public static futureGuardedObject createGuardedObject(){
        futureGuardedObject go = new futureGuardedObject(generateId());
        boxes.put(go.getId(),go);
        return go;
    }

    public static futureGuardedObject getGuradObject(int id){
        return boxes.remove(id);
    }

    public static Set<Integer> getIds(){
        return boxes.keySet();
    }
}
Person
class Person extends Thread{
   @Override
   public void run(){
       futureGuardedObject go = Mailboxes.createGuardedObject();
       log.debug("开始收信 id{}",go.getId());
       Object mail = go.get(5000);
       log.debug("收到信 id{} 内容{}",go.getId(),mail);
   }
}
PostMan
class PostMan extends Thread{
    private int id;
    private String mail;

    public PostMan(String mail,int id){
        this.mail = mail;
        this.id = id;
    }

    @Override
    public void run(){
        log.debug("开始送信 id{} 内容{}",id,mail);
       futureGuardedObject go = Mailboxes.getGuradObject(this.id);
        Sleeper.sleep(2);
       go.complete(mail);
    }
}
运行测试
public class futureSecurityStop {
    public static void main(String[] args) {
        for (int i = 0; i <3 ; i++) {
            new Person().start();
        }
        Sleeper.sleep(1);
        Set<Integer> set = Mailboxes.getIds();
        for (Integer id : set) {
            new PostMan("信封"+id,id).start();
        }
    }
}
16:30:44.541 [Thread-0] DEBUG People - 开始收信 id1
16:30:44.541 [Thread-2] DEBUG People - 开始收信 id2
16:30:44.541 [Thread-1] DEBUG People - 开始收信 id3
16:30:45.554 [Thread-3] DEBUG PostMan - 开始送信 id3 内容信封3
16:30:45.554 [Thread-4] DEBUG PostMan - 开始送信 id2 内容信封2
16:30:45.554 [Thread-5] DEBUG PostMan - 开始送信 id1 内容信封1
16:30:47.562 [Thread-1] DEBUG People - 收到信 id3 内容信封3
16:30:47.562 [Thread-2] DEBUG People - 收到信 id2 内容信封2
16:30:47.562 [Thread-0] DEBUG People - 收到信 id1 内容信封1

异步模式之生产者/消费者

定义

要点
  • 不需要像GuardedObject一样,每有一个生产者就对应一个消费者,去一一对应
  • 生产者无需关心数据处理,只需要负责生产就行,消费者专心处理生产者产生的数据
  • 消息队列容量是有限度的,当容量满了的时候,就不会在往里面添加数据,当容量空了的时候,消费者也不会继续消耗数据
  • JDK各种阻塞队列采用的就是这种模式

image-20220326163817796

**注:**异步是因为生产者的消息不会立刻被处理

实现
Message
class Message{
    private int id;
    private Object value;

    public Message(int id, Object value) {
        this.id = id;
        this.value = value;
    }

    public int getId() {
        return id;
    }

    public Object getValue() {
        return value;
    }

    @Override
    public String toString() {
        return "Message{" +
                "id=" + id +
                ", value=" + value +
                '}';
    }
}
MessageQueue
class MessageQueue{
      private LinkedList<Message> list = new LinkedList<>();
      private int capcity;

      public MessageQueue(int capcity){
          this.capcity = capcity;
      }

      public Message take(){
         synchronized (list){
             while(list.isEmpty()){
                 try {
                     log.debug("队列为空,暂无需要处理的内容");
                     list.wait();
                 }catch (InterruptedException e){
                     e.printStackTrace();
                 }
             }
             Message message = list.removeFirst();
             list.notifyAll();
             return message;
         }
      }

      public void set(Message message){
          synchronized (list){
              while (list.size() == capcity){
                  try {
                      log.debug("队列已满,请稍后");
                      list.wait();
                  }catch (InterruptedException e){
                      e.printStackTrace();
                  }
              }
              log.debug("已经添加 产品 id{} 内容 {}",message.getId(),message.getValue());
              list.addLast(message);
              list.notifyAll();
}
      }
}
运行测试
public class ProducersAndConsumers {
    public static void main(String[] args) {
        MessageQueue messageQueue = new MessageQueue(3);
        for (int i = 0; i <5 ; i++) {
            int temp = i;
            new Thread(()->{
                log.debug("生产者{} 开始生产",temp);
                Message message = new Message(temp,"学习资料"+temp);
                Sleeper.sleep(2);
                messageQueue.set(message);
            },"生产者"+temp).start();
        }

        new Thread(()->{
            while (true){
                Message message =  messageQueue.take();
                log.debug("已经处理 产品 id{} 内容 {}",message.getId(),message.getValue());
            }
        },"消费者").start();
    }
}
17:11:14.080 [消费者] DEBUG MessageQueue - 队列为空,暂无需要处理的内容
17:11:14.080 [生产者3] DEBUG main - 生产者3 开始生产
17:11:14.080 [生产者4] DEBUG main - 生产者4 开始生产
17:11:14.080 [生产者1] DEBUG main - 生产者1 开始生产
17:11:14.080 [生产者2] DEBUG main - 生产者2 开始生产
17:11:14.080 [生产者0] DEBUG main - 生产者0 开始生产
17:11:16.100 [生产者2] DEBUG MessageQueue - 已经添加 产品 id2 内容 学习资料2
17:11:16.100 [消费者] DEBUG main - 已经处理 产品 id2 内容 学习资料2
17:11:16.100 [生产者1] DEBUG MessageQueue - 已经添加 产品 id1 内容 学习资料1
17:11:16.100 [生产者4] DEBUG MessageQueue - 已经添加 产品 id4 内容 学习资料4
17:11:16.100 [生产者3] DEBUG MessageQueue - 已经添加 产品 id3 内容 学习资料3
17:11:16.101 [生产者0] DEBUG MessageQueue - 队列已满,请稍后
17:11:16.101 [消费者] DEBUG main - 已经处理 产品 id1 内容 学习资料1
17:11:16.101 [生产者0] DEBUG MessageQueue - 已经添加 产品 id0 内容 学习资料0
17:11:16.101 [消费者] DEBUG main - 已经处理 产品 id4 内容 学习资料4
17:11:16.101 [消费者] DEBUG main - 已经处理 产品 id3 内容 学习资料3
17:11:16.101 [消费者] DEBUG main - 已经处理 产品 id0 内容 学习资料0
17:11:16.101 [消费者] DEBUG MessageQueue - 队列为空,暂无需要处理的内容

6 线程切换

6.1 重新理解线程状态转换

image-20220328204903731

情况1 NEW --> RUNNABLE
  • 调用Thread.start方法,由New–>Runnable
情况2 RUNNABLE <—> WAITING

t线程用synchronized(obj)获取对象后

  • 调用wait()方法,进入WAITING状态
  • 调用notify(),notifyAll(),interrupt()时
  • 竞争成功,从waiting转为runnable
  • 竞争失败,从waiting转为blocked
情况3 RUNNABLE <—> WAITING
  • 当前线程调用t.join()方法时,当前线程从RUNNABLE–>WAITING,当前线程在t对象的线程监视器上等待
  • 当t线程执行结束后,或者调用了当前线程的interrupt()时,当前线程从WAITING --> RUNNABLE
情况4 RUNNABLE <–> WAITING
  • 当前线程 LockSupport.park() 方法会让当前线程 从RUNNABLE --> WAITING

  • 调用 LockSupport.unpark() 方法 或者 调用了 interrupt方法 会使 WAITING --> RUNNABLE

情况5 RUNNABLE <–> TIMED_WAITING

t线程用synchronized(obj)获取对象后

  • 调用wait(time)方法,进入TIME_WAITING状态
  • 调用notify()notifyAll()interrupt(),或者wait时间耗尽时
  • 竞争成功,从waiting转为runnable
  • 竞争失败,从waiting转为blocked
情况6 RUNNABLE <–> TIMED_WAITING
  • t线程调用sleep(time),进入TIME_WAITING状态
  • 超时或者调用interrupt从RUNNABLE–>TIMED_WAITING
情况7 RUNNABLE <–> TIMED_WAITING
  • t线程调用LockSupport.park(time),进入TIME_WAITING状态

  • 超时或者调用interrupt从RUNNABLE–>TIMED_WAITING

情况8 RUNNABLE <–> TIMED_WAITING
  • t线程调用join(time),进入TIME_WAITING状态

  • 超时或者调用interrupt从RUNNABLE–>TIMED_WAITING

情况9 RUNNABLE <–> TERMINATED

线程执行完毕

8 同步模式之顺序控制

固定运行顺序

例题:要求线程B先输出再线程A输出

wait-notify的方法来先后输出
static boolean t2runned = false;
public static void main(String[] args) {
    Object lock = new Object();

    Thread A = new Thread(()->{
        synchronized (lock){
             while(!t2runned){
               try {
                  lock.wait();
               }catch (InterruptedException e){
                 e.printStackTrace();
               }
             }
            log.debug("1");
        }

    },"t1");

    Thread B = new Thread(()->{
         synchronized (lock){
             log.debug("2");
            t2runned = true;
            lock.notify();
         }
    },"t2");

    A.start();
    B.start();
}
park-unpark的方法来先后输出
public static void test2(){
    Thread t1 = new Thread(()->{
        LockSupport.park();
        log.debug("1");
    },"t1");

    Thread t2 = new Thread(()->{
        log.debug("2");
        LockSupport.unpark(t1);
    },"t2");

    t2.start();
    t1.start();
}
交替输出

例题:线程1 输出 a 5次,线程2 输出 b 5次, 线程3 输出 c 5次。现在要求输出abcabcabcabcabc怎么实现

wait-notify版本
@Slf4j
class WaitNotify{
    private int loopNum;

    private int flag;

    public WaitNotify(int loopNum,int flag) {
        this.loopNum = loopNum;
        this.flag = flag;
    }

    public void print(int index, int nextIndex, String word){
        for (int i = 0; i <loopNum ; i++) {
            synchronized (this){
                while(index!=flag){
                    try {
                        this.wait();
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
                log.debug(word);
                this.notifyAll();
                flag = nextIndex;
            }
        }
    }
}

public static void test1(){
        WaitNotify waitNotify = new WaitNotify(5,1);
        new Thread(()->{
            waitNotify.print(1,2,"a");
        },"t1").start();
        new Thread(()->{
            waitNotify.print(2,3,"b");
        },"t2").start();new Thread(()->{
            waitNotify.print(3,1,"c");
        },"t3").start();
    }
21:25:53.672 [t1] DEBUG JUC.model.WaitNotify - a
21:25:53.679 [t2] DEBUG JUC.model.WaitNotify - b
21:25:53.679 [t3] DEBUG JUC.model.WaitNotify - c
21:25:53.679 [t1] DEBUG JUC.model.WaitNotify - a
21:25:53.679 [t2] DEBUG JUC.model.WaitNotify - b
21:25:53.679 [t3] DEBUG JUC.model.WaitNotify - c
21:25:53.679 [t1] DEBUG JUC.model.WaitNotify - a
21:25:53.679 [t2] DEBUG JUC.model.WaitNotify - b
21:25:53.679 [t3] DEBUG JUC.model.WaitNotify - c
21:25:53.679 [t1] DEBUG JUC.model.WaitNotify - a
21:25:53.679 [t2] DEBUG JUC.model.WaitNotify - b
21:25:53.679 [t3] DEBUG JUC.model.WaitNotify - c
21:25:53.679 [t1] DEBUG JUC.model.WaitNotify - a
21:25:53.679 [t2] DEBUG JUC.model.WaitNotify - b
21:25:53.679 [t3] DEBUG JUC.model.WaitNotify - c
await-signal版本
@Slf4j
class AwaitSignal extends ReentrantLock{
    private int loopNumber;

    public AwaitSignal(int loopNumber){
        this.loopNumber = loopNumber;
    }

    public void print(Condition condition,Condition nextCondition,String word){
        for (int i = 0; i <loopNumber ; i++) {
            lock();
            try {
                try {
                    condition.await();
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                log.debug(word);
                nextCondition.signal();
            }finally {
                unlock();
            }
        }
    }
}

public static void test2(){
        AwaitSignal awaitSignal = new AwaitSignal(5);
        Condition condition1 = awaitSignal.newCondition();
        Condition condition2 = awaitSignal.newCondition();
        Condition condition3 = awaitSignal.newCondition();
        new Thread(()->{
            awaitSignal.print(condition1,condition2,"a");
        },"t1").start();
        new Thread(()->{
            awaitSignal.print(condition2,condition3,"b");
        },"t2").start();new Thread(()->{
            awaitSignal.print(condition3,condition1,"c");
        },"t3").start();
        Sleeper.sleep(1);
        awaitSignal.lock();
        condition1.signal();
        awaitSignal.unlock();
    }
21:48:25.989 [t1] DEBUG JUC.model.AwaitSignal - a
21:48:25.995 [t2] DEBUG JUC.model.AwaitSignal - b
21:48:25.995 [t3] DEBUG JUC.model.AwaitSignal - c
21:48:25.996 [t1] DEBUG JUC.model.AwaitSignal - a
21:48:25.996 [t2] DEBUG JUC.model.AwaitSignal - b
21:48:25.996 [t3] DEBUG JUC.model.AwaitSignal - c
21:48:25.996 [t1] DEBUG JUC.model.AwaitSignal - a
21:48:25.996 [t2] DEBUG JUC.model.AwaitSignal - b
21:48:25.996 [t3] DEBUG JUC.model.AwaitSignal - c
21:48:25.996 [t1] DEBUG JUC.model.AwaitSignal - a
21:48:25.996 [t2] DEBUG JUC.model.AwaitSignal - b
21:48:25.996 [t3] DEBUG JUC.model.AwaitSignal - c
21:48:25.996 [t1] DEBUG JUC.model.AwaitSignal - a
21:48:25.996 [t2] DEBUG JUC.model.AwaitSignal - b
21:48:25.997 [t3] DEBUG JUC.model.AwaitSignal - c

注:此处有bug,当a唤醒b后,此时a未进入condition进行等待,而是直接运行b,b再运行c,c唤醒a后,再次进入c然后进行等待,然后运行a 进行等待,就会使得a无人唤醒。

park-unpark版本
public static void test3(int n){
        ParkUnPark parkUnPark = new ParkUnPark(n);
        t1 = new Thread(()->{
            parkUnPark.print("a",t2);
        },"3-t1");
        t2 = new Thread(()->{
            parkUnPark.print("b",t3);
        },"3-t2");
        t3 = new Thread(()->{
            long start = System.currentTimeMillis();
            parkUnPark.print("c",t1);
            long end = System.currentTimeMillis();
            log.debug("ParkUnPark time{} ms",start-end);
        },"3-t3");
        t1.start();
        t2.start();
        t3.start();
        LockSupport.unpark(t1);

    }
}
class ParkUnPark{
    private int loopNumber;

    public void print(String word,Thread next){
        for (int i = 0; i <loopNumber ; i++) {
            LockSupport.park();
            //log.debug(word);
            LockSupport.unpark(next);
        }
    }

    public ParkUnPark(int loopNumber){
        this.loopNumber = loopNumber;
    }
}

三个版本运行50000次速度比较

01:38:15.238 [3-t3] DEBUG JUC.model.alternateControl - ParkUnPark time-13545 ms
01:38:16.938 [2-t3] DEBUG JUC.model.alternateControl - AwaitSignal time-15245 ms
01:38:20.998 [1-t3] DEBUG JUC.model.alternateControl - WaitNotify time-19289 ms

ParkUnPark > AwaitSignal > WaitNotify

9 共享模型之内存

9.1 Java内存模型

简介

JMM即 Java Memory Model,它定义了主存,工作内存抽象概念,底层对应着CPU寄存器,缓存,硬件内存,CPU指令优化等

JMM体现在以下几个方面:

  • 原子性-保证指令不会受到上下文切换的影响
  • 可见性-保证指令不受CPU缓存的影响
  • 有序性-保证指令不会CPU指令并行优化的影响

9.2 可见性

简介:

我们知道,在现代计算机中,由于 CPU 直接从主内存中读取数据的效率不高,所以都会对应的 CPU 高速缓存,先将主内存中的数据读取到缓存中,线程修改数据之后首先更新到缓存,之后才会更新到主内存。如果此时还没有将数据更新到主内存其他的线程此时来读取就是修改之前的数据。而在多线程的情况下,可能会导致变量的值改变,与实际的值不符,而缓存里的值是不会改变的。所以应当保证在多线程的情况下,变量的值得改变要同步,volatile保证了每次值都从内存中获取。

定义:

可见性就是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

问题
static boolean runnable = true;
    static int i =0;

    public static void main(String[] args) {
        new Thread(()->{
            Sleeper.sleep(1);
            runnable = false;
            log.debug("stop");
        }).start();
//        new Thread(()->{
//            foo();
//        }).start();
        foo();
    }

    public static void foo(){
        int i = 0;
        while(runnable){
             i++;
        }
        log.debug("结束 i={}",i);
    }
结果

JDK5 中 运行不会停止

image-20220331154727822

JDK8中 运行停止了(这里是因为我用的是jdk x86的原因 导致不会有可见性存在)

image-20220331154758252

原理

image-20220331142913212

当全局变量定义时,放入在主内存,此时t线程读取主内存的run,进入循环
image-20220331143050864

有JIT优化的原因,会给t线程开辟一个高速缓存,并将变量run存入高速缓存,以帮助线程更快的获取变量

image-20220331143203102

此时主线程修改主内存中的run,run被改为false,但是t线程中的run还是使用的高速缓存,则导致不可见性,

循环一直持续。

解决方法
volatile(易变关键字)
volatile static boolean runnable = true;

它可以用来修饰成员变量和静态成员变量,他可以避免线程从自己的线程的工作缓存获取资源,必须到主存中获取它的值,线程操作volatile变量都是直接操作主存

synchronized
public static void main(String[] args) {
        foo2();
        Sleeper.sleep(1);
        log.debug("stop");
        synchronized (lock){
            runnable = false;
        }
    }
public static void foo2(){
    Thread t1 = new Thread(()->{
        while (true){
            synchronized (lock){
                if(!runnable){
                    break;
                }
            }
        }
    },"t1");
    t1.start();
}
为什么在while循环中加入System.out.println() 也会使其退出循环?

因为在System.out.println()的源码中,进行了加锁操作

public void println(String x) {
        synchronized (this) {
            print(x);
            newLine();
        }
    }
Synchronized vs Volatile
  • volatile保证多个线程之间,一个线程对volatile变量修改,一个线程对volatile读取,可以保证可见性,但不能保证原子性,仅用在一个线程读取一个线程修改的情况。

  • synchronized 可以保证多个线程之间的读写问题,即既能保证原子性也能保证可见性,但是synchronized属于重量级操作,十分消耗性能。

使用volatile的两阶段终止模式
private Thread monitor;
volatile private boolean stop = false;

public TwoPhaseInterrput(){

}
public void start(){
    Runnable runnable = ()->{
        while(true){
            Thread current = Thread.currentThread();
            if(stop){
                log.debug("料理后事");
                break;
            }
            try {
                log.debug("监控中");
                Thread.sleep(2000);
            }catch (InterruptedException e){
                //current.interrupt(); //由于sleep会清除Interrupt标记,重新打断
            }
        }
    };
    monitor = new Thread(runnable,"t1");
    monitor.start();
}

public void stop(){
    Sleeper.sleep(4);
    stop = true;
    log.debug("停止监控");
    monitor.interrupt();
}

public static void main(String[] args) throws InterruptedException{
    TwoPhaseInterrput twoPhaseInterrput = new TwoPhaseInterrput();
    twoPhaseInterrput.start();
    twoPhaseInterrput.stop();
}
同步模式之Balking

Balking(犹豫)模式,用在一个线程发现另一个线程做了本线程已经做了某一件相同的事,那么线程则无需在做了,直接返回

@Slf4j
class TwoPhaseTermination{
    private Thread monitorThread;
    private boolean stop = false;
    private boolean starting = false;

    public void start(){
        synchronized (this){
            if(monitorThread!=null&&monitorThread.getState().equals(Thread.State.TERMINATED)){
                starting = false;
                log.debug("唤醒监控");
            }
            if(starting){
                log.debug("监控已经启动");
                return;
            }
            starting = true;
        }
            log.debug("创建新监控");
            monitorThread = new Thread(()->{
                log.debug("启动监控");
                Sleeper.sleep(1);
                log.debug("监控关闭");
            },"monitor");
            monitorThread.start();

    }
}
public static void main(String[] args) {
    TwoPhaseTermination twoPhaseTermination = new TwoPhaseTermination();
    Thread t1 = new Thread(()->{
        for (int i = 0; i <10 ; i++) {
            twoPhaseTermination.start();
            try {
                Thread.sleep(500);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    },"t1");
    t1.start();
    Thread t2 = new Thread(()->{
        for (int i = 0; i <10 ; i++) {
            twoPhaseTermination.start();
        }
    },"t2");
    t2.start();
}
结果:
18:46:18.095 [t1] DEBUG JUC.model.TwoPhaseTermination - 创建新监控
18:46:18.095 [t2] DEBUG JUC.model.TwoPhaseTermination - 监控已经启动
18:46:18.105 [t2] DEBUG JUC.model.TwoPhaseTermination - 监控已经启动
18:46:18.105 [t2] DEBUG JUC.model.TwoPhaseTermination - 监控已经启动
18:46:18.105 [t2] DEBUG JUC.model.TwoPhaseTermination - 监控已经启动
18:46:18.105 [t2] DEBUG JUC.model.TwoPhaseTermination - 监控已经启动
18:46:18.105 [t2] DEBUG JUC.model.TwoPhaseTermination - 监控已经启动
18:46:18.105 [t2] DEBUG JUC.model.TwoPhaseTermination - 监控已经启动
18:46:18.105 [t2] DEBUG JUC.model.TwoPhaseTermination - 监控已经启动
18:46:18.105 [t2] DEBUG JUC.model.TwoPhaseTermination - 监控已经启动
18:46:18.105 [t2] DEBUG JUC.model.TwoPhaseTermination - 监控已经启动
18:46:18.106 [monitor] DEBUG JUC.model.TwoPhaseTermination - 启动监控
18:46:18.621 [t1] DEBUG JUC.model.TwoPhaseTermination - 监控已经启动
18:46:19.113 [monitor] DEBUG JUC.model.TwoPhaseTermination - 监控关闭
18:46:19.128 [t1] DEBUG JUC.model.TwoPhaseTermination - 唤醒监控
18:46:19.128 [t1] DEBUG JUC.model.TwoPhaseTermination - 创建新监控
18:46:19.128 [monitor] DEBUG JUC.model.TwoPhaseTermination - 启动监控
18:46:19.635 [t1] DEBUG JUC.model.TwoPhaseTermination - 监控已经启动
18:46:20.136 [t1] DEBUG JUC.model.TwoPhaseTermination - 监控已经启动
18:46:20.136 [monitor] DEBUG JUC.model.TwoPhaseTermination - 监控关闭
18:46:20.649 [t1] DEBUG JUC.model.TwoPhaseTermination - 唤醒监控
18:46:20.649 [t1] DEBUG JUC.model.TwoPhaseTermination - 创建新监控
18:46:20.649 [monitor] DEBUG JUC.model.TwoPhaseTermination - 启动监控
18:46:21.164 [t1] DEBUG JUC.model.TwoPhaseTermination - 监控已经启动
18:46:21.664 [monitor] DEBUG JUC.model.TwoPhaseTermination - 监控关闭
18:46:21.664 [t1] DEBUG JUC.model.TwoPhaseTermination - 监控已经启动
18:46:22.165 [t1] DEBUG JUC.model.TwoPhaseTermination - 唤醒监控
18:46:22.165 [t1] DEBUG JUC.model.TwoPhaseTermination - 创建新监控
18:46:22.165 [monitor] DEBUG JUC.model.TwoPhaseTermination - 启动监控
18:46:22.666 [t1] DEBUG JUC.model.TwoPhaseTermination - 监控已经启动
18:46:23.165 [monitor] DEBUG JUC.model.TwoPhaseTermination - 监控关闭
应用:

实现线程安全的单例模式

public class Singleton {
    private Singleton(){
        
    }
    
    private static Singleton INSTANCE = null;
    
    public static synchronized Singleton getInstance(){
        if(INSTANCE!=null){
            return INSTANCE;
        }
        INSTANCE = new Singleton();
        return INSTANCE;
    }
}

9.3 有序性

什么是【指令重排】?

JVM会在不影响正确性的前提下,可以调整语句的执行顺序

static int i;
static int j;

i = ..
j = ..

对于以上结果,先执行i,还是先执行j,都不会对最后的结果有影响,这样的特性称之为【指令重排】,但是在多线程下【指令重排会影响正确性。

为什么要有【指令重排】这一特性呢?
指令重排序优化

现代处理器会设计为一个时钟周期完成一条执行时间最长的CPU指令。指令可以再划分成一个个更小的阶段,例如,每条指令都可以分为:取指令 - 指令译码 - 执行指令 - 内存访问 - 数据写回
image-20220331191315711

如像图上操作的话,将浪费很多时间,我们可以在指令译码期间,执行下一条指令的取指令。现代CPU支持多级指令的流水线操作,例如同时执行 取指令 - 指令译码 - 执行指令 - 内存访问 - 数据写回 的处理器,就可以称为五级指令流水线。这是CPU可以在一个时钟周期内,同时运行五条指令的不同阶段,本质上,多级指令流水线操作不能缩短单条指令的运行速度,但是可以大大提高吞吐量

image-20220331191655681

在不改变程序结果的前提下,这些指令的各个阶段可以通过重排序组合来实现指令并行,如下图就是可以进行指令重排序

int a = 20
int b = 20;

这是不能指令重排序的例子

int a = 10;
int b = 20 - a;
诡异的结果
int num = 0;

// volatile 修饰的变量,可以禁用指令重排 volatile boolean ready = false; 可以防止变量之前的代码被重排序
boolean ready = false; 

// 线程1 执行此方法
public void actor1(I_Result r) {
	 if(ready) {
	 	r.r1 = num + num;
	 } 
	 else {
	 	r.r1 = 1;
	 }
}
// 线程2 执行此方法
public void actor2(I_Result r) {
	 num = 2;
	 ready = true;
}

以上代码 r1 会有几种结果

  • 结果一:先执行线程2的actor2方法,执行完后,执行线程1,最后答案为4
  • 结果二:先执行actor1方法,执行完后,答案为1
  • 结果三:因为重排序的原因,使得ready和num的代码执行顺序倒转,使得ready = true先执行,从而导致答案为0
解决:

对ready变量加一个volatile,使得重排序问题解决

9.4 volatile 原理

volatile的底层原理是内存屏障

  • 读屏障:对volatile变量执行读指令会添加读屏障
  • 写屏障:对volatile变量执行写指令会添加写屏障
如何保证可见性

写屏障: 在该屏障之前,对同步到主存当中,将修改的数据修改至主存当中

public void actor2(I_Result r) {
	 num = 2;
	 ready = true; //ready 是 volatile赋值
    //写屏障
}

读屏障:在该屏障之后,对共享变量的读取,会在主存当中去读取

public void actor1(I_Result r) {
    //读屏障
	 if(ready) {
	 	r.r1 = num + num;
	 } 
	 else {
	 	r.r1 = 1;
	 }
}

image-20220402174335491

如何保证有序性

写屏障:在该屏障之前的代码不会排在写屏障之后

public void actor2(I_Result r) {
	 num = 2;
	 ready = true; //ready 是 volatile赋值
    //写屏障
}

读屏障:在该屏障之后的代码不会排在读屏障之前

public void actor1(I_Result r) {
    //读屏障
	 if(ready) {
	 	r.r1 = num + num;
	 } 
	 else {
	 	r.r1 = 1;
	 }
}

image-20220402175426307

volatile虽然能解决可见性和有序性,但是无法解决原子性,也就是指令交错
  • 写操作能保证,主存中更新最新的数据,但是不能保证,读指令在写操作之前发生
  • 有序性也只能保证在本线程内相关代码不被重新排序

image-20220402175746518

double-checked locking 问题
public class Singleton {
    private Singleton(){

    }

    private static Singleton INSTANCE = null;

    public static synchronized Singleton getInstance(){
        if(INSTANCE!=null){
            return INSTANCE;
        }
        INSTANCE = new Singleton();
        return INSTANCE;
    }
}

以上实现的特点:

  • 懒惰实例化
  • 利用synchronized对getInstance加锁,防止线程可见性问题

**问题:**虽说保证了线程安全,保证实例对象只会生成一个,但是在单例对象生成后,我就不需要对其加上线程安全锁了,每一次调用getInstance方法都会要上锁,会导致性能降低

于是就有了著名的 double-checked locking单例模式

public class Singleton {
    private Singleton(){

    }

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

首次访问会同步加锁,之后使用不会使用synchronized

上述代码看似完美,但也是有问题的

getInstance()字节码
public static JUC.model.Singleton getInstance();
    Code:
       0: getstatic     #2                  // Field INSTANCE:LJUC/model/Singleton;
       3: ifnonnull     37
       6: ldc           #3                  // class JUC/model/Singleton 加锁获得类对象
       8: dup                               //类对象引用指针复制一份
       9: astore_0                          //存储一份
      10: monitorenter
      11: getstatic     #2                  // Field INSTANCE:LJUC/model/Singleton;
      14: ifnonnull     27
      17: new           #3                  // class JUC/model/Singleton
      20: dup
      21: invokespecial #4                  // Method "<init>":()V
      24: putstatic     #2                  // Field INSTANCE:LJUC/model/Singleton;
      27: aload_0
      28: monitorexit
      29: goto          37
      32: astore_1
      33: aload_0
      34: monitorexit
      35: aload_1
      36: athrow
      37: getstatic     #2                  // Field INSTANCE:LJUC/model/Singleton;
      40: areturn
    Exception table:
       from    to  target type
          11    29    32   any
          32    35    32   any

  • 17:new一个对象引用
  • 20:复制该对象引用
  • 21:调用构造方法
  • 24:将引用放入静态变量中

此时 问题来了 由于jvm优化的结果,会导致21行指令和24行指令进行重排序,虽然是在synchronized里面,但是第一个if语句是不包含在synchronized语句中的,所以就会出现t2在INSTANCE还没初始化,就被返回一个NULL值。

#:虽然synchronized能保证有序性,但是无法改变JVM的指令重排序优化

image-20220402183416400

问题解决

对INSTANCE加上volatile变量

 private static Singleton INSTANCE = null;
public static JUC.model.Singleton getInstance();
    Code:
    // ----------------------------------------------------> 加入对INSTANCE变量的读屏障
       0: getstatic     #2                  // Field INSTANCE:LJUC/model/Singleton;
       3: ifnonnull     37
       6: ldc           #3                  // class JUC/model/Singleton 加锁获得类对象
       8: dup                               //类对象引用指针复制一份
       9: astore_0                          //存储一份
      10: monitorenter -----------------------> 保证原子性,可见性
      11: getstatic     #2                  // Field INSTANCE:LJUC/model/Singleton;
      14: ifnonnull     27
      17: new           #3                  // class JUC/model/Singleton
      20: dup
      21: invokespecial #4                  // Method "<init>":()V
      24: putstatic     #2                  // Field INSTANCE:LJUC/model/Singleton;
      //--------------------------------------> 加入INSTANCE变量的写屏障
      27: aload_0
      28: monitorexit
      29: goto          37
      32: astore_1
      33: aload_0
      34: monitorexit
      35: aload_1
      36: athrow
      37: getstatic     #2                  // Field INSTANCE:LJUC/model/Singleton;
      40: areturn
    Exception table:
       from    to  target type
          11    29    32   any
          32    35    32   any

**情况1:**t2 getstatic() 获取的是空值NULL此时会进入synchronized,但由于A已经加锁,会使得t2锁住,直到A解锁,此时INSTANCE已经赋值了,t2获取最新的INSTANCE并且返回

image-20220402190134854

**情况2:**此时的putstatic不会在invokespecial的前面,就不会出现先赋值,再构造的情况了

image-20220402190341247

happens-before

happens-before规定了对共享变量的写操作对其他的线程的读操作可见,它是可见性与有序性的一套规则总结,抛开以下happens-before规则,JMM并不能保证一个线程对共享变量的写,对于其他线程对该共享变量的读可见

  • 线程解锁m之前对变量的写,对于接下来对m加锁的其他线程对该变量的读可见

image-20220402191318405

  • 线程对volatile变量的写,对接下来其它线程对变量的读可见

image-20220402191318405

  • 线程start前对变量的写,对该线程开始后对该变量的读可见

image-20220402191508935

  • 线程结束前对变量的写,对其他线程得知他结束后的读可见(比如其他线程调用t1,isAlive(),t1.join()等待他结束)

image-20220402191728776

  • 线程t1打断t2前对变量的写,对于其他线程得知t2被打断后对变量的读可见(通过t2.interrupted或t2.isInterrupted)
    image-20220402191935840

  • 对变量默认值的写(0,false,null),对其他线程对该变量可见

  • 具有传递性,由于写屏障会将写屏障之前的赋值都同步到主存中,所以

image-20220402192753344

9.5 线程安全单例

单例模式有很多实现方法,饿汉,懒汉,静态内部类,枚举类

懒汉式:类加载不会导致该单例实例对象被创建,而是首次使用该对象时才会被创建

饿汉式:类加载就会导致该单例对象被创建

实现1:
//问题1:为什么加final
//问题2:如果实现序列化接口,还需要做什么来防止反序列化破坏单例
public final class Singleton2 implements Serializable {
    //问题3:为什么设置未私有?是否能防止反射创建新的实例
    private Singleton2(){}
    //问题4:这也初始化是否能保证单例对象创建时的线程安全?
    private static final Singleton2 INSTANCE = new Singleton2();
    //问题5:为什么提高静态方法而不是直接将INSTANCE设置未public.
    public static Singleton2 getInstance(){
        return INSTANCE;
    }
}

问题1:防止子类继承,更改方法导致线程不安全

问题2:由于反序列化会创建新的对象,所以我们可以重写readResovle来返回原本的单例

public Object readResovle(){
 return INSTANCE
}

问题3:不让外界对象调用构造方法,破坏单例模式,不能防止反射

问题4:静态成员变量的初始化是在类加载阶段完成的,由JVM完成,不会有线程安全问题

问题5:利用方法 可以对单例对象有更多控制,用方法可以返回泛型,还可以实现一个懒惰式的加载

实现2:
//问题1:枚举单例是如何限制实例个数的
//问题2:枚举单例是否有并发问题
//问题3:枚举单例是否能被反射破坏单例
//问题4:枚举单例是否被反序列化破坏单例
//问题5:枚举单例属于懒汉式还是饿汉式
//问题6:枚举单例如果希望加入一些单例创建时的初始化逻辑应该如何做
enum Singleton{
   INSTANCE;
}

枚举变量字节码文件

final enum JUC/model/SingletonEnum extends java/lang/Enum {

  // compiled from: SingletonEnum.java

  // access flags 0x4019
  public final static enum LJUC/model/SingletonEnum; INSTANCE

问题1:创建了多少个枚举类型,就有多少个实例对象

问题2:无并发问题,因为是静态变量

问题3:枚举类型无法用反射破坏单例

问题4:枚举类型默认实现了序列化,不会被反序列破坏单例

问题5:饿汉式

问题6:再枚举类里面可以加一些构造方法

实现3:
public class Singleton {
        private Singleton(){

        }

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

        public static void main(String[] args) {

        }
}
实现4:
public class SingletonStatic {
    private SingletonStatic(){ }

    private static class LazyHolder{
        static final SingletonStatic INSTANCE = new SingletonStatic();
    }

    public static SingletonStatic getInstance(){
        return LazyHolder.INSTANCE;
    }
}

10 保护共享资源-无锁实现

10.1 AtomicInteger

Account 接口
interface Account{
    Integer getBalance();
    void withdraw(Integer balance);
    static void demo(Account account){
        List<Thread> ts = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            ts.add(new Thread(()->{
                account.withdraw(10);
            }));
        }
        long start = System.nanoTime();
        ts.forEach(Thread::start);
        ts.forEach(t->{
            try {
                t.join();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        });
        long end = System.nanoTime();

        System.out.println(account.getBalance()+" cost:"+(end-start)/1000_000+" ms");
    }
}
有锁技术实现资源共享
class AccountUnsafe implements Account{
    private int balance;

    public AccountUnsafe(int balance){
        this.balance = balance;
    }
    @Override
    public synchronized Integer getBalance() {
        return this.balance;
    }

    @Override
    public synchronized void withdraw(Integer balance) {
        this.balance -= balance;
    }
}
无所技术实现资源共享
class AccountCas implements Account{
    private AtomicInteger balance;

    public AccountCas(int balance){
        this.balance = new AtomicInteger(balance);
    }

    @Override
    public Integer getBalance(){
        return this.balance.get();
    }

    @Override
    public void withdraw(Integer balance){
        while(true){
            int prev = this.balance.get();
            int next = prev-balance;
            if(this.balance.compareAndSet(prev,next)){
                break;
            }
        }
    }
}
结果比较
0 cost:107 ms //Unsafe
0 cost:105 ms //AtomicInteger

Process finished with exit code 0

10.2 CAS与volatile

为什么 compareAndSet 可以实现不加锁也能保证线程安全问题?

compareAndSet 又简称为CAS,由于他是原子操作,所以可以保证线程安全问题

image-20220422214037388

流程解析
  • 线程1获取 余额 100 并且 减少10 此时的值为90
  • 此时线程2 已经将余额变为了 90
  • 线程1利用cas(100,90)去比较,发现此时的balance 为 90 和 100不符合于是返回false
  • 线程1重新获得余额值

注意

其实CAS的底层是lock cmpxchg指令(x86架构),在单核CPU和多核CPU都能保证【比较-交换】的原子性

  • 在多核状态下,某个核执行到带 lock 的指令时,CPU会让总线锁住,当这个核把此指令执行完成时,才开启总线,这个过程不会被进程调度机制所给打断,保证了多个线程对内存操作的准确性,原子性
volatile 与 CAS之间的关系

由于CAS操作必须要读取当前的最新值,所以必须要将当前的值同步到主存,才能保证线程之间的可见性,所以在AtomicInteger中的value是被volatile进行修饰的。

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;
为什么无锁效率高?
  • 无锁情况下,线程在高速运行,无停歇。而加锁的线程,会因为进入临界区域,停下来,且再次启动需要进行上下文切换,会导致性能代价比较大
  • 但是无锁情况下,一旦线程的数量超过cpu的数量,就会导致无时间片可分配,线程还是要进入阻塞状态,进行上下文切换
CAS的特点

结合CAS和volatile可以实现无锁并发,适用于线程较少的,多核CPU的场景下

  • CAS是基于乐观锁的思想:最乐观的估计,不怕别的线程来修改共享变量。
  • synchronized采用的是悲观锁的思想,防止其他线程来修改共享变量。

image-20220422223808771

11 原子

11.1 原子整数

  • AtomicBoolean
  • AtomicInteger
  • AtomicLong
例:AtomicInteger
AtomicInteger atomicInteger = new AtomicInteger(5);
System.out.println(atomicInteger.getAndIncrement()); //i++
System.out.println(atomicInteger.incrementAndGet()); //++i
System.out.println(atomicInteger.updateAndGet(x -> x * 10)); //i*10
updareAndGet原理
public final int updateAndGet(IntUnaryOperator updateFunction) {
        int prev, next;
        do {
            prev = get();
            next = updateFunction.applyAsInt(prev);
        } while (!compareAndSet(prev, next));
        return next;
    }

11.2 原子引用

  • AtomicReference
  • AtomicMarkableReference
  • AtomicStampedReference
ABA问题
@Slf4j
public class AtomReference {
    static AtomicReference<String> ref = new AtomicReference<>("A");
    public static void main(String[] args) {
      String prev = ref.get();
        other();
        Sleeper.sleep(1);
        log.debug("change A->C {}",ref.compareAndSet(prev,"C"));
    }

    public static void other() {
       Thread t1 = new Thread(()->{
           String prev = ref.get();
           log.debug("change A->B {}",ref.compareAndSet(prev,"B"));
       });
       t1.start();
       Thread t2 = new Thread(()->{
           String prev = ref.get();
           log.debug("change B->A {}",ref.compareAndSet(prev,"A"));
       });
       t2.start();

    }
}

引用A为AtomicReference

主线程 将引用A 改为 引用C

线程1 将引用A 改为 引用B

线程2 将引用B 改为 引用A

此时 线程1先启动,其次线程2,再主线程

在如上情况,主线程无法察觉到引用A被更改,虽然再平常工作中无大碍,但是是存在安全隐患的,所以我们可以用

AtomicStampedReference来解决这个问题

AtomicStampedReference
@Slf4j
public class AtomReference {
    static AtomicStampedReference<String> ref = new AtomicStampedReference<>("A",0);
    public static void main(String[] args) {
      String prev = ref.getReference();
      int stamp = ref.getStamp();
        other();
        Sleeper.sleep(1);
        log.debug("change A->C {}",ref.compareAndSet(prev,"C",stamp,stamp+1));
    }

    public static void other() {
       Thread t1 = new Thread(()->{
           log.debug("change A->B {}",ref.compareAndSet(ref.getReference(),"B",ref.getStamp(),ref.getStamp()+1));
       });
       t1.start();
       Thread t2 = new Thread(()->{
           log.debug("change B->A {}",ref.compareAndSet(ref.getReference(),"A",ref.getStamp(),ref.getStamp()+1));
       });
       t2.start();

    }
}

AtomicStampedReference可以给原子引用加上版本号,确定整个原子的变化过程

AtomicMarkableReference

image-20220425223520696

如果不想知道版本号,只想知道是否修改,则可以用AtomicMarkableReference

11.3 原子数组

public class AtomArray {
    public static void main(String[] args) {
       demo(
               ()->new AtomicIntegerArray(10),
               (array) -> array.length(),
               (array,index)->array.getAndIncrement(index),
               array-> System.out.println(array)
       );
    }

    private static <T> void demo(
            Supplier<T> arraySupplier,
            Function<T,Integer> lengthFun,
            BiConsumer<T,Integer> putConsumer,
            Consumer<T> printConsumer
    ){
        List<Thread> ts = new ArrayList<>();
        T array = arraySupplier.get();
        int length = lengthFun.apply(array);
        for (int i = 0; i <length ; i++) {
            ts.add(new Thread(()->{
                for (int j = 0; j <10000 ; j++) {
                    putConsumer.accept(array,j%length);
                }
            }));
        }
        ts.forEach(t->t.start());
        ts.forEach(t->{
         try {
            t.join();
         }catch (InterruptedException e){
            e.printStackTrace();
         }});
        printConsumer.accept(array);
    }
}

11.4 原子更新器

package JUC.g_noLock.Atomic;

import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

public class AtomFileUpdate {

    public static void main(String[] args) {
        Student genius = new Student("Genius");
        AtomicReferenceFieldUpdater updater = AtomicReferenceFieldUpdater.newUpdater(Student.class,String.class,"name");
        System.out.println(updater.compareAndSet(genius,"Genius","fuck"));
        System.out.println(genius);

    }
}

class Student{
    volatile String name;
    public Student(String name){
        this.name = name;
    }

    @Override
    public String toString() {
        return name;
    }
}

11.5 原子累加器

package JUC.g_noLock.Atomic;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;
import java.util.function.Consumer;
import java.util.function.Supplier;

public class AtomSum {
    public static void main(String[] args) {
         demo(
                 ()->new AtomicLong(0),
                 (adder)->adder.getAndIncrement()
         );
        demo(
                ()->new LongAdder(),
                (adder)->adder.increment()
        );
    }

    private static <T> void demo(Supplier<T> adderSupplier, Consumer<T> action){
        T adder = adderSupplier.get();
        List<Thread> ts = new ArrayList<>();
        for (int i = 0; i <4 ; i++) {
            ts.add(new Thread(()->{
                for (int j = 0; j <50000000 ; j++) {
                    action.accept(adder);
                }
            }));
        }
        long start = System.nanoTime();
        ts.forEach(Thread::start);
        ts.forEach(t->{
            try {
                t.join();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        });
        long end = System.nanoTime();
        System.out.println((end-start)/1000_000);
    }
}

java为累加提供了专门的累加器,效果是AtomicInteger累加的十倍

Atomic_Sum cost: 19332
LongAdder_Sum cost: 1041

性能提升的原因:LongAdder为每一个Thread分配了一个累加单元Cell[0],再累加的过程中,每个线程对自己的Cell[0]进行操作,累加结束后再将结果汇总,减少了CAS失败的次数,从而提升了效率

源码之LongAdder

LongAdder类有几个关键域

//累加单元数组,懒惰初始化
transient volatile Cell[] cells;

// 基础值,如果没有竞争,则用cas累加这个域
transient volatile long base;

// 在cells 创建或扩容时,置为1,表示加锁
transient volatile int cellsBusy
Cas锁
public class CasLock {
    private AtomicInteger lock;
    public CasLock(){
        lock = new AtomicInteger(0);
    }
    
    //当无人竞争时,线程占用锁,且将busy改为1,当其他线程使用该锁时,将会循环
    public void lock(){
        log.debug("lock");
        while(true){
            if(lock.compareAndSet(0,1)){
                break;
            }
        }
    }

    //调用unlock 将busy改为0,使其解锁
    public void unlock(){
        lock.set(0);
        log.debug("unlock");
    }
}
原理之伪共享
Cell类
@sun.misc.Contended 
static final class Cell {
    volatile long value;
    Cell(long x) { value = x; }
    final boolean cas(long cmp, long val) {
        return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
    }

    // Unsafe mechanics
    private static final sun.misc.Unsafe UNSAFE;
    private static final long valueOffset;
    static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> ak = Cell.class;
            valueOffset = UNSAFE.objectFieldOffset
                (ak.getDeclaredField("value"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }
}
缓存与内存的比较

image-20220427210009831

image-20220427210850571

由上图可知,使用缓存的原因是因为cpu读取内存的时间十分长,相对于缓存来说慢很多。

而缓存以缓存行为单位,一个缓存行一般是以64byte为单位(8个long)。

缓存并不是十全十美的,缓存会导致数据副本问题,也就是可能造成数据不一致。

当一个CPU对缓存中的数据进行更改(如二级缓存),则另一个CPU中的二级缓存的数据就必须要失效。

image-20220428085137816

因为Cell是数组形式,连续存放在CPU中,一个Cell单位是24字节(16byte对象头+8byte long),Cell[0]和Cell[1]两个缓存行的数据大小是48,可以存放在一个缓存行中,此时问题来了:

  • Core-0 修改Cell[0]
  • Core-1 修改Cell[1]

此时不管是谁修改了Cell,都会导致对方的缓存行失效,导致Cpu-Core需要重新到内存中去寻找数据,大大降低了读取效率

@sun.misc.Contended 此时该注解就能发挥效果了,java对标注该注解的对象进行padding填充,将128byte填充在对象后面,这个时候就能使对象不占用同一缓存行了

**注:**为什么是128byte呢,因为缓存行的大小在 32~256之间,128可以保证只能放下一个缓存行

add方法

image-20220428085137816

public void add(long x) {
    Cell[] as; long b, v; int m; Cell a;
    if ((as = cells) != null || !casBase(b = base, b + x)) {
        boolean uncontended = true;
        if (as == null || (m = as.length - 1) < 0 ||
            (a = as[getProbe() & m]) == null ||
            !(uncontended = a.cas(v = a.value, v + x)))
            longAccumulate(x, null, uncontended);
    }
}
longAccumlate方法
final void longAccumulate(long x, LongBinaryOperator fn,
                          boolean wasUncontended) {
    int h;
    if ((h = getProbe()) == 0) {
        ThreadLocalRandom.current(); // force initialization
        h = getProbe();
        wasUncontended = true;
    }
    boolean collide = false;                // True if last slot nonempty
    for (;;) {
        Cell[] as; Cell a; int n; long v;
        //已经创建
        //在无锁状态下锁是否为空,且槽是否被占用,不为空且没被占用进入下一层
        if ((as = cells) != null && (n = as.length) > 0) {
            if ((a = as[(n - 1) & h]) == null) {
                if (cellsBusy == 0) {       // Try to attach new Cell
                    Cell r = new Cell(x);   // Optimistically create
                    if (cellsBusy == 0 && casCellsBusy()) {
                        //在有锁状态下锁是否为空,且槽是否被占用,不为空则创建
                        boolean created = false;
                        try {               // Recheck under lock
                            Cell[] rs; int m, j;
                            if ((rs = cells) != null &&
                                (m = rs.length) > 0 &&
                                rs[j = (m - 1) & h] == null) {
                                rs[j] = r;
                                created = true;
                            }
                        } finally {
                            cellsBusy = 0;
                        }
                        if (created)
                            break;
                        continue;           // Slot is now non-empty
                    }
                }
                collide = false;
            }
            else if (!wasUncontended)       // CAS already known to fail
                wasUncontended = true;      // Continue after rehash
            //槽位不为空则进行累加
            else if (a.cas(v = a.value, ((fn == null) ? v + x :
                                         fn.applyAsLong(v, x))))
                break;
            //累加失败,则看线程数是否超过CPU的核心数
            else if (n >= NCPU || cells != as)
                collide = false;            // At max size or stale
            else if (!collide)
                collide = true;
            //未超过CPU的核心数,则进行扩容,并且加锁
            else if (cellsBusy == 0 && casCellsBusy()) {
                try {
                    if (cells == as) {      // Expand table unless stale
                        Cell[] rs = new Cell[n << 1];
                        for (int i = 0; i < n; ++i)
                            rs[i] = as[i];
                        cells = rs;
                    }
                } finally {
                    cellsBusy = 0;
                }
                collide = false;
                continue;                   // Retry with expanded table
            }
            //重新分配一个新的槽位
            h = advanceProbe(h);
        }
        //未创建,未加锁,不存在
        else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
            boolean init = false;
            try {                           // Initialize table
                if (cells == as) {
                    Cell[] rs = new Cell[2];
                    rs[h & 1] = new Cell(x);
                    cells = rs;
                    init = true;
                }
            } finally {
                cellsBusy = 0;
            }
            if (init)
                break;
        }
        //加锁失败
        else if (casBase(v = base, ((fn == null) ? v + x :
                                    fn.applyAsLong(v, x))))
            break;                          // Fall back on using base
    }
}
Cell未存在

image-20220428090016999

Cell存在未创建

image-20220428090622030

Cell存在已创建

image-20220428092151435

11.6 Unsafe

概述

Unsafe对象提供了非常底层的,操作内存,线程的方法,Unsafe对象不能直接调用,只能通过反射获取

public class unsafeTest {
    public static void main(String[] args) throws NoSuchFieldException,IllegalAccessException{
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe unsafe = (Unsafe)theUnsafe.get(null);
        System.out.println(unsafe);
    }
}
Unsafe CAS操作
public static void main(String[] args) throws NoSuchFieldException,IllegalAccessException{
    Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
    theUnsafe.setAccessible(true);
    Unsafe unsafe = (Unsafe)theUnsafe.get(null);
    System.out.println(unsafe);

    //1 获取偏移地址
    long idOffset = unsafe.objectFieldOffset(Teacher.class.getDeclaredField("id"));
    long nameOffset = unsafe.objectFieldOffset(Teacher.class.getDeclaredField("name"));
    System.out.println(idOffset);

    Teacher t = new Teacher();
    //2 进行CAS操作
    unsafe.compareAndSwapInt(t,idOffset,0,1);
    unsafe.compareAndSwapObject(t,nameOffset,null,"Genius");
    System.out.println(t);
}

12 不可变类

12.1 不可变类的设计

**定义:**不可变类指的是,该类在多线程的情况下,不会因为其他线程参与,导致类的内部属性遭到篡改,而出现线程安全问题

**例子:**String 类是大家熟知的不可变类,我们借此来看看不可变类设计的要素

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0
1. final的使用

在不可变类中,大部分的属性,方法,类都有加上final前缀

  • final修饰保证该变量为可读,不可修饰
  • final修饰类,防止子类继承覆盖父类方法,导致父类不可变性遭到破坏
2.保护性拷贝

当遇到一些字符串修改问题时,就会对原先的字符串数组进行拷贝,在拷贝的基础上进行修改

public String substring(int beginIndex) {
    if (beginIndex < 0) {
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    int subLen = value.length - beginIndex;
    if (subLen < 0) {
        throw new StringIndexOutOfBoundsException(subLen);
    }
    return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}

12.2 享元模式

通过不可变类的拷贝我们可以发现,每次修改字符串都要创建一个新的字符串,从而导致过多的内存浪费,而享元模式就是针对有限个数的同一类对象可重复使用的场景来使用的

常见的享元模式例子:

image-20220620160018128

12.3 自定义连接池

连接池功能:

**1,**在连接池中会有一定的容量大小来存储已有连接(已经实现)

**2,**当有线程要获取连接时,则查看连接池中的每个连接的状态,有空闲即拿走,没有空闲则等待(已经实现)

**3,**被使用完的连接,要归回连接,且更改状态(已经实现)

**4,**连接的动态增长与收缩

**5,**连接保活

**6,**等待超时处理

**7,**分布式hash

package JUC.h_immuteable;

import JUC.g_noLock.Atomic.AtomArray;
import lombok.extern.slf4j.Slf4j;

import java.sql.*;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicIntegerArray;

@Slf4j
public class ConnectionPool {
    public static void main(String[] args){
        Pool pool = new Pool(1);
        for (int i = 0; i <100 ; i++) {
            new Thread(()->{
                Connection connection = pool.getConn();
//                try {
//                    Thread.sleep(new Random().nextInt(1000));
//                }catch (InterruptedException e){
//                    e.printStackTrace();
//                }
               pool.free(connection);
            }).start();
        }
    }

}
@Slf4j(topic="pool")
class Pool{
    private final int poolSize;

    private Connection connection[];

    private AtomicIntegerArray state;

    public Pool(int poolSize){
        this.poolSize = poolSize;
        connection = new Connection[poolSize];
        state = new AtomicIntegerArray(poolSize);
        for (int i = 0; i < poolSize; i++) {
            connection[i] = new GeniusConnection("connection"+i);
        }
    }

    public Connection getConn(){
        while (true){
            for (int i = 0; i <this.poolSize ; i++) {
                if(state.compareAndSet(i,0,1)){
                    Connection conn = connection[i];
                    log.debug("get {}",conn);
                    return conn;
                }
            }
            synchronized (this){
                try {
                    log.debug("no connection wait");
                    this.wait();
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        }
    }

    public void free(Connection conn){
        for (int i = 0; i <poolSize ; i++) {
            if(conn == connection[i]){
                state.set(i,0);
                log.debug("{} free",conn);
                synchronized (this){
                    this.notify();
                }
                break;
            }
        }
    }
}

class GeniusConnection implements Connection{

    private String name;

    public GeniusConnection(String name){
        this.name = name;
    }

    @Override
    public String toString() {
        return "GeniusConncetion{" +
                "name='" + name + '\'' +
                '}';
    }

    @Override
    public Statement createStatement() throws SQLException {
        return null;
    }
}

12.4 final原理

1,设置final变量的原理
public class killFinal {
    final static int a = 20;
}

字节码

public JUC.h_immuteable.killFinal();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: bipush        20
       7: putfield      #2                  // Field a:I
         <-- 写屏障
      10: return

发现final变量的赋值也会通过putfield指令完成,在putfield指令之后会加入写屏障,防止出现不可见性问题

2,获取final变量的原理
public class killFinal {
    final static int a = 20;
    final static int b = Integer.MAX_VALUE+1;
    static int c = 21;
}
class useFinal{

    public void test(){
        System.out.println(killFinal.a);
        System.out.println(killFinal.b);
        System.out.println(killFinal.c);
        System.out.println(killFinal.d);
    }
}
  • final static int a 的读取

        L0
        LINENUMBER 22 L0
        GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
        BIPUSH 20
        INVOKEVIRTUAL java/io/PrintStream.println (I)V
    

    并没有从类中取出a变量,而是直接从常量池取出数字20 推入操作数栈,相当于复制一份来进行处理(由于 -128<a<127 所以为BIPUSH)

  • final static int b 的读取

    L1
    LINENUMBER 23 L1
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC -2147483648
    INVOKEVIRTUAL java/io/PrintStream.println (I)V

​ 和a同理,只是采用的是LDC的方式从常量池取出,推入到栈中

  • static int c的读取
    L2
    LINENUMBER 24 L2
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    GETSTATIC JUC/h_immuteable/killFinal.c : I
    INVOKEVIRTUAL java/io/PrintStream.println (I)V 

普通常量的读取就和final读取有不同,是从变量的类中,也就是从堆中去寻找,取出后,得到该值,所以final变量的读取会要比非final变量读取更加的快速

13 线程池

13.1 手写线程池

image-20220622142058663

1,BlockingQueue 阻塞队列

当线程池中核心调用数已满时,则将要添加的任务,放入阻塞队列中,由于多线程操作,所以要保证BlockingQueue是线程安全的同时需要用条件来维护BlockingQueue的满员状态和空状态

class BlockQueue<T>{
    private Deque<T> deque = new ArrayDeque<>();

    private ReentrantLock lock = new ReentrantLock();

    private Condition fullWaitSet = lock.newCondition();

    private Condition emptyWaitSet = lock.newCondition();

    private int capacity;

    public BlockQueue(int capacity) {
        this.capacity = capacity;
    }

    public T get(long timeout,TimeUnit timeUnit){
        lock.lock();
        try {
            while (deque.isEmpty()){
                long nanos = timeUnit.toNanos(timeout);
                try {
                    log.debug("任务队列为空");
                    nanos = emptyWaitSet.awaitNanos(nanos);
                    if(nanos<=0){
                        return null;
                    }
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
            T element = deque.removeFirst();
            log.debug("取出任务 {}",element);
            fullWaitSet.signal();
            return element;
        }finally {
            lock.unlock();
        }
    }

    public void tryPut(T task,RejectPolicy<T> rejectPolicy){
        try{
            lock.lock();
            if(deque.size()>=capacity){
                rejectPolicy.reject(this,task);
            }else{
                deque.addLast(task);
                emptyWaitSet.signal();
            }
        }finally {
            lock.unlock();
        }
    }

    public void put(T element){
        lock.lock();
        try {
            while (deque.size()>=capacity){
                try {
                    log.debug("任务队列已满");
                    fullWaitSet.await();
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
            log.debug("放入任务 {}",element);
            deque.addLast(element);
            emptyWaitSet.signal();
        }finally {
            lock.unlock();
        }
    }

    public int size(){
        lock.lock();
        try {
            return deque.size();
        }finally {
            lock.unlock();
        }
    }

}
2,ThreadPool 线程池
2.1 拒绝策略

拒绝策略即,当每一个任务工作时间十分长,核心数已满,且阻塞队列也已满,此时put的话会导致BlockQueue长时间等待,所以,我们采用拒绝策略来针对满员时put的操作,并且将该拒绝策略下放给调用者去执行

@FunctionalInterface
interface RejectPolicy<T> {
    void reject(BlockQueue<T> queue, T task);
}

BlockQueue中的tryput

public void tryPut(T task,RejectPolicy<T> rejectPolicy){
    try{
        lock.lock();
        if(deque.size()>=capacity){
            rejectPolicy.reject(this,task);
        }else{
            deque.addLast(task);
            emptyWaitSet.signal();
        }
    }finally {
        lock.unlock();
    }
}
2.2 worker

worker即线程池中工作的线程,负责完成分配任务以及阻塞队列的任务,每完成一个任务则设置为空,并且去阻塞队列寻找下一个任务

class Worker extends Thread{
        private Runnable task;
        public Worker(Runnable task){
            this.task = task;
        }

        @Override
        public void run(){
            while(task!=null||(task=taskQueue.get(timeOut,timeUnit))!=null){
                try {
                    task.run();
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    log.debug("任务执行结束");
                    task = null;
                }
            }
            synchronized (workers){
                log.debug("{}移除 没有任务可做",this);
                workers.remove(this);
            }
        }

    }
2.3 线程池代码
public class GeniusThreadPool {
    private BlockQueue<Runnable> taskQueue;

    private HashSet<Worker> workers = new HashSet<>();

    private int poolCapacity;

    private int timeOut;

    private TimeUnit timeUnit;

    private RejectPolicy<Runnable> rejectPolicy;

    public GeniusThreadPool(int poolCapacity,int queueCapacity,int timeOut, TimeUnit timeUnit,RejectPolicy<Runnable> rejectPolicy) {
        this.poolCapacity = poolCapacity;
        this.timeOut = timeOut;
        this.timeUnit = timeUnit;
        this.rejectPolicy = rejectPolicy;
        this.taskQueue = new BlockQueue<>(queueCapacity);
    }

    public void execute(Runnable task){
        synchronized (workers){
            if(workers.size()<poolCapacity){
                Worker worker = new Worker(task);
                log.debug("添加worker对象 {}", worker);
                workers.add(worker);
                worker.start();
            }else{
                log.debug("核心已满 将task {} 添加阻塞队列",task);
                taskQueue.tryPut(task,rejectPolicy);
            }
        }

    }

}

13.2 ThreadPoolExecutor

image-20220622135849975

1,线程池状态

线程池的状态由 int 的高3位组成,低29位组成线程的数量

image-20220622140707645

线程池状态的大小:TERMINATED > TIDIYING > STOP > SHUTDOWN > RUNNING

线程池状态和线程池线程数量由原子变量ctl存储,线程池状态之所以用这种合二为一的方式去存储,是因为如果分别用两个原子变量存储需要进行两次CAS,而这样子只需要执行一次CAS操作

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static int ctlOf(int rs, int wc) { return rs | wc; }
2,构造方法
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
参数介绍

corePoolSize:核心线程数

maximumPoolSize:最大线程数

keepAliveTime:救急线程存活时间

unit:时间处理

workQueue:任务阻塞队列
threadFactory:线程工厂,为线程取名字

handler:拒绝策略

救急线程:

**作用:**当核心线程和阻塞线程都满了时,再次添加任务java线程池不会先调用拒绝策略,而是先开辟一个救急线程来对任务进行处理,当任务执行完毕后,如果救急线程到达它最大存活时间,则销毁该线程

**救急线程的数量:**maximumPoolSize - corePoolSize

image-20220622141300868

拒绝策略

image-20220623013619733

image-20220623013216541

3,工厂方法构造线程池
  • newFixThreadPool 固定大小线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

1,没有救急线程

2,阻塞队列未设置大小,大小“无限”

3,适用于任务量已知,任务量大的线程

  • newCachedThreadPool 带缓冲的线程池
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

1,可以无限次创建线程,且核心线程为0,则代表所有线程都是救急线程,且存活时间为60秒

2,SynchronousQueue队列的容量为0,只有在有线程取任务的时候,才能放入任务,相当于一手交钱,一手交货(底层采用Park/unPark实现)

3,适用于任务量庞大,且每个任务时间较短的情况

  • newSingleThreadExecutor 单线程的线程池
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

1,该线程池只有一个线程,用来处理多任务排队处理的情况

和单线程的区别

  • 当其中一个线程任务失败异常抛出后,还会创建一个新的线程来执行
  • ExecutorService.newSingleThreadExecutor()线程池线程数始终为一,不允许修改

与newFixThreadExecutor(1)不同的是,newSingleThreadExecutor()中的FinalizableDelegatedExecutor,是装饰器模式,只暴露了一些调用方法的接口,不允许更改Executor内部信息,而newFixThreadExecutor返回的是ThreadPoolExecutor,可以通过setCoreCapacity来更改线程数量

  • newScheduledThreadPool 定时执行任务线程池
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

与Timer的区别

1,Timer十分脆弱,当一系列任务中某些任务的时间非常长或者任务出现异常都会影响到后续任务,但是newScheduledThreadPool并不会

shceduleAtiFixedRate与scheduleWithFixedDelay的区别

两个方法都是定时循环执行某个任务,且参数意义都相同

1,shceduleAtiFixedRate 的延时执行时间 取决于 max(任务执行时间,延时时间)

2,scheduleWithFixedDelay 的延时执行时间 取决于 延时时间+任务执行时间

小练手:每周四下午四点定时执行任务
image-20220624174237917

4,提交任务
  • submit

提交任务,并等待返回结果,采用的是保护性暂停设计模式

<T> Future<T> submit(Callable<T> task);
  • invokeAll

提交一系列任务,等待提交的所有任务的返回结果

<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
    throws InterruptedException;
  • invokeAny

提交一系列任务,返回最先执行完的任务结果,且其他任务不会继续执行下去

<T> T invokeAny(Collection<? extends Callable<T>> tasks)
    throws InterruptedException, ExecutionException;
5,关闭线程池
  • shutdown

将线程池的状态设为shutdown

不再接受新的任务

已提交的任务会一直执行完

调用线程不会等待任务全部执行完

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        //将线程池状态设置为shutdown
        advanceRunState(SHUTDOWN);
        //打断空闲线程
        interruptIdleWorkers();
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    //尝试终结线程(没有任务时立即终结,有任务存在也不会等待)
    tryTerminate();
}
  • shutdownNow

将线程池的状态变为STOP

不再接受新的任务,已提交的任务也不再执行,并且返回队列中的任务

正在执行的任务用interrupt进行打断

public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        //修改线程池状态
        advanceRunState(STOP);
        //打断所有线程
        interruptWorkers();
        //返回队列中的任务
        tasks = drainQueue();
    } finally {
        mainLock.unlock();
    }
    //尝试终结
    tryTerminate();
    return tasks;
}
  • 其他方法
 //不在RUNNING状态下的线程池返回True
public boolean isShutdown()
 //线程池是否为Terminating   
public boolean isTerminating()
 //调用shutdown后,调用线程并不会等待线程池中任务执行,此时可以调用awaitTermination进行等待
 public boolean awaitTermination(long timeout, TimeUnit unit)

13.3 创建线程池多少个线程合适呢

  • 过小会导致程序不能充分利用系统资源,导致饥饿
  • 过大会导致更多的线程上下文切换,占用更多内存
1,CPU密集型运算

采用 cpu核数+1 能够实现最优的CPU利用率,+1是保证当前线程由于页故障或其他原因导致暂停时,额外的线程能顶替上去。

2,I/O密集型运算

CPU不总处于繁忙状态,比如数据库增删查改,RFC框架调用,I/0操作,此时你的CPU就会闲下来。

经验公式如下:

线程数 = 核数 * 期望CPU利用率 * 总时间(CPU计算时间+等待时间) / CPU计算时间

例如:4核CPU计算时间是50%,其它等待时间是50%,期望cpu被100%利用

4*100% *100%/ 50% = 8

13.4 Tomcat线程池

image-20220624180953828

  • LimitLatch用来限制流量,控制最大连接数
  • Accpetor负责接受新的socket连接
  • Poller只负责监听socket channle是否有 【可读I/O事件】
  • 一旦读取,把事件封装成【socketProcessor】交给Excutor线程池处理

image-20220624181202589

image-20220624181231465

image-20220624180500536

Tomcat救急线程原理更改

image-20220624180557243

13.5 Fork/Join

1,概念

Fork/Join采用的是一种分治的思想,把一个任务拆解为多个任务,利用多线程来进行拆解,合并,提高运算效率

14 JUC

14.1 AQS

1,概述

全称是AbstractQueuedSynchronizedr,与阻塞式锁相关的同步器工具的框架

特点:

  • 用state来代表资源状态,子类定义如何维护这个状态,控制如何获取锁,释放锁

AQS定义两种资源共享方式

Exclusive(独占):只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁:

  • 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁
  • 非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的

Share(共享):多个线程可同时执行,如Semaphore/CountDownLatch。Semaphore、CountDownLatch、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。

  • 提供了基于FIFO的等待队列,类似于Monitor的EntryList
  • 条件变量来实现等待,唤醒机制,支持多个条件变量,类似于Monitor的WaitSet

image-20220625203233523

14.2,ReentrantLock

相对于synchronized它具备如下特点

  • 可中断
  • 设置超时时间
  • 可以设置为公平锁
  • 支持多个条件变量

与synchronized一样,支持可重入

语法
ReentrantLock reentrantLock = new ReentrantLock();
        reentrantLock.lock();
        try{
            //临界区
        }finally {
            reentrantLock.unlock();
        }
    }
可重入

可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获得这把锁,如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住

 static ReentrantLock Lock = new ReentrantLock();
    public static void main(String[] args) {
       method1();
    }
    public static void method1(){
        try {
           Lock.lock();
           log.debug("come in");
            method2();
        }finally {
            Lock.unlock();
        }
    }
    public static void method2(){
        try {
           Lock.lock();
           log.debug("come in2");
        }finally {
            Lock.unlock();
        }
    }
可打断
public static void main(String[] args){
    Thread t1 = new Thread(()->{
        try {
            log.debug("尝试获取锁");
            lock.lockInterruptibly();
        }catch (InterruptedException e){
            e.printStackTrace();
            log.debug("没有获得锁");
            return;
        }
        try {
            log.debug("获取锁");
        }finally {
            lock.unlock();
        }
    },"t1");
    lock.lock();
    t1.start();
    Sleeper.sleep(1);
    log.debug("打断t1");
    t1.interrupt();
}
锁超时
public static void main(String[] args){
    Thread t1 = new Thread(()->{
        log.debug("尝试获得锁");
        try {
            if(!lock.tryLock(2, TimeUnit.SECONDS)){
                log.debug("获取不到锁");
                return;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
            return;
        }
        try {
            log.debug("获得到锁");
        }finally {
            lock.unlock();
        }

    },"t1");
    lock.lock();
    t1.start();
    sleep(1);
    log.debug("释放了锁");
    lock.unlock();
}
22:51:45.783 [t1] DEBUG JUC.e_ReentrantLock.lockWait - 尝试获得锁
22:51:46.797 [main] DEBUG JUC.e_ReentrantLock.lockWait - 释放了锁
22:51:46.797 [t1] DEBUG JUC.e_ReentrantLock.lockWait - 获得到锁

lock.tryLock():

timeout=0没获取锁,直接返回

timeout>0没获取锁,等待timeout时间,再返回

公平锁

简介:多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁。

ReentrantLock 默认为不公平的

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

*注:公平锁会降低并发度

条件变量

ReentrantLock的条件变量比synchronized强大之处在于,它支持多个条件变量

使用流程
  • await前需要获取锁
  • await执行后,会释放锁,进入conditionObject等待
  • await的线程唤醒后,重新竞争lock锁
  • 竞争lock锁成功后,从await后继续执行

14.3 原理

image-20220625205626823

1,加锁以及竞争锁
  • 在没有线程竞争锁时

image-20220626172830834

Thread线程将state设置为1,且EXCThread变为Thread-0

  • 第一个竞争出现时

image-20220626173058819

final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

会先采用CAS来判断是否能获取锁,CAS失败后调用acquire(1)方法

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

acquire此时还会调用一次tryAcquire(),再进行一次CAS来做最后一次挣扎,看是否能获取锁,如果无法获取则进入acquireQueued逻辑将该线程封装成一个Node加入FIFO队列中

image-20220626174055812

在FIFO队列中

  • 黄色的三角形代表当前Node的waitStatus状态,0代表正常
  • 在阻塞队列中会有一个Null Node节点,当作哨兵节点使用
  • Node创建是懒惰的
  • acquireQueued 逻辑
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

再进入acquireQueued逻辑之后,会先判断该节点的上一个节点是否为头节点,如果为头节点,则再次尝试获取。

如果获取失败,则进入shouldParkAfterFailedAcquire逻辑,将前期的Node节点的waitStatus设置为-1,且这次返回false。

-1的节点代表负责唤醒其后继node.next节点的任务
image-20220626182806742

shouldParkAfterFailedAcquire执行完毕后会再次执行一遍tryAcquire,如果再次获取失败,则进入shouldParkAfterFailedAcquire

,此时返回true,即进入阻塞状态

image-20220626183038234

经历多次竞争后,变成如下这个样子

image-20220626183323473

2,解锁流程
  • 当Thread-0执行结束后,会调用unlock,释放资源

release逻辑

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

此时会先进行tryRelease来尝试解锁,解锁成功后,会将当前执行线程设为null,同时调用unparkSuccessor来唤醒后继线程

image-20220626184408218

  • unparkSuccessor唤醒后继线程
private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}
  • 队列线程(没有竞争),加锁成功

exclusiveOwnerThread设置为Thread-1,state=1

head指向刚刚的Thread-1节点,并将Node.thread设为null,之前的节点离开队列,被GC回收

此时会找到离head最近的一个Node节点,调用unpark来唤醒后继线程,当后继线程被唤醒后,会进行加锁

image-20220626184634884

  • 加锁失败,有其他线程来竞争(非公平锁)

image-20220626222937304

Thread-1再次进入acquireQueued流程,获取锁失败,重新进入park

3,可重入
  • 不公平锁可重入
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        //此时重入的话,则state++
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}
protected final boolean tryRelease(int releases) {
    //解锁时,考虑到可重入,state--
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}
4,可打断
  • 不可打断模式

不可打断模式下的线程可以被外界线程打断,但是并不会因为打断而退出阻塞队列独立运行

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);  //阻塞的线程,被外界线程打断阻塞
    //此时调用interrupted方法,返回true,同时清除打断标记(park只能park,没被打断的线程,方便下次循环后打断)
    return Thread.interrupted();
}
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                //返回被打断的结果
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                //被打断的线程将标记设为空,此时线程并没有出阻塞队列独立运行,而是继续竞争锁
                //1 ,竞争失败 --> parkAndCheckInterrupt 继续阻塞
                //2 ,竞争成功 --> return interrupted
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        //若打断标记为true,则在打断一次,将打断标记设为true,目的是防止再之后运行中,再次被阻塞
        selfInterrupt();
}
static void selfInterrupt() {
    Thread.currentThread().interrupt();
}
  • 可打断模式
private void doAcquireInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                //在自身被打断后,直接抛出异常
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
5,公平锁

与非公平锁不同的是,非公平锁中线程到来即可竞争锁,而公平锁中线程到来先要判断阻塞队列是否有其他队列或者到来线程为阻塞队列第二名,即可竞争锁

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}
public final boolean hasQueuedPredecessors() {
    Node t = tail; 
    Node h = head;
    Node s;
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}
6,条件变量实现原理
await
public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    //将node添加到条件等待队列
    Node node = addConditionWaiter();
    //fullyRelease是获取到锁的全部可重入次数,并将state设为0
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

将Thread-0放入ConditionObject的Waiter双向链表中,并将Node状态设置为-2
image

通过fullyRelease,将原本的Thread-0的锁(不管是否重入)给释放,同时将Thread-1唤醒,使用锁

image

Thread-0阻塞

image

signal
public final void signal() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}

唤醒ConditionObject中的第一个线程

image

private void doSignal(Node first) {
    do {
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}

将第一个Node从链表中删除,同时转换到锁的阻塞队列中,转换失败的话(有可能是被外界线程打断唤醒),则唤醒下一个线程节点,重复上述步骤

image

当成功加入到阻塞队列中时,将前一个节点的的状态设置为-1
image

14.4 ReentrantReadWriteLock

**锁的场景:**在读取操作多于写操作的情况下,对大量读操作的加锁,反而会导致系统性能下降,于是便将读写锁分离,来进行设计

特性:
  • **基本原则:**读读共享,读写互斥,写线程只允许存在一个
  • 读锁没有条件变量,写锁拥有条件变量
  • 读锁不可以升级为写锁(不能进行锁升级),写锁可以降级为读锁(能进行锁降级)
volatile boolean cacheInvalid;
public void lockLevelDown(){
    readLock.lock();
    try {
        if(!cacheInvalid){
           readLock.unlock();
           writeLock.lock();
           try {
               if(!cacheInvalid){
                   data = "Genius";
                   cacheInvalid = true;
               }
           }finally {
               //进行锁降级,当写锁释放开后,其他读锁可以共享缓存
               readLock.lock();
               writeLock.unlock();
           }
        }
    }finally {
        log.debug("data {}",data);
        readLock.unlock();
    }
}
应用:缓存更新

image

image

image

原理:

ReentrantReadWriteLock本质上还是使用的同一个同步器,但是有一个特殊设计的一点,read lock和write lock使用的是同一个state,即read lock使用state的高16位,write lock使用state的低16位

readLock获取锁的状态是: state >>>16 无状态右移动16位

writeLock获取锁的状态是:state & (1<<16-1),将高位补0

t1 w.lock t2 r.lock
protected final boolean tryAcquire(int acquires) {
    /*
     * Walkthrough:
     * 1. If read count nonzero or write count nonzero
     *    and owner is a different thread, fail.
     * 2. If count would saturate, fail. (This can only
     *    happen if count is already nonzero.)
     * 3. Otherwise, this thread is eligible for lock if
     *    it is either a reentrant acquire or
     *    queue policy allows it. If so, update state
     *    and set owner.
     */
    Thread current = Thread.currentThread();
    int c = getState();  //获取当前状态
    int w = exclusiveCount(c); //与低15位1 即c&(1<<16)-1,判断是否为写锁
    if (c != 0) {//状态不为0,即已经被加锁,进入判断
        if (w == 0 || current != getExclusiveOwnerThread())
            /**
            1,w==0 代表加的是读锁,读写互斥,返回false
            2,w!=0 代表加的是写锁,判断持有锁的线程是否为本线程,若为本线程,则锁可重入加锁成功
            **/
            return false;
        if (w + exclusiveCount(acquires) > MAX_COUNT)// 可重入锁是否达到上限制
            throw new Error("Maximum lock count exceeded");
        // Reentrant acquire
        setState(c + acquires);
        return true;
    }
    //c==0的话代表没有人加锁,此时由于非公平锁,writerShouldBlock()始终返回false,判断是否能正常CAS,如果可以说明没有竞争,占用锁
    /*state=0才能走到此处,writerShouldBlock()是用来判断是否需要排队
            非公平锁:
                直接返回false,然后使用CAS来修改state,如果修改成功则说明拿到锁了
                那么可以继续将独占锁的持有线程设置为当前线程。
            公平锁:
                公平锁会调用hasQueuedPredecessors(),这个方法是判断入口等待队列
                中是否有线程在等待锁。如果没有线程等待或者等待队列中排在最前边的是当前
                线程,那么可以继续进行后边的CAS操作,否则返回false。
   */
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}

1) t1申请W锁,此时state为0所以自然而然可以申请到W锁

image

tryAcquireShared 返回值

val=-1 代表获取锁失败

val=1代表获取锁成功

val>1 代表获取锁成功并唤醒val数量的线程

注:

共享锁会为每个获取ReadLock的线程创建一个HoldCounter来记录该线程的线程ID和获取ReadLock的次数(包括重入)。并将这个HoldCounter对象保存在线程自己的ThreadLocal中。

ThreadLocalHoldCounter readHolds;
HoldCounter cachedHoldCounter;
protected final int tryAcquireShared(int unused) {
    Thread current = Thread.currentThread();
    int c = getState();
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        //判断是否为写锁,且是否可以进行锁降级
        return -1;
    int r = sharedCount(c);
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        compareAndSetState(c, c + SHARED_UNIT)) {
        if (r == 0) {
            /**设计者考虑到只有一个线程使用读锁重入的情况,这个时候不使用ThreadLocal里面的HoldCounter这样会消耗性能,便使用reentrantLock中的firstReaderHoldCount来记录**/
            firstReader = current;
            firstReaderHoldCount = 1;
        } else if (firstReader == current) {
            firstReaderHoldCount++;
        } else {
            //多个线程来申请读锁会到这一步,从cacheHoldCounter中拿
            HoldCounter rh = cachedHoldCounter;
            /**
              1,如果rh.tid == getThreadId(current) 说明拿过两次
              2,如果rh.tid != getThreadId(current) 说明缓存失效,重新从ThreadLocal里面取出HoldCounter,并且修改缓存
              **/
            if (rh == null || rh.tid != getThreadId(current))
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
            rh.count++;
        }
        return 1;
    }
    return fullTryAcquireShared(current);
}

3)此时t2申请R锁,此时state低位16不为0,代表已经有写锁,且不为自己加的写锁,此时返回-1进入阻塞队列

image

private void doAcquireShared(int arg) {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

4)添加两个SHARED节点,且再次进行tryAcquire,如果失败,则阻塞
image

t3 r.lock t4 w.lock

5)这种状态下,假设又有t3加读锁和t4加写锁,此时t1仍然持有锁,就会进入如下状态

注:

写锁加的是EX Node节点

读锁加的是 Shared Node节点

image

t1 w.unlock

6)此时t1会释放锁,如果释放成功,则会唤醒后续线程(和reentrantLock原理一致s)

image

7)此时t2被t1唤醒后抢占到锁(无竞争的情况下),此时将自己的节点设置为头节点,此时并没有结束,他会调用

setHeadAndPropagate(node, r)方法,来唤醒后续所有为shared结点的线程

private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head; // Record old head for check below
    setHead(node);
 
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        Node s = node.next;
        if (s == null || s.isShared())//唤醒后续为
            doReleaseShared();
    }
}

image

 private void doReleaseShared() {
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) //此时将节点的状态设置为0(目的是为了防止有其他线程干扰,如果有线程进来发现是-1会再唤醒一次) 不是很懂???
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

8)此时t2的节点状态被设置为0,且t3被t2唤醒,t3重复t2的过程获取锁并且唤醒后继shared节点

image
9)后继节点为Ex 所以唤醒结束,t2,t3占有锁,且状态为2_0

image

t2 r.unlock() t3 r.unlock()
protected final boolean tryReleaseShared(int unused) {
    Thread current = Thread.currentThread();
    //重入读锁解锁
    if (firstReader == current) {
        // assert firstReaderHoldCount > 0;
        if (firstReaderHoldCount == 1)
            firstReader = null;
        else
            firstReaderHoldCount--;
    } else {
        HoldCounter rh = cachedHoldCounter;
        if (rh == null || rh.tid != getThreadId(current))
            rh = readHolds.get();
        int count = rh.count;
        if (count <= 1) {
            readHolds.remove();
            if (count <= 0)
                throw unmatchedUnlockException();
        }
        --rh.count;
    }
    //解锁
    for (;;) {
        int c = getState();
        int nextc = c - SHARED_UNIT;
        if (compareAndSetState(c, nextc))
            //可重入锁全部解锁则返回true
            return nextc == 0;
    }
}

10)此时t3解锁(正常流程解锁),state减为1,read lock并没有被全部解锁,不唤醒后续线程

image

11)此时t2解锁,state减为0,read lock被全部解锁,唤醒后续线程

image

12)t4节点被唤醒成功,获取读锁

image

流程图
读锁

image

写锁

image

14.5 StampedLock

由于ReentrantReadWriteLock的需要加锁来实现读读并发,读写并发,性能肯定不如不加锁的并发状态,此时StampedLock可以完美解决这个问题,他拥有更高的性能,每一次使用读锁和写锁都必须要配合一个【戳】
image
image

StampedLock性能比ReadWriteLock好的原因
  • ReadWriteLock,有大量读操作时,会导致写操作饥饿,一直获取不到锁,而StampedLock采用乐观读的方式,即使在大量读操作下也是可以获取写锁的
  • ReadWriteLock的读操作,每一次都需要CAS更改state,但是StampedLock乐观读的方式无需使用CAS,可以获得更高的性能
原理
StampedLock的状态

StampedLock和ReadWriteLock的状态形式差不多,也是采用同步状态按位切分

64位state 状态切分

0~7位:读锁获取数量即状态 (可存放128个读锁,超过上限用readOverFlow来存储)

8位:写锁的状态

8~64位:写锁的数量

写锁使用状态

此处考虑到了ABA问题

  1. 检查是否有写锁,state 第 8 位为 0,没有写锁,拷贝数据;
  2. 检查是否有线程获取过写锁,state 第 8 位为 0,没有线程获取过,直接使用原来拷贝的数据。

第一次检查 state 第 8 位为 0 之后,有线程获取写锁修改数据并释放了写锁,那么之后在检查是否有线程获取过写锁时 state 第 8 位还是 0,认为没有线程获取过写锁,可能导致数据不一致。

因此写锁的状态十分巧妙,当获取写锁时,将state的第8位设置为1,释放写锁时在state第8位+1,此时即同步了写锁数量,又改变了写锁状态

锁状态相关属性
// 一个单位的读锁        0000... 0000 0000 0001
private static final long RUNIT = 1L;   
 
// 一个单位的写锁        0000... 0000 1000 0000                
private static final long WBIT = 1L << LG_READERS;   
 
// 读状态标识            0000... 0000 0111 1111  
private static final long RBITS = WBIT - 1L; 
 
// 读锁最大数量          0000... 0000 0111 1110           
private static final long RFULL = RBITS - 1L;    
 
// 用于获取读写状态      0000... 0000 1111 1111       
private static final long ABITS = RBITS | WBIT;  
 
//                       1111... 1111 1000 0000       
private static final long SBITS = ~RBITS;               
 
// 锁state初始值,0000... 0001 0000 0000
private static final long ORIGIN = WBIT << 1;
 
/** 锁队列状态, 当处于写模式时第8位为1,读模式时前7为为1-126
(附加的readerOverflow用于当读者超过126时) */
private transient volatile long state;
 
/** 将state超过 RFULL=126的值放到readerOverflow字段中 */
private transient int readerOverflow;
节点

StampedLockd的等待队列和AQS差不多,只是多了一个栈来保存读线程

// 结点状态
private static final int WAITING = -1;
private static final int CANCELLED = 1;
 
// 结点的读写模式
private static final int RMODE = 0;
private static final int WMODE = 1;
 
/** Wait nodes */
static final class WNode {
    volatile WNode prev;
    volatile WNode next;
    volatile WNode cowait; // 读模式使用该结点形成栈
    volatile Thread thread; // non-null while possibly parked
    volatile int status; // 0, WAITING, or CANCELLED
    final int mode; // RMODE or WMODE
 
    WNode(int m, WNode p) {
        mode = m;
        prev = p;
    }
}
 
/** CLH队头结点 */
private transient volatile WNode whead;
/** CLH队尾结点 */
private transient volatile WNode wtail;
自旋次数优化

image

注意
  1. 需要特别注意,那就是:如果线程阻塞在 StampedLock 的 readLock() 或者 writeLock() 上时,此时调用该阻塞线程的 interrupt() 方法,会导致 CPU 飙升。例如下面的代码中,线程 T1 获取写锁之后将自己阻塞,线程 T2 尝试获取悲观读锁,也会阻塞;如果此时调用线程 T2 的 interrupt() 方法来中断线程 T2 的话,你会发现线程 T2 所在 CPU 会飙升到 100%。
  2. StampedLock 支持锁的降级(通过 tryConvertToReadLock() 方法实现)和升级(通过 tryConvertToWriteLock() 方法实现),但是建议你要慎重使用。下面的代码也源自 Java 的官方示例,隐藏了一个 Bug,就是在锁升级成功的时候,最后没有释放最新的写锁,可以在if块的break上加个stamp=ws进行释放。

14.6 Semaphore

信号量,用来限制能同时访问共享资源的线程上限

public static void main(String[] args) {
    Semaphore semaphore = new Semaphore(3);
    for (int i = 0; i <10 ; i++) {
        new Thread(()->{
           try {
               semaphore.acquire();
               Thread.sleep(2000);
               log.debug("Thread {}",Thread.currentThread().getName());
           }catch (InterruptedException e){
               e.printStackTrace();
           }finally {
               semaphore.release();
           }
        },"t"+i).start();
    }

}
Semaphore应用

image

在限流方面并不咋样,最好还是采用分布式锁g

更改数据库连接池
public Connection getConn2(){
    try {
        semaphore.acquire();
    }catch (InterruptedException e){
        e.printStackTrace();
    }
    for (int i = 0; i <this.poolSize ; i++) {
        if(state.compareAndSet(i,0,1)){
            Connection conn = connection[i];
            log.debug("get {}",conn);
            return conn;
        }
    }
    return null;
}

public void free2(Connection conn){
    for (int i = 0; i <poolSize ; i++) {
        if(conn == connection[i]){
            state.set(i,0);
            log.debug("{} free",conn);
            semaphore.release();
            break;
        }
    }
}
原理

Semaphore就类似于停车场,开放多少资源,state就有多少,每有一个线程获取资源时,state减1,直到<0的时候,线程在获取即进入阻塞队列,总体的解锁和加锁过程还是和ReadWriteLock一样。

加锁,CAS state,不为0拿到锁,为0进入阻塞。

image

解锁,释放资源,state加一,并且唤醒后继线程

image

14.7 CountdownLatch

用于等待多个线程任务共同执行结束,进行同步的一个锁,state=n,则代表有n个线程正在执行,线程执行完毕调用CountDown()方法来将state-1,当state为0时,等待线程结束等待,被唤醒。

为什么不用Join

Join需要等待线程生命周期结束,才会开始执行任务,而一个线程可能要完成多项任务,可能需要同步的过程发生在中途,此时就无法使用Join

应用
等待多个线程准备完毕
ExecutorService service = Executors.newFixedThreadPool(10);
String[] all = new String[10];
CountDownLatch countDownLatch = new CountDownLatch(10);
for(int i=0;i<10;i++){
    final int flag = i;
    service.submit(()->{
        try {
            for (int j = 0; j <=100; j++) {
                Thread.sleep(new Random().nextInt(100)+1);
                all[flag] = j+"%" ;
                System.out.print("\r"+ Arrays.toString(all));
            }
            countDownLatch.countDown();
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    });
}
countDownLatch.await();
System.out.println("Game Start");
等待多个远程调用结束

14.8 CyclicBarrier

利用一个计数器,来记录阻塞的线程数,每有一个阻塞的线程进入,将会减少count,当count减少为0时,则将唤醒所有阻塞线程,以及调用自身的Runnable线程(如果有的话),共同向下执行

private int dowait(boolean timed, long nanos)
    throws InterruptedException, BrokenBarrierException,
           TimeoutException {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        final Generation g = generation;
        if (g.broken)
            throw new BrokenBarrierException();

        if (Thread.interrupted()) {
            breakBarrier();
            throw new InterruptedException();
        }
        int index = --count;//自减判断
        //当index==0;唤醒所有的阻塞线程
        if (index == 0) { 
            boolean ranAction = false;
            try {
                final Runnable command = barrierCommand;
                if (command != null)
                    command.run();//执行CyclicBarrier的任务
                ranAction = true;
                nextGeneration();//将count恢复,并且唤醒所有线程
                return 0;
            } finally {
                if (!ranAction)
                    breakBarrier();
            }
        }
        //自旋阻塞
        for (;;) {
            try {
                if (!timed)
                    trip.await();
                else if (nanos > 0L)
                    nanos = trip.awaitNanos(nanos);
            } catch (InterruptedException ie) {
                if (g == generation && ! g.broken) {
                    breakBarrier();
                    throw ie;
                } else {
       
                    Thread.currentThread().interrupt();
                }
            }

            if (g.broken)
                throw new BrokenBarrierException();

            if (g != generation)
                return index;

            if (timed && nanos <= 0L) {
                breakBarrier();
                throw new TimeoutException();
            }
        }
    } finally {
        lock.unlock();
    }
}
应用
ExecutorService service = Executors.newFixedThreadPool(2);
CyclicBarrier cyclicBarrier = new CyclicBarrier(2,()->{
    log.debug("GO GO GO");
});
for (int i = 0; i <3 ; i++) {
    service.submit(()->{
        try {
            log.debug("{} start",Thread.currentThread().getName());
            cyclicBarrier.await();
            log.debug("{} end",Thread.currentThread().getName());
        }catch (InterruptedException | BrokenBarrierException e){
            e.printStackTrace();
        }
    });
    service.submit(()->{
        try {
            log.debug("{} start",Thread.currentThread().getName());
            Thread.sleep(1000);
            cyclicBarrier.await();
            log.debug("{} end",Thread.currentThread().getName());
        }catch (InterruptedException | BrokenBarrierException e){
            e.printStackTrace();
        }
    });
}
  0000... 0000 0000 0001

private static final long RUNIT = 1L;

// 一个单位的写锁 0000… 0000 1000 0000
private static final long WBIT = 1L << LG_READERS;

// 读状态标识 0000… 0000 0111 1111
private static final long RBITS = WBIT - 1L;

// 读锁最大数量 0000… 0000 0111 1110
private static final long RFULL = RBITS - 1L;

// 用于获取读写状态 0000… 0000 1111 1111
private static final long ABITS = RBITS | WBIT;

// 1111… 1111 1000 0000
private static final long SBITS = ~RBITS;

// 锁state初始值,0000… 0001 0000 0000
private static final long ORIGIN = WBIT << 1;

/** 锁队列状态, 当处于写模式时第8位为1,读模式时前7为为1-126
(附加的readerOverflow用于当读者超过126时) */
private transient volatile long state;

/** 将state超过 RFULL=126的值放到readerOverflow字段中 */
private transient int readerOverflow;


#### 节点

StampedLockd的等待队列和AQS差不多,只是多了一个栈来保存读线程

```java
// 结点状态
private static final int WAITING = -1;
private static final int CANCELLED = 1;
 
// 结点的读写模式
private static final int RMODE = 0;
private static final int WMODE = 1;
 
/** Wait nodes */
static final class WNode {
    volatile WNode prev;
    volatile WNode next;
    volatile WNode cowait; // 读模式使用该结点形成栈
    volatile Thread thread; // non-null while possibly parked
    volatile int status; // 0, WAITING, or CANCELLED
    final int mode; // RMODE or WMODE
 
    WNode(int m, WNode p) {
        mode = m;
        prev = p;
    }
}
 
/** CLH队头结点 */
private transient volatile WNode whead;
/** CLH队尾结点 */
private transient volatile WNode wtail;
自旋次数优化

[外链图片转存中…(img-oCBN6GaF-1660116052667)]

注意
  1. 需要特别注意,那就是:如果线程阻塞在 StampedLock 的 readLock() 或者 writeLock() 上时,此时调用该阻塞线程的 interrupt() 方法,会导致 CPU 飙升。例如下面的代码中,线程 T1 获取写锁之后将自己阻塞,线程 T2 尝试获取悲观读锁,也会阻塞;如果此时调用线程 T2 的 interrupt() 方法来中断线程 T2 的话,你会发现线程 T2 所在 CPU 会飙升到 100%。
  2. StampedLock 支持锁的降级(通过 tryConvertToReadLock() 方法实现)和升级(通过 tryConvertToWriteLock() 方法实现),但是建议你要慎重使用。下面的代码也源自 Java 的官方示例,隐藏了一个 Bug,就是在锁升级成功的时候,最后没有释放最新的写锁,可以在if块的break上加个stamp=ws进行释放。

14.6 Semaphore

信号量,用来限制能同时访问共享资源的线程上限

public static void main(String[] args) {
    Semaphore semaphore = new Semaphore(3);
    for (int i = 0; i <10 ; i++) {
        new Thread(()->{
           try {
               semaphore.acquire();
               Thread.sleep(2000);
               log.debug("Thread {}",Thread.currentThread().getName());
           }catch (InterruptedException e){
               e.printStackTrace();
           }finally {
               semaphore.release();
           }
        },"t"+i).start();
    }

}
Semaphore应用

[外链图片转存中…(img-Jg5Udeti-1660116052668)]

在限流方面并不咋样,最好还是采用分布式锁g

更改数据库连接池
public Connection getConn2(){
    try {
        semaphore.acquire();
    }catch (InterruptedException e){
        e.printStackTrace();
    }
    for (int i = 0; i <this.poolSize ; i++) {
        if(state.compareAndSet(i,0,1)){
            Connection conn = connection[i];
            log.debug("get {}",conn);
            return conn;
        }
    }
    return null;
}

public void free2(Connection conn){
    for (int i = 0; i <poolSize ; i++) {
        if(conn == connection[i]){
            state.set(i,0);
            log.debug("{} free",conn);
            semaphore.release();
            break;
        }
    }
}
原理

Semaphore就类似于停车场,开放多少资源,state就有多少,每有一个线程获取资源时,state减1,直到<0的时候,线程在获取即进入阻塞队列,总体的解锁和加锁过程还是和ReadWriteLock一样。

加锁,CAS state,不为0拿到锁,为0进入阻塞。

[外链图片转存中…(img-9iO7r1vC-1660116052668)]

解锁,释放资源,state加一,并且唤醒后继线程

[外链图片转存中…(img-msyPbPWu-1660116052668)]

14.7 CountdownLatch

用于等待多个线程任务共同执行结束,进行同步的一个锁,state=n,则代表有n个线程正在执行,线程执行完毕调用CountDown()方法来将state-1,当state为0时,等待线程结束等待,被唤醒。

为什么不用Join

Join需要等待线程生命周期结束,才会开始执行任务,而一个线程可能要完成多项任务,可能需要同步的过程发生在中途,此时就无法使用Join

应用
等待多个线程准备完毕
ExecutorService service = Executors.newFixedThreadPool(10);
String[] all = new String[10];
CountDownLatch countDownLatch = new CountDownLatch(10);
for(int i=0;i<10;i++){
    final int flag = i;
    service.submit(()->{
        try {
            for (int j = 0; j <=100; j++) {
                Thread.sleep(new Random().nextInt(100)+1);
                all[flag] = j+"%" ;
                System.out.print("\r"+ Arrays.toString(all));
            }
            countDownLatch.countDown();
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    });
}
countDownLatch.await();
System.out.println("Game Start");
等待多个远程调用结束

14.8 CyclicBarrier

利用一个计数器,来记录阻塞的线程数,每有一个阻塞的线程进入,将会减少count,当count减少为0时,则将唤醒所有阻塞线程,以及调用自身的Runnable线程(如果有的话),共同向下执行

private int dowait(boolean timed, long nanos)
    throws InterruptedException, BrokenBarrierException,
           TimeoutException {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        final Generation g = generation;
        if (g.broken)
            throw new BrokenBarrierException();

        if (Thread.interrupted()) {
            breakBarrier();
            throw new InterruptedException();
        }
        int index = --count;//自减判断
        //当index==0;唤醒所有的阻塞线程
        if (index == 0) { 
            boolean ranAction = false;
            try {
                final Runnable command = barrierCommand;
                if (command != null)
                    command.run();//执行CyclicBarrier的任务
                ranAction = true;
                nextGeneration();//将count恢复,并且唤醒所有线程
                return 0;
            } finally {
                if (!ranAction)
                    breakBarrier();
            }
        }
        //自旋阻塞
        for (;;) {
            try {
                if (!timed)
                    trip.await();
                else if (nanos > 0L)
                    nanos = trip.awaitNanos(nanos);
            } catch (InterruptedException ie) {
                if (g == generation && ! g.broken) {
                    breakBarrier();
                    throw ie;
                } else {
       
                    Thread.currentThread().interrupt();
                }
            }

            if (g.broken)
                throw new BrokenBarrierException();

            if (g != generation)
                return index;

            if (timed && nanos <= 0L) {
                breakBarrier();
                throw new TimeoutException();
            }
        }
    } finally {
        lock.unlock();
    }
}
应用
ExecutorService service = Executors.newFixedThreadPool(2);
CyclicBarrier cyclicBarrier = new CyclicBarrier(2,()->{
    log.debug("GO GO GO");
});
for (int i = 0; i <3 ; i++) {
    service.submit(()->{
        try {
            log.debug("{} start",Thread.currentThread().getName());
            cyclicBarrier.await();
            log.debug("{} end",Thread.currentThread().getName());
        }catch (InterruptedException | BrokenBarrierException e){
            e.printStackTrace();
        }
    });
    service.submit(()->{
        try {
            log.debug("{} start",Thread.currentThread().getName());
            Thread.sleep(1000);
            cyclicBarrier.await();
            log.debug("{} end",Thread.currentThread().getName());
        }catch (InterruptedException | BrokenBarrierException e){
            e.printStackTrace();
        }
    });
}
  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值