Java 多线程并发LockSupport

目录
  • 概览
  • 源码分析
    • 静态方法
    • Blocker
    • unpark
    • Unsafe 的 unpark 方法
    • park
    • 不带 blocker 参数的分组
    • 需要 blocker 参数的分组
  • park/unpark 和 Object 的 wait/notify
    • 区别

概览

这部分内容来自于这个类的注释,简单翻译了下。

LockSupport 类是用于创建锁和其他同步类的基本线程阻塞原语。

它的实现思想是给每个使用它的线程颁发一个许可,当许可是可用状态时(线程有许可),调用 park 方法会消耗一个许可,方法立即返回(与信号量的作用类似),线程可以继续执行 park 方法后面的逻辑;如果调用 park 方法前,许可处于不可用状态(线程没有许可),park 方法不会立即返回,从而导致线程阻塞。而此时,可以通过调用 unpark 方法使许可恢复到可用状态(但与信号量不同,许可不会累积。最多有一个)。

方法 park 和 unpark 提供了阻塞和解除阻塞线程的有效方法,这些线程不会遇到 Thread.suspend 和 Thread.resume 存在的问题(因suspend 容易导致死锁,这俩个方法因为这个原因已经被弃用 ),因为 park 和 unpark 调用的线程,不存在锁竞争。

而如果调用 park 方法的线程被中断,park 方法将会立即 return ,并且有设置超时版本的 park 方法。park 方法也可以在任何其他时间没有原因的 return,因此通常必须在返回时重新检查条件的循环中调用。从这个意义上说,park 是对“繁忙等待”的优化,它不会浪费太多时间旋转,但必须与 unpark 配对才能有效。

park 方法有对应带有 blocker 对象参数的重载方法, blocker 对象在线程被阻塞时被记录,以允许监视和诊断工具识别线程被阻塞的原因。 (此类工具可以使用 getBlocker(Thread) 方法访问阻止程序。)强烈建议使用这些表单而不是没有此参数的原始表单。 在锁实现中作为阻塞器提供的正常参数是 this。

本质上 LockSupport 实现了一种自旋,构造类似于:

while (!canProceed()) { ... LockSupport.park(this); }

其中 `canProceed 或在调用之前的任何其他操作都不会导致锁定或阻塞。 因为每个线程只有一个许可,所以任何对 park 的中间使用都可能会干扰其预期效果。

示例用法。 这是一个先进先出不可重入锁类的草图:

 class FIFOMutex {
   private final AtomicBoolean locked = new AtomicBoolean(false);
   private final Queue<Thread> waiters
     = new ConcurrentLinkedQueue<Thread>();
   public void lock() {
     boolean wasInterrupted = false;
     Thread current = Thread.currentThread();
     waiters.add(current);
     // Block while not first in queue or cannot acquire lock
     while (waiters.peek() != current ||
            !locked.compareAndSet(false, true)) {
       LockSupport.park(this);
       if (Thread.interrupted()) // ignore interrupts while waiting
         wasInterrupted = true;
     }
     waiters.remove();
     if (wasInterrupted)          // reassert interrupt status on exit
       current.interrupt();
   }
   public void unlock() {
     locked.set(false);
     LockSupport.unpark(waiters.peek());
   }
 }

源码分析

整个 LockSupport 类的代码量还算少,去掉注释仅有 100 行,所有的属性和方法都是静态的,并且备注明确说明了 LockSupport 无法实例化:

public class LockSupport {
    private LockSupport() {} // 无法实例化
    // ...
}

LockSupport 中包含了几个私有的内部静态属性:

public class LockSupport {
    // 通过内部 API 实现 Hotspot
    private static final Unsafe U = Unsafe.getUnsafe();
    private static final long PARKBLOCKER= U.objectFieldOffset(Thread.class, "parkBlocker");
    private static final long TID = U.objectFieldOffset(Thread.class, "tid");
    // ...
}

从这些内部私有的静态属性可以看出,最重要的就是 U 了,LockSupport 中的方法,本质上也是调用U 提供的能力。U 在 CAS 与原子类中有介绍,是 JDK 中提供的一些非阻塞线程安全的实现能力的类,它的大多数方法都是 native 方法。

静态方法

LockSupport 基本上就是个静态工具类,它的主要能力,集中在它的静态方法中。

public class LockSupport {
    public static void unpark(Thread thread)
    public static void park(Object blocker)
    public static void parkNanos(Object blocker, long nanos)
    public static void parkUntil(Object blocker, long deadline)
    public static Object getBlocker(Thread t)
    public static void setCurrentBlocker(Object blocker)
    private static void setBlocker(Thread t, Object arg)
    public static void park()
    public static void parkNanos(long nanos)
    public static void parkUntil(long deadline)
    static final long getThreadId(Thread thread)
}

从方法名就可以看出,主要分为三个:

  • unpark
  • park
  • getBlocker

Blocker

public static Object getBlocker(Thread t) {
    if (t == null)
        throw new NullPointerException();
    return U.getReferenceOpaque(t, PARKBLOCKER);
}

private static void setBlocker(Thread t, Object arg) {
    U.putReferenceOpaque(t, PARKBLOCKER, arg);
}
// 在 JDK 14 之前是不存在该方法的, setBlocker 只能从内部进行
public static void setCurrentBlocker(Object blocker) {
    U.putReferenceOpaque(Thread.currentThread(), PARKBLOCKER, blocker);
}

setCurrentBlocker 的作用是,设置当前线程调用 getBlocker 返回的对象。 在 JDK 14 后暴露这个方法的用途,是用来配合park() 的无参数版本设置 Blocker ,它可以实现 park(blocker) 的效果:

 setCurrentBlocker(b);
 park();
 setCurrentBlocker(null);

而私有静态方法 setBlocker 是在 park 的有参数方法中封装使用的。

对于 blocker 的保存,本质是通过 Unsafe 的 putReferenceOpaque 方法保存和 getReferenceOpaque 方法读取的。

@IntrinsicCandidate
public final void putReferenceOpaque(Object o, long offset, Object x) {
    putReferenceVolatile(o, offset, x);
}
// 使用 volatile 存储语义将引用值存储到给定的 Java 变量中。 否则等同于 putReference(Object, long, Object)
@IntrinsicCandidate
public native void putReferenceVolatile(Object o, long offset, Object x);

@IntrinsicCandidate
public final Object getReferenceOpaque(Object o, long offset) {
    return getReferenceVolatile(o, offset);
}
// 从给定的 Java 变量中获取引用值,具有可变加载语义。 否则等同于 getReference(Object, long)
@IntrinsicCandidate
public native Object getReferenceVolatile(Object o, long offset);

在 src/hotspot/share/opto/library_call.cpp 中发现了 Java 到 native 方法的映射:

case vmIntrinsics::_putReferenceVolatile: return inline_unsafe_access( is_store, T_OBJECT,   Volatile, false);

native 最终调用到是老朋友 inline_unsafe_access 。(这里不详细展开了。。我也没搞明白这个方法,和汇编指令相关)

需要注意的是,最终的都是将对象设置为了 volatile 。充分说明 LockSupport 也是一套非阻塞同步方案。

而上面提到的 putReference(Object, long, Object)的作用是:将引用值存储到给定的 Java 变量中。

@IntrinsicCandidate
public native void putReference(Object o, long offset, Object x);

除非存储的引用 x 为 null 或与字段类型匹配,否则结果是未定义的。 如果引用 o 不为空,则更新该对象的卡片标记或其他存储屏障(如果 VM 需要它们)。

unpark

public static void unpark(Thread thread) {
    if (thread != null)
        U.unpark(thread);
}

解除调用 park 的线程的阻塞状态,或者如果调用 park 的线程没有阻塞,则会导致后续调用 park 不会造成阻塞。

注意:这个操作是不安全的,调用者必须确保线程没有被销毁。

这是什么意思呢?通过下面这个例子可以感受到先调用 unpark 后,指定参数中的线程参数对象调用 park 不会造成阻塞:

class LockSupportDemo {
    fun check() {
        val thread1 = Thread {
            Thread.sleep(1000)
            println("thread1 start + ${Date(System.currentTimeMillis())}")
            LockSupport.park()
            println("thread1 end +  ${Date(System.currentTimeMillis())}")
        }
​
        val thread2 = Thread {
            println("thread2 start +  ${Date(System.currentTimeMillis())}")
            LockSupport.unpark(thread1)
            println("thread2 end +  ${Date(System.currentTimeMillis())}")
        }
        thread1.start()
        thread2.start()
    }
}

打印日志:

thread2 start +  Fri Jun 03 02:19:34 CST 2022
thread2 end +  Fri Jun 03 02:19:34 CST 2022
thread1 start + Fri Jun 03 02:19:35 CST 2022
thread1 end +  Fri Jun 03 02:19:35 CST 2022

thread2 优先执行,thread1 在 thread2 开始执行 1s 后执行,thread1 调用了 LockSupport.park() ,并没有造成自身阻塞。

Unsafe 的 unpark 方法

LockSupport.unpark(thread) 内部实际只调用了 Unsafe#unpark(thread);

public native void unpark(Object thread);
复制代码

又是一个 native 方法。在 JDK 中发现 Parker::unpark 的定义在 os_posix.cpp 和 on_windows.cpp 中:

可以看出这个 native 方法,应该在不同的平台会有不同的实现。

park

park 方法有两组重载方法:

// 不带 blocker
public static void park()
public static void parkNanos(long nanos)
public static void parkUntil(long deadline)
// 需要 blocker 参数
public static void park(Object blocker)
public static void parkNanos(Object blocker, long nanos)
public static void parkUntil(Object blocker, long deadline)

不带 blocker 参数的分组

没有 blocker 参数的一组本质上的逻辑是:

U.park(boolean, long);
复制代码

这一点可以从三个方法中看出:

public static void park() {
    U.park(false, 0L);
}
public static void parkNanos(long nanos) {
    if (nanos > 0)
        U.park(false, nanos);
}
public static void parkUntil(long deadline) {
    U.park(true, deadline);
}

这三个方法的区别是:

park

方法的作用是:除非许可处于可用状态,否者关闭当前线程的线程调度的意图。如果许可可用,则许可被使用掉,并立即返回。否则当前线程因为线程调度的意图被关闭而导致阻塞。

直到发生以下三种情况之一:

其他线程以当前线程为目标调用 unpark

其他线程中断当前线程

调用虚假地 return(即 no reason)

这个方法不会报告是哪一种原因导致的 return。调用者应该重新检查导致线程第一次停止的条件。 例如,调用者还可以确定线程在返回时的中断状态。

parkNanos

禁用当前线程的线程调度意图,直到指定的等待时间,除非许可可用。如果参数 nanos 为 0 或为负数,这个方法将不会做任何事情。

parkUntil

禁用当前线程的线程调度意图,直到指定的最后期限,除非许可可用。参数 deadline 是截止日期——从 Epoch 开始等待的绝对时间,以毫秒为单位。

需要 blocker 参数的分组

public static void park(Object blocker) {
    Thread t = Thread.currentThread();
    setBlocker(t, blocker);
    U.park(false, 0L);
    setBlocker(t, null);
}
public static void parkNanos(Object blocker, long nanos) {
    if (nanos > 0) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        U.park(false, nanos);
        setBlocker(t, null);
    }
}
public static void parkUntil(Object blocker, long deadline) {
    Thread t = Thread.currentThread();
    setBlocker(t, blocker);
    U.park(true, deadline);
    setBlocker(t, null);
}

带有 blocker 参数的这组函数,它们和对应的不带参数的方法意义是一样的,多了一个设置对象的操作。而这个对象 blocker 是负责此线程 parking 的同步对象 。

这组重载方法有共同的逻辑:

  • 获取当前线程对象。
  • 将 blocker 对象的引用值存储到线程对象中。
  • 调用 Unsafe 对象的 park(boolean, long) 方法。
  • 将当前线程对象存储的引用值设置为 null 。

让我困惑的是,这个存储引用值操作有什么作用。但是联想到线程操作和对象,惊奇的发现我们常用的 API Object.wait() 和 Object.notify() ,好像和这个场景很像,都用到了一个 Object ,也都造成了线程阻塞唤醒。

park/unpark 和 Object 的 wait/notify

public class LockSupportJava {
    Object obj = new Object();​
    public static void main(String[] args) {
        LockSupportJava lock = new LockSupportJava();
        lock.waitAndNotify();
    }
    void waitAndNotify() {
        Thread thread1 = new Thread(() -> {
            synchronized(obj) {
                try {
                    System.out.println("thread1 start + " + new Date(System.currentTimeMillis()));
                    Thread.sleep(1000);
                    obj.notify();
                    System.out.println("thread1 end + " + new Date(System.currentTimeMillis()));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });​
        Thread thread2 = new Thread(() -> {
            synchronized(obj) {
                try {
                    thread1.start();
                    Thread.sleep(3000);
                    System.out.println("thread2 start + " + new Date(System.currentTimeMillis()));
                    obj.wait();
                    System.out.println("thread2 end + " + new Date(System.currentTimeMillis()));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread2.start();
    }
}

这是一个简单的 Demo ,创建了两个线程 thread1 和 thread2 。

线程 2 从主线程先执行,持有了 obj对象的锁,在它的执行逻辑中:

  • 先启动线程 1
  • 线程 2 睡眠 3 秒
  • 三秒后打印 start,obj 对象调用 wait ,使当前线程让出对象的锁,并进入阻塞状态。

此时,线程 1 可以获取到 obj 对象的锁了,它的执行逻辑:

  • 线程 1 开始打印 start
  • 睡眠 1 秒
  • obj 对象调用 notify ,线程 2 开始尝试获取 obj 的锁。
  • 打印 end ,执行结束,让出 obj 的锁

最后,线程 2 重新获取到了 obj 的锁,继续执行打印 end 。

从打印日志中,验证打印顺序:

thread2 start + Fri Jun 03 04:02:17 CST 2022
thread1 start + Fri Jun 03 04:02:17 CST 2022
thread1 end + Fri Jun 03 04:02:18 CST 2022
thread2 end + Fri Jun 03 04:02:18 CST 2022

注意:使用wait/notify实现同步时,必须先调用wait,后调用notify,如果先调用notify,再调用wait,会导致线程一直阻塞。

而如果使用 park / unpark 实现一个阻塞唤醒效果:

    Thread thread;
    void parkAndUnpark() {
        Thread thread2 = new Thread(() -> {
            System.out.println("thread2 start + " + new Date(System.currentTimeMillis()));
            thread.start();
            LockSupport.park(obj);
            System.out.println("Blocker info " + LockSupport.getBlocker(Thread.currentThread()));
            System.out.println("thread2 end + " + new Date(System.currentTimeMillis()));
        });
        // 唤起 thread2
        Thread thread1 = new Thread(() -> {
            try {
                System.out.println("thread1 start + " + new Date(System.currentTimeMillis()));
                Thread.sleep(3000);
                System.out.println("Blocker info " + LockSupport.getBlocker(thread2));
                LockSupport.unpark(thread2);
                System.out.println("thread1 end + " + new Date(System.currentTimeMillis()));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        thread = thread1;
        thread2.start();
    }

parkAndUnpark() 方法中,先启动了线程 2,线程 2 打印完 start 后就启动了线程 1 。而此时线程 2 继续执行:

  • 调用 park 进入阻塞状态

在线程 2 执行上面两个逻辑的同时,线程 1 也在同时执行:

  • 线程 1 启动后先打印 start
  • 线程 1 睡眠 3 秒
  • 检查线程 2 调用 park(Object)方法设置的 Blocker 信息
  • 调用 LockSupport.unpark(thread2) 解除线程 2 的阻塞
  • 线程 1 打印 end 执行结束

线程 2 在被线程 1 唤醒后,继续执行打印信息:

  • 打印当前线程的 Blocker 信息,为 null
  • 打印 end 执行结束

打印日志:

thread2 start + Fri Jun 03 04:24:32 CST 2022
thread1 start + Fri Jun 03 04:24:32 CST 2022
Blocker info java.lang.Object@7a3acdd9
thread1 end + Fri Jun 03 04:24:35 CST 2022
Blocker info null
thread2 end + Fri Jun 03 04:24:35 CST 2022

注意:先调用 unpark 后调用 park 也不会导致阻塞,更加灵活。

可以发现,blocker 在线程恢复后变成了 null ,个人理解它的作用就是用来做阻塞标记的,可以用来在线程阻塞状态下,将一个对象设置上,然后在其他线程中读取这个对象。blocker 与线程的单次阻塞状态绑定,可以用于对线程状态的排查和线程状态的监控。

区别

从 wait/notify 和 park/unpark 两种阻塞线程和唤起线程的方式,能够感受出两者的不同之处:

  • wait/notify 需要配合 synchronized 进行;park/unpark 虽然设置了 blocker 但全程没有锁。
  • wait/notify 需要保证 wait 在前,notify 在后,否则会阻塞线程;park/unpark 对调用顺序没有限制。不会造成阻塞。
  • wait/notify 方法在 Object 中定义,用于对象锁的资源让出;park/unpark 来自于 LockSupport ,是静态方法,用于对线程本身进行挂起唤醒。并可以通过 blocker 绑定线程阻塞状态下的一些信息。

个人感觉 park/unpark 更像是 挂起/恢复,而 Thread.suspend 和 Thread.resume 都已废弃,是很好的替代方案。

  • suspend,挂起线程,但是不会释放类似锁这样的资源。
  • resume,恢复线程,如果之前没有使用suspend暂停线程,则不起作用。
  • Thread.stop() 由于其固有的风险而被逐步淘汰。当你停止一个线程时,它会解锁它锁定的所有监视器。如果以前受这些监视器保护的任何对象处于不一致状态,其他线程可能会看到这些对象处于不一致状态。 作用在受损物体上的线可能会有意或无意地行为不规律。与其他不受控制的异常不同,ThreadDeath 会静默地杀死线程,不会向用户发出程序可能已损坏的警告。损坏发生后,损坏可能会在无法预料的时刻出现。此外,在多线程环境中使用 DBMS – JDBC 时,终止线程会产生问题。
  • Thread.suspend() 已被弃用,因为它本质上容易死锁。因此,Thread.resume() 也必须被弃用。当目标线程被挂起时,它会在监视器上锁定一个保护关键系统资源的锁,并且在目标线程恢复之前没有其他线程可以访问它。如果将重新启动目标线程的线程在调用 resume() 之前尝试锁定此监视器,则会发生死锁。

到此这篇关于Java 多线程并发LockSupport的文章就介绍到这了,更多相关Java  LockSupport内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 详解Java多线程编程中LockSupport类的线程阻塞用法

    LockSupport是用来创建锁和其他同步类的基本线程阻塞原语. LockSupport中的park() 和 unpark() 的作用分别是阻塞线程和解除阻塞线程,而且park()和unpark()不会遇到"Thread.suspend 和 Thread.resume所可能引发的死锁"问题. 因为park() 和 unpark()有许可的存在:调用 park() 的线程和另一个试图将其 unpark() 的线程之间的竞争将保持活性. 基本用法 LockSupport 很类似于二元信号

  • Java并发编程系列之LockSupport的用法

    目录 1.什么是LockSupport? 2.两类基本API 3.LockSupport本质 4.LockSupport例子 5.LockSupport源码 总结 1.什么是LockSupport? LockSupport是用于创建锁和其他同步类的基本线程阻塞原语 2.两类基本API LockSupport提供了两类最基本的API: block线程类:一般都是以pack开头的方法名,pack*(...) pack方法有两个重载的版本:blocker是一个对象,用于指定阻塞哪个对象.不知道的情况,

  • Java并发编程学习之Unsafe类与LockSupport类源码详析

    一.Unsafe类的源码分析 JDK的rt.jar包中的Unsafe类提供了硬件级别的原子操作,Unsafe里面的方法都是native方法,通过使用JNI的方式来访问本地C++实现库. rt.jar 中 Unsafe 类主要函数讲解, Unsafe 类提供了硬件级别的原子操作,可以安全的直接操作内存变量,其在 JUC 源码中被广泛的使用,了解其原理为研究 JUC 源码奠定了基础. 首先我们先了解Unsafe类中主要方法的使用,如下: 1.long objectFieldOffset(Field

  • java线程阻塞中断与LockSupport使用介绍

    上周五和周末,工作忙里偷闲,在看java cocurrent中也顺便再温故了一下Thread.interrupt和java 5之后的LockSupport的实现. 在介绍之前,先抛几个问题. Thread.interrupt()方法和InterruptedException异常的关系?是由interrupt触发产生了InterruptedException异常? Thread.interrupt()会中断线程什么状态的工作? RUNNING or BLOCKING? 一般Thread编程需要关注

  • Java并发编程之LockSupport类详解

    一.LockSupport类的属性 private static final sun.misc.Unsafe UNSAFE; // 表示内存偏移地址 private static final long parkBlockerOffset; // 表示内存偏移地址 private static final long SEED; // 表示内存偏移地址 private static final long PROBE; // 表示内存偏移地址 private static final long SEC

  • Java concurrency之LockSupport_动力节点Java学院整理

    LockSupport介绍 LockSupport是用来创建锁和其他同步类的基本线程阻塞原语. LockSupport中的park() 和 unpark() 的作用分别是阻塞线程和解除阻塞线程,而且park()和unpark()不会遇到"Thread.suspend 和 Thread.resume所可能引发的死锁"问题. 因为park() 和 unpark()有许可的存在:调用 park() 的线程和另一个试图将其 unpark() 的线程之间的竞争将保持活性. LockSupport

  • 教你如何使用Java多线程编程LockSupport工具类

    LockSupport类 用于创建锁和其他同步类的基本线程阻塞原语,此类与使用它的每个线程关联一个许可.如果获得许可,将立即返回对park的调用,并在此过程中消耗掉它:否则may会被阻止.调用unpark可使许可证可用(如果尚不可用).(不过与信号量不同,许可证不会累积.最多只能有一个.) 方法park和unpark提供了有效的阻塞和解阻塞线程的方法,这些线程不会遇到导致已弃用的方法Thread.suspend和Thread.resume无法用于以下问题:由于许可,在调用park的一个线程与试图

  • Java 多线程并发LockSupport

    目录 概览 源码分析 静态方法 Blocker unpark Unsafe 的 unpark 方法 park 不带 blocker 参数的分组 需要 blocker 参数的分组 park/unpark 和 Object 的 wait/notify 区别 概览 这部分内容来自于这个类的注释,简单翻译了下. LockSupport 类是用于创建锁和其他同步类的基本线程阻塞原语. 它的实现思想是给每个使用它的线程颁发一个许可,当许可是可用状态时(线程有许可),调用 park 方法会消耗一个许可,方法立

  • Java 多线程并发AbstractQueuedSynchronizer详情

    目录 AbstractQueuedSynchronizer 核心思想 为什么需要 AQS 用法 用法示例 AQS 底层原理 父类 AbstractOwnableSynchronizer CLH 队列 Condition 用于等待的方法 用于唤醒的方法 ConditionObject Signalling methods Waiting methods enableWait canReacquire unlinkCancelledWaiters 对外提供的等待方法 awaitUninterrupt

  • Java 多线程并发 ReentrantReadWriteLock详情

    目录 前言 ReadWriteLock ReentrantReadWriteLock 源码分析 类关系 Sync HoldCounter ThreadLocalHoldCounter 属性 构造方法 核心方法 锁的计数方法 读写锁阻塞检查方法 公平策略实现 FairSync 和非公平策略实现 NonfairSync NonfairSync 非公平策略 FairSync 公平策略 Release 和 Acquire 方法组 ReadLock WriteLock 读写锁降级 总结 前言 Reentr

  • Java多线程并发FutureTask使用详解

    目录 基本使用 代码分析 继承关系 Future RunnableFuture FutureTask 状态 属性 内部类 构造方法 检索 FutureTask 状态 取消操作 计算结果 立刻获取结果或异常 run 方法组 本文基于最新的 OpenJDK 代码,预计发行版本为 19 . Java 的多线程机制本质上能够完成两件事情,异步计算和并发.并发问题通过解决线程安全的一系列 API 来解决:而异步计算,常见的使用是 Runnable 和 Callable 配合线程使用. FutureTask

  • Java多线程并发开发之DelayQueue使用示例

    在学习Java 多线程并发开发过程中,了解到DelayQueue类的主要作用:是一个无界的BlockingQueue,用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走.这种队列是有序的,即队头对象的延迟到期时间最长.注意:不能将null元素放置到这种队列中. Delayed,一种混合风格的接口,用来标记那些应该在给定延迟时间之后执行的对象.此接口的实现必须定义一个 compareTo 方法,该方法提供与此接口的 getDelay 方法一致的排序. 在网上看到了一些

  • Java 多线程并发编程_动力节点Java学院整理

    一.多线程 1.操作系统有两个容易混淆的概念,进程和线程. 进程:一个计算机程序的运行实例,包含了需要执行的指令:有自己的独立地址空间,包含程序内容和数据:不同进程的地址空间是互相隔离的:进程拥有各种资源和状态信息,包括打开的文件.子进程和信号处理. 线程:表示程序的执行流程,是CPU调度执行的基本单位:线程有自己的程序计数器.寄存器.堆栈和帧.同一进程中的线程共用相同的地址空间,同时共享进进程锁拥有的内存和其他资源. 2.Java标准库提供了进程和线程相关的API,进程主要包括表示进程的jav

  • Java多线程并发编程 Volatile关键字

    volatile 关键字是一个神秘的关键字,也许在 J2EE 上的 JAVA 程序员会了解多一点,但在 Android 上的 JAVA 程序员大多不了解这个关键字.只要稍了解不当就好容易导致一些并发上的错误发生,例如好多人把 volatile 理解成变量的锁.(并不是) volatile 的特性: 具备可见性 保证不同线程对被 volatile 修饰的变量的可见性. 有一被 volatile 修饰的变量 i,在一个线程中修改了此变量 i,对于其他线程来说 i 的修改是立即可见的. 如: vola

  • Java多线程并发编程(互斥锁Reentrant Lock)

    Java 中的锁通常分为两种: 通过关键字 synchronized 获取的锁,我们称为同步锁,上一篇有介绍到:Java 多线程并发编程 Synchronized 关键字. java.util.concurrent(JUC)包里的锁,如通过继承接口 Lock 而实现的 ReentrantLock(互斥锁),继承 ReadWriteLock 实现的 ReentrantReadWriteLock(读写锁). 本篇主要介绍 ReentrantLock(互斥锁). ReentrantLock(互斥锁)

  • Java多线程并发编程和锁原理解析

    这篇文章主要介绍了Java多线程并发编程和锁原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一.前言 最近项目遇到多线程并发的情景(并发抢单&恢复库存并行),代码在正常情况下运行没有什么问题,在高并发压测下会出现:库存超发/总库存与sku库存对不上等各种问题. 在运用了 限流/加锁等方案后,问题得到解决. 加锁方案见下文. 二.乐观锁 & 悲观锁 1.乐观锁 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁

随机推荐