栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

JUC笔记

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JUC笔记

尚硅谷 周阳老师 JUC 课程, 课程笔记

Future

定义操作异步任务执行的一些方法 , 专门处理耗时任务.

// Future 接口的定义

package java.util.concurrent;

public interface Future {

    boolean cancel(boolean mayInterruptIfRunning);

	boolean isCancelled();
    
    boolean isDone();
    
    V get() throws InterruptedException, ExecutionException;
    
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}
三个特点
  • 多线程
  • 有返回
  • 异步任务
对比
// 无返回值 无异常抛出
class MyThread implements Runnable {
    @Override
    public void run() {

    }
}
// 有返回值 有异常抛出
class MyThread2 implements Callable {
    @Override
    public Object call() throws Exception {
        return null;
    }
}
// 有返回值 无异常抛出
public class FutureTask implements RunnableFuture {
    
    public FutureTask(Callable callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }
    
    public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;       // ensure visibility of callable
    }
    
}
 
使用 
public class FutureTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        
        FutureTask task = new FutureTask<>(new MyThread2());
        new Thread(task).start();
        System.out.println(task.get());

    }

}
class MyThread2 implements Callable {
    @Override
    public Object call() throws Exception {
        System.out.println("come in Callable...");
        return "hello Callable";
    }
}


 
优缺点 
优点 

Future + 线程池异步多线程任务配合, 显著提高执行程序的执行效率

public static void main(String[] args) throws ExecutionException, InterruptedException {

    ExecutorService service = Executors.newFixedThreadPool(3);

    FutureTask task1 = new FutureTask(() -> {
        Thread.sleep(300);
        return "task1, over";
    });

    service.submit(task1);

    FutureTask task2 = new FutureTask(() -> {
        Thread.sleep(300);
        return "task2, over";
    });

    service.submit(task2);

    FutureTask task3 = new FutureTask<>(() -> {
        Thread.sleep(200);
        return "task3, over";
    });

    service.submit(task3);

    service.shutdown();

    System.out.println(task1.get());
    System.out.println(task2.get());
    System.out.println(task3.get());

    System.out.println("main end.....");

}
 
缺点 
  1. get() 阻塞, 必须等到结果才离开, 容易程序堵塞
    1. 一般放在最后
    2. 可以设置等待时间, 超过等待时间抛出超时异常.
  2. isDone() 轮询, 耗费无谓的CUP
CompletableFuture

减少阻塞版本的 Future, Future 的增强版

package java.util.concurrent;

public class CompletableFuture implements Future, CompletionStage {
    ...
}
创建

使用四个方法进行创建对象

// 1. 无返回值, 无线程池
public static CompletableFuture runAsync(Runnable runnable) {
    return asyncRunStage(asyncPool, runnable);
}

// 2. 无返回值, 有线程池
public static CompletableFuture runAsync(Runnable runnable,
                                               Executor executor) {
    return asyncRunStage(screenExecutor(executor), runnable);
}

// 3. 有返回值, 无线程池
public static  CompletableFuture supplyAsync(Supplier supplier) {
    return asyncSupplyStage(asyncPool, supplier);
}

// 4. 有返回值, 有线程池
public static  CompletableFuture supplyAsync(Supplier supplier,
                                                   Executor executor) {
    return asyncSupplyStage(screenExecutor(executor), supplier);
}

线程池的创建

// 核心线程数
int corePoolSize = 5;
// 最大线程数
int maximumPoolSize = 15;
// 线程最大存活时间
int keepAliveTime = 30;
// 时间单位
TimeUnit unit = TimeUnit.SECONDS;
// 任务队列
BlockingQueue workQueue = 
    new ArrayBlockingQueue(5);
// 线程工厂 -> MyThreadFactory <- 自定义线程工厂
ThreadFactory threadFactory = 
    new MyThreadFactory("MyThreadPool");
// 拒绝策略
RejectedExecutionHandler handler = 
    new ThreadPoolExecutor.DiscardOldestPolicy();


ThreadPoolExecutor threadPoolExecutor = 
    new ThreadPoolExecutor(corePoolSize,
                           maximumPoolSize,
                           keepAliveTime,
                           unit,
                           workQueue,
                           threadFactory,
                           handler
                          );
// 线程工厂的具体实现
public class MyThreadFactory implements ThreadFactory {
	// 前缀
    private final String prefix;
	// 线程计数
    private final AtomicInteger number = new AtomicInteger(1);
	// 前缀赋值
    public MyThreadFactory(String prefix) {
        this.prefix = prefix;
    }
	// 线程工厂方法, 继承于 ThreadFactory, 重写
    @Override
    public Thread newThread(Runnable r) {
        return new Thread(null, r, prefix + " " + number.getAndIncrement());
    }
}
基本使用
public static void main(String[] args) {

    // 核心线程数
    int corePoolSize = 5;
    // 最大线程数
    int maximumPoolSize = 15;
    // 线程最大存活时间
    int keepAliveTime = 30;
    // 时间单位
    TimeUnit unit = TimeUnit.SECONDS;
    // 任务队列
    BlockingQueue workQueue = new ArrayBlockingQueue(5);
    // 线程工程
    ThreadFactory threadFactory = new MyThreadFactory("MyThreadPool");
    // 拒绝策略
    RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardOldestPolicy();


    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize,
                                                                   maximumPoolSize,
                                                                   keepAliveTime,
                                                                   unit,
                                                                   workQueue,
                                                                   threadFactory,
                                                                   handler
                                                                  );

    CompletableFuture.runAsync(() -> {
        System.out.println("completableFuture.....NoReturn");
    });
    CompletableFuture.runAsync(() -> {
        System.out.println("completableFuture.....NoReturn......Pool");
    }, threadPoolExecutor);

    CompletableFuture.supplyAsync(() -> {
        System.out.println("completableFuture.....Return");
        return "over, NoPool";
    });
    CompletableFuture.supplyAsync(() -> {
        System.out.println("completableFuture.....Return.....Pool");
        return "over, NoPool";
    }, threadPoolExecutor);

    CompletableFuture.supplyAsync(() -> {

        System.out.println("... in come ....");
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        int nextInt = ThreadLocalRandom.current().nextInt(10);

        if(nextInt % 2 == 0){
            throw new RuntimeException();
        }

        return nextInt;
    }, threadPoolExecutor).whenComplete((i, e) -> {
        if(e == null){
            System.out.println("The number is : " + i);
        }
    }).exceptionally(e -> {
        e.printStackTrace();
        return 0;
    });

    threadPoolExecutor.shutdown();

}
优点
  1. 异步任务结束时会自动调用某个对象方法
  2. 主线程设置好回调之后, 不在关心异步任务的执行, 异步任务之间可以顺序执行
  3. 异步任务出错时, 会自动回调某个对象的方法
电商比价案例(Excuse me?)
package mall;

import pool.MyThreadPool;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


public class MallDemo {
    static List mallList = Arrays.asList(
            new Mall("JingDong"),
            new Mall("DangDang"),
            new Mall("TaoBao"),
            new Mall("TianMao")
    );

    public static List comparePrice() {

        ThreadPoolExecutor pool = MyThreadPool.getPool();

        try {
            return mallList.stream()
                    .map(mall -> CompletableFuture.supplyAsync(
                            () -> String.format("<< MySQL >> in %s price is %.2f", mall.getName(), mall.getPrice())
                            , pool))
                    .collect(Collectors.toList())
                    .stream()
                    .map(future -> future.join())
                    .collect(Collectors.toList());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            pool.shutdown();
        }

        return null;

    }

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        List prices = comparePrice();
        long end = System.currentTimeMillis();
        System.out.println("runtime : " + (end - start));

        if (prices != null) {
            for (String price : prices) {

                System.out.println(price);

            }
        }

    }

}

class Mall {

    private String name;

    private double price;

    public Mall(String name) {
        this.name = name;
        this.price = ThreadLocalRandom.current().nextDouble() * 10 + 100;
    }

    public String getName() {
        return name;
    }

    public Mall setName(String name) {
        this.name = name;
        return this;
    }

    public double getPrice() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return price;
    }

    public Mall setPrice(double price) {
        this.price = price;
        return this;
    }
}
常用API
// 传入两个参数 v, e -> v: 使用该方法的 CompletableFuture 对象的返回值, e: 异常对象 
public CompletableFuture whenComplete(
    BiConsumer action) {
    return uniWhenCompleteStage(null, action);
}

// 传入一个参数 e -> e: 异常对象, 当出现异常时进入该方法 
public CompletableFuture exceptionally(
    Function fn) {
    return uniExceptionallyStage(fn);
}

// thenRun 与上一个 CompletableFuture 对象不再有承接关系, 无传入无传出
// 默认
public CompletableFuture thenRun(Runnable action) {
    return uniRunStage(null, action);
}
// 新开异步任务, 不再使用上一个 CompletableFuture 对象使用的线程池(重新选择线程池)
public CompletableFuture thenRunAsync(Runnable action) {
    return uniRunStage(asyncPool, action);
}
// 新开异步任务, 使用传入的线程池 Executor executor
public CompletableFuture thenRunAsync(Runnable action,
                                            Executor executor) {
    return uniRunStage(screenExecutor(executor), action);
}

// thenAccept 与上一个 CompletableFuture 具有消费关系, 消费上一个的传出, 有传入无传出
// 默认
public CompletableFuture thenAccept(Consumer action) {
    return uniAcceptStage(null, action);
}
// 新开异步任务, 重新选择线程池
public CompletableFuture thenAcceptAsync(Consumer action) {
    return uniAcceptStage(asyncPool, action);
}
// 新开异步任务, 使用传入线程池 Executor executor
public CompletableFuture thenAcceptAsync(Consumer action,
                                               Executor executor) {
    return uniAcceptStage(screenExecutor(executor), action);
}

// handle 与上一个 CompletableFuture 具有承接关系, 获取上一个的传出与异常对象, 有传入有传出
// 默认
public  CompletableFuture handle(
    BiFunction fn) {
    return uniHandleStage(null, fn);
}
// 新开异步任务, 重新选择线程池
public  CompletableFuture handleAsync(
    BiFunction fn) {
    return uniHandleStage(asyncPool, fn);
}
// 新开异步任务, 使用传入线程池 Executor executor
public  CompletableFuture handleAsync(
    BiFunction fn, Executor executor) {
    return uniHandleStage(screenExecutor(executor), fn);
}

// 与调用该方法的 CompletableFuture 的结果和当前方法中 CompletableFuture 的结果进行处理
public  CompletableFuture thenCombine(
    CompletionStage other,
    BiFunction fn) {
    return biApplyStage(null, other, fn);
}
 
// 与调用该方法的 CompletableFuture 的处理速度和当前方法中 CompletableFuture 的处理速度进行比较, 使用最先完成的那个结果
public  CompletableFuture applyToEither(
    CompletionStage other, Function fn) {
    return orApplyStage(null, other, fn);
}

public  CompletableFuture applyToEitherAsync(
    CompletionStage other, Function fn) {
    return orApplyStage(asyncPool, other, fn);
}

public  CompletableFuture applyToEitherAsync(
    CompletionStage other, Function fn,
    Executor executor) {
    return orApplyStage(screenExecutor(executor), other, fn);
}
锁 乐观锁与悲观锁 乐观锁

认为使用数据时不会有别的线程修改数据或资源, 不会添加锁.

使用判断规则来判断这次修改能否进行更新

判断规则

  1. 版本号机制
  2. CAS 算法, Java 原子类中的递增操作就通过 CAS 自旋实现
悲观锁

使用数据的时候, 必定加锁

synchronized

作用于 实例方法 -> 对当前对象加锁

作用于 代码块 -> 对括号里面配置的对象加锁

作用于 静态方法 -> 当前类加锁

识别

同步代码块中 -> 底层使用 monitorenter 和 monitorexit 指令 进行实现

实例方法中 -> 底层使用 ACC_SYNCHRONIZED 标识进行区分

静态方法中 -> 底层使用 ACC_STATIC 与 ACC_SYNCHRONIZED 标识进行区分

公平锁与非公平锁

公平锁 -> 先到先得

非公平锁 -> 顺序不定

为什么使用非公平锁?

恢复挂起的线程到真正锁的获取还是有时间差的, 从开发人员来看这个时间微乎其微, 以cup的角度去看时间差还是很明显的, 因此非公平锁能够更充分的使用 CPU 的时间片, 减少 CPU 空闲状态时间

多线程很重要的考量点是线程切换的开销, 采用非公平锁时, 一个线程请求所获取同步状态, 然后释放同步状态, 所以刚释放锁的线程在此刻再次获取同步状态的概率非常大, 所以就减少了线程的开销

可重入锁(递归锁)

同一个线程在外层方法获取锁的时候, 在进入该线程的内层方法会自动获取锁 ReentrantLock 与 synchronized 都是可重入锁

原理

每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针。

当执行 monitorenter 时,如果目标锁对象的计数器为零,那么说明它没有被其他线程所持有,Java虚拟机会将该锁对象的持有线程设置为当前线程,并且将其计数器加1。

在目标锁对象的计数器不为零的情况下,如果锁对象的持有线程是当前线程,那么Java虚拟机可以将其计数器加1。否则需要等待,直至持有线程释放该锁。

当执行monitorexit时,Java虚拟机则需将锁对象的计数器减1。计数器为零代表锁已被释放。

ObjectMonitor() {
    _header       = NULL;
    _count        = 0;
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL;
    _WaitSet      = NULL;
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ;
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
    _previous_owner_tid = 0;
}

// _owner 指向持有 ObjectMonitor 对象的线程
// _WaitSet 存放处于 wait 状态的线程队列
// _EntryList 存放处于等待锁 block 状态的线程队列
// _recursions 锁的重入次数
// _count 记录该线程获取锁的次数
LockSupport 与 线程中断 线程中断

线程应该由自己决定是否停止, 但是某些情况下需要将比较耗时的线程进行停止 -> 中断

线程中存在一个中断标识, 若其他线程希望中断该线程则会调用该线程的 interrupt 方法将中断标识设置为 true, 但是检测到中断标识后如何处理需要该线程自己实现

相关 API
  1. interrupt
  2. interrupted
  3. isInterrupted
// 1. interrupt 	中断次线程		
// 2. interrupted   测试当前线程是否已被中断
// 3. isInterrupted 测试此线程是否已被中断

// 1. interrupt 	中断次线程 -> 仅仅将线程的中断状态设置为 true
public void interrupt() {
    if (this != Thread.currentThread())
        checkAccess();

    synchronized (blockerLock) {
        Interruptible b = blocker;
        if (b != null) {
            interrupt0();           // Just to set the interrupt flag
            b.interrupt(this);
            return;
        }
    }
    interrupt0();
}

// 2. interrupted   测试当前线程是否已被中断 -> 静态方法
// -> 返回当前线程的中断状态, 并将中断状态设置为 false (重置状态)
public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}

// 3. isInterrupted 测试此线程是否已被中断 -> 判断当前线程是否被中断
public boolean isInterrupted() {
    return isInterrupted(false);
}
 
// interrupted 与 isInterrupted 底层, 一个传递 true 一个 传递 false
private native boolean isInterrupted(boolean ClearInterrupted);
interrupt()

具体来说,当对一个线程,调用interrupt()时:

①如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。被设置中断标志的线程将继续正常运行,不受影响。所以,interrupt()并不能真正的中断线程,需要被调用的线程自己进行配合才行。

②如果线程处于被阻塞状态(例如处于sleep, wait, join等状态),在别的线程中调用当前线程对象的interrupt方法,那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。

​ 解决: 在 sleep 等方法的 try cache 中进行再次中断设置

③如果线程已经停止不会产生任何影响

LockSupport

用来创建锁和其他同步线程的基本线程阻塞原语

LockSupport 中的 park() 和 unpark() 的作用分别是阻塞线程和解除阻塞线程

线程的阻塞与唤醒 三种阻塞唤醒方法

Object 中的 wait() 和 notify()

Condition 的 await() 和 signal()

LockSupport 的 park() 和 unpark()

wait() 与 notify()

public static void main(String[] args) {

    Object oLock = new Object();

    new Thread(()->{
        synchronized (oLock){

            try {
                System.out.println("coming!");
                oLock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }, "t1").start();

    try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

    new Thread(() -> {

        synchronized (oLock){
            oLock.notify();
        }

        System.out.println("解除等待!");

    }, "t2").start();

}

如果, wait() 与 notify() 在调用之前没有先获取调用对象的锁, 则会出现如下异常

// java.lang.IllegalMonitorStateException:current thread not owner

如果, notify() 在 wait() 方法之前进行了调用, 那么 wait() 将在下一次 notify() 之前被一直阻塞

Condition 的 await() 和 signal()

public static void main(String[] args) {

    ReentrantLock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    new Thread(() -> {

        lock.lock();
        try {
            System.out.println("coming!");
            condition.await();
            System.out.println("awake!");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }, "t1").start();


    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    new Thread(() -> {
        lock.lock();
        try {
            condition.signalAll();
            System.out.println("notice!");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }, "t2").start();

}

如果, await() 与 signal() 在调用之前没有先获取 condition 的锁, 则会出现如下异常

// java.lang.IllegalMonitorStateException:current thread not owner

如果, signal() 在 await() 方法之前进行了调用, 那么 await() 将在下一次 signal() 之前被一直阻塞

LockSupport 的 park() 和 unpark()

public static void main(String[] args) {

    Thread thread1 = new Thread(() -> {
        System.out.println("coming!");
        LockSupport.park();
        System.out.println("awake!");
    }, "thread1");
    thread1.start();

    try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

    new Thread(() -> {
        LockSupport.unpark(thread1);
        System.out.println("notice!");
    }).start();

}

不需要放置在同步代码块中

使用标志位的形式, 可以先给予许可证再进行判定

许可证的上限为 1

JMM( Java Memory Model )

JMM的作用是屏蔽不同硬件和操作系统之间的内存访问差异, 使 Java 程序在各种平台下都能达到一致的内存访问结果.

JMM 本身是一种抽象的概念并不是真是存在的, 它仅仅描述的是一组预定或者规范, 通过这组规范定义了程序中各个变量的读写访问方式并决定一个线程何时对共享变量的写入以及如何变成对另一个线程可见, 关键技术点都是围绕多线程的原子性、可见性和有序性展开的.

原则

JMM的关键技术点都围绕多线程的原子性、可见性和有序性展开的

作用

通过 JMM 来实现 线程和主存之间的抽象关系

屏蔽各个硬件平台和操作系统的内存访问差异, 实现 Java 程序在各个平台下都能达到一致的内存访问效果

JMM 三大特性
  1. 可见性
  2. 原子性
  3. 有序性
可见性

当一个线程修改某共享变量的值, 其他线程是否可以立即知道其值的改变, JMM 规定所有变量都存储在主存中

原子性

一个操作是不可以被打断的, 多线程环境下, 操作不能被其他线程干扰

有序性

只要最终的结果一致, 指令的执行顺序允许和代码的顺序不一致, 这个过程叫做指令重排.

处理器在进行重排序时需要考虑指令之间的数据依赖性

读写过程
  1. 线程意见的共享变量存储在主内存中
  2. 每个线程有一个私有的本地工作内存, 本地工作内存中存储了该线程用来 读/写 共享变量的副本
  3. 线程之间不能共享私有的工作内存
  4. 线程无法直接修改主存中的共享数据, 只能通过本地副本进行回写修改
多线程先行发生原则( happens-before )

总原则

如果一个操作 happens-before另一个操作, 那么第一个操作的执行结果将对第二个操作可见,而且第一个操作在第二个操作之前.

两个操作之间存在 happens-before 关系, 并不意味着一定要按照 happens-before 原则指定的顺序来执行. 如果重排序的之后的结果与按照 happens-before 关系来执行的结果一致, 那么这种重排序并不非法.

happens-before 8条原则
  • 次序规则
    • 一个线程内, 按照代码顺序, 写在前面的操作先行发生于写在后面的操作
  • 锁定规则
    • 一个 unLock 操作先行发生于后续( 时间顺序上的后续 )对同一个锁的 Lock
  • volatile 变量规则
    • 对一个 volatile 变量的写操作必定先于后续( 时间顺序上的后续 )对这个变量的读操作, 前面的写对后面的读是可见的
  • 传递规则
    • A 先于 B, B 先于 C, A 一定先于 C
  • 线程启动规则
    • Thread 对象的 start() 方法先行发生于此线程的每一 个工作
  • 线程中断规则
    • 对线程 interrupt() 方法的调用先行发生于被中断线程的代码检测到中断事件的发生
    • 可以通过 Thread.interrupted() 检测都是否发生中断
    • 必需先通过 interrupt() 设置中断标志位, 才能检测到中断发送
  • 线程终止规则
    • 线程中的所有操作都先行发生于对此线程的终止检测, 可以使用 isAlive() 等手段检测线程是否已经终止执行.
    • 就是线程任务执行完成之前不会被检测到终止状态
  • 对象终结规则
    • 一个对象的初始化完成( 构造函数执行结束 )先行发生于他的 finalize() 方法的开始
Volatile 特点

保证了变量的可见性与有序性.

内存语义

写一个 volatile 变量时, JMM 会将本地内存中的共享变量立即刷新会主存

读一个 volatile 变量时, JMM 会将本地内存中的共享变量设置为无效, 重新回到主存中读取数据

如何保证可见性与有序性

内存屏障 Memory Barrier

内存屏障

屏障指令, 对编译器或CPU执行排序优化时设置一个排序的约束

组织指令重排序

写屏障( Store Memory Barrier)

在写屏障之前将所有存储在缓存中的数据同步到主内存

store 之前所有的数据都回写

读屏障( Load Memory Barrier )

读屏障之后的读操作, 都在读屏障之后执行

load 之后去主存读取最新数据

四种内存屏障
屏障类型指令示例说明
LoadLoadLoad1;LoadLoad; Load2保证load1的读取操作在load2及后续读取操作之前执行
StoreStoreStore1; StoreStore; Store2在store2及其后的写操作执行前,保证store1的写操作已刷新到主内存
LoadStoreLoad1; LoadStore; Store2在stroe2及其后的写操作执行前,保证load1的读操作已读取结束
StoreLoadStore1; StoreLoad; Load2保证store1的写操作已刷新到主内存之后,load2及其后的读操作才能执行
四种内存屏障插入策略 读屏障

在每个 volatile 读操作的后面插入一个 LoadLoad 屏障

在每个 volatile 读操作的后面插入一个 LoadStore 屏障

写屏障

在每个 volatile 写操作之前插入一个 StoreStore 屏障

在每个 volatile 写操作之后插入一个 StoreLoad 屏障

volatile 读写流程

重排序的流程

源代码 -> 编译器优化重排 -> 指令集并行重排 -> 内存系统重排 -> 执行

正确使用 volatile
  1. 单一赋值
  2. 状态标志
  3. 开销低的 读, 写锁 策略
  4. DCL双端锁的发布
底层

使用 volatile 标识一个变量后, 在生成的字节码文件中会出现 ACC_VOLATILE 标识, 这时 JVM 在操作该 volatile 变量时会自动插入相应的内存屏障

CAS 原子类

java.util.concurrent.atomic

什么是 CAS

CAS (compare and swap) 比较并交换

三个参数 -> 内存位置 / 预期原值 / 更新值

原理

位置内存值 V , 旧的预期值 A , 要更改的更新值 B

当 A == V 时, 将 V 修改为 B, 否则什么都不做或者重来

重试的行为称为自旋

硬件保证

CAS 是 JDK 提供的非阻塞原子性操作, 通过硬件保证了 比较-更新 的原子性.

CAS 是一条 CPU 的原子指令 ( cmpxchg ), Unsafe 提供的 CAS 方法底层实现即为 CPU 的 cmpxchg 指令

执行 cmpxchg 指令的时候, 会判断当前系统是否为多核系统, 如果是就会给总线加锁, 只有一个线程会对总线加锁成功, 加锁成功之后会执行 CAS 操作, 也就是说 CAS 的原子性其实是 CPU 实现独占的

原子引用
public static void main(String[] args) {

    AtomicReference userAtomic = new AtomicReference<>();

    User z3 = new User().setName("z3").setAge(22);
    User li4 = new User().setName("li4").setAge(23);
    User w5 = new User().setName("w5").setAge(24);

    userAtomic.set(z3);

    System.out.println(userAtomic.compareAndSet(z3, li4) + "t -- " + userAtomic.get());
    System.out.println(userAtomic.compareAndSet(z3, w5) + "t -- " + userAtomic.get());

}

class User{

    private String name;
    private int age;
    
    // ......
}
自旋锁
public final int getAndAddInt(Object o, long offset, int delta) {
    int v;
    do {
        v = getIntVolatile(o, offset);
    } while (!compareAndSwapInt(o, offset, v, v + delta));
    return v;
}
自己实现自旋加锁
public class CASTest {

    AtomicReference threadAtomic = new AtomicReference<>();

    public void lock(){
        Thread thread = Thread.currentThread();
        while (!threadAtomic.compareAndSet(null, thread)){
            // 如果有人占用了位置则进行自旋
        }
    }

    public void unLock(){
        Thread thread = Thread.currentThread();
        threadAtomic.compareAndSet(thread, null);
    }

    public static void main(String[] args) {

        CASTest casTest = new CASTest();

        new Thread(() -> {

            casTest.lock();
            System.out.println(Thread.currentThread().getName() + "t -- coming!");
            System.out.println(Thread.currentThread().getName() + "t -- lock success!");
            System.out.println(Thread.currentThread().getName() + "t -- working.....");
            try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println(Thread.currentThread().getName() + "t -- worked!");
            casTest.unLock();

        }, "A").start();

        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> {
            casTest.lock();
            System.out.println(Thread.currentThread().getName() + "t -- coming!");
            System.out.println(Thread.currentThread().getName() + "t -- lock success!");
            System.out.println(Thread.currentThread().getName() + "t -- working.....");
            try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println(Thread.currentThread().getName() + "t -- worked!");
            casTest.unLock();
        }, "B").start();

    }
}
缺点

循环时间长开销时间大

ABA 问题 -> 使用版本号进行解决

// 带有版本号的 AtomicStampedReference

public static void main(String[] args) {

    Book java = new Book(1, "Java");
    Book mySQL = new Book(2, "MySQL");

    AtomicStampedReference bookAtomic = new AtomicStampedReference(java, 1);

    System.out.println("initt" + bookAtomic.getReference() + "t" + bookAtomic.getStamp());

    boolean b;
    b = bookAtomic.compareAndSet(java, mySQL, bookAtomic.getStamp(), bookAtomic.getStamp() + 1);

    System.out.println(b + "t" + bookAtomic.getReference() + "t" + bookAtomic.getStamp());

    b = bookAtomic.compareAndSet(mySQL, java, bookAtomic.getStamp(), bookAtomic.getStamp() + 1);

    System.out.println(b + "t" + bookAtomic.getReference() + "t" + bookAtomic.getStamp());

}

class Book {

    private int id;
    private String name;
	
    //...
}
多线程下的使用
public static void main(String[] args) {

    AtomicStampedReference atomicStampInteger = new AtomicStampedReference<>(1, 1);

    new Thread(() -> {

        int stamp = atomicStampInteger.getStamp();

        System.out.println("initt" + Thread.currentThread().getName() + "t" + stamp);

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        boolean b = atomicStampInteger.compareAndSet(1, 2, atomicStampInteger.getStamp(), atomicStampInteger.getStamp() + 1);

        System.out.println(b + "t" + Thread.currentThread().getName() + "t" + atomicStampInteger.getReference() + "t" + atomicStampInteger.getStamp());

        b = atomicStampInteger.compareAndSet(2, 1, atomicStampInteger.getStamp(), atomicStampInteger.getStamp() + 1);

        System.out.println(b + "t" + Thread.currentThread().getName() + "t" + atomicStampInteger.getReference() + "t" + atomicStampInteger.getStamp());

    }, "A").start();

    new Thread(() -> {

        int stamp = atomicStampInteger.getStamp();

        System.out.println("initt" + Thread.currentThread().getName() + "t" + stamp);

        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        boolean b = atomicStampInteger.compareAndSet(1, 2, atomicStampInteger.getStamp(), atomicStampInteger.getStamp() + 1);

        System.out.println(b + "t" + Thread.currentThread().getName() + "t" + atomicStampInteger.getReference() + "t" + atomicStampInteger.getStamp());

        b = atomicStampInteger.compareAndSet(2, 3, atomicStampInteger.getStamp(), atomicStampInteger.getStamp() + 1);

        System.out.println(b + "t" + Thread.currentThread().getName() + "t" + atomicStampInteger.getReference() + "t" + atomicStampInteger.getStamp());

    }, "B").start();


}
原子操作类
  1. 基本类型
  2. 数组类型
  3. 引用类型
  4. 对象的属性修改
  5. 原子操作增强类
基本类型原子类

AtomicInteger

AtomicBoolean

AtomicLong

public class AtomicIntegerDemo {

    public static final AtomicInteger atoInt = new AtomicInteger();
    public static final int SIZE = 50;

    private static void add() {
        atoInt.getAndIncrement();
    }

    public static void main(String[] args) {

        ThreadPoolExecutor pool = MyThreadPool.getPool();

        CompletableFuture.supplyAsync(() -> {

            CountDownLatch downLatch = new CountDownLatch(SIZE);

            pool.submit(() -> {
                for (int i = 0; i < SIZE; i++) {
                    new Thread(() -> {
                        try {
                            for (int j = 0; j < 1000; j++) {
                                add();
                            }
                        } finally {
                            downLatch.countDown();
                        }
                    }).start();
                }
            });

            try {
                downLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            return atoInt.get();
        }, pool).whenComplete((v, e) -> {

            if (e == null) {
                System.out.println(v);
            }

        });

        pool.shutdown();

    }

}
数组类型原子类

AtomicIntegerArray

AtomicLongArray

AtomicReferenceArray

引用类型原子类

AtomicReference

AtomicStampedReference

AtomicMarkableReference

Stamped 与 Markable 的区别

AtomicStampedReference 使用版本号的方式进行标识, 可以加一加一加一

解决修改了几次的问题

AtomicMarkableReference 使用标志位 -> true / false

解决是否进行了修改的问题

Demo
public static void main(String[] args) {

    AtomicMarkableReference mark = new AtomicMarkableReference(1, false);

    CountDownLatch countDownLatch = new CountDownLatch(2);

    new Thread(() -> {

        try {
            boolean marked = mark.isMarked();
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
            boolean b = mark.compareAndSet(1, 100, marked, !marked);
            System.out.println(Thread.currentThread().getName() + "t"+ b + "t" +mark.isMarked() + "t" + mark.getReference());

        }finally {
            countDownLatch.countDown();
        }

    }, "A").start();

    new Thread(() -> {

        try {

            boolean marked = mark.isMarked();
            try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
            boolean b = mark.compareAndSet(1, 100, marked, !marked);
            System.out.println(Thread.currentThread().getName() + "t"+ b + "t" +mark.isMarked() + "t" + mark.getReference());

        }finally {
            countDownLatch.countDown();
        }

    }, "B").start();

    try {
        countDownLatch.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    System.out.println("maint" + mark.isMarked() + "t" + mark.getReference());

}
对象的属性修改原子类

AtomicIntegerFieldUpdater

AtomicLongFiledUpdater

AtomicReferenceFieldUpdater

原子更新对象中的 Int 类型字段的值

原子更新对象中的 Long 类型字段的值

原子更新引用类型字段的值

使用目的

以一种线程安全的方式操作非线程安全对象内的某些字段

使用要求

更新的对象属性必须使用 public volatile修饰符

因为对象的属性修改类型原子类都是抽象类, 所以每次使用都必须使用静态方法 newUpdater() 创建一个更新器, 并且需要设置想要更新的类和属性

Demo1
class User {

    private static final AtomicIntegerFieldUpdater UPDATER =
            AtomicIntegerFieldUpdater.newUpdater(User.class, "money");
    public volatile int money = 0;
    public static void transferMoney(User user, int m) {
        // 使用自旋保证更新一定成功
        while (!UPDATER.compareAndSet(user, user.money, user.money + m)) {
        }
    }
    public static void main(String[] args) {
        // 线程池获取线程
        ThreadPoolExecutor pool = MyThreadPool.getPool();
		// 构建数据类
        User user = new User();
		// 提交线程工作
        pool.submit(
            	// Lambda 创建 线程
                () -> {
					// 线程拦截
                    CountDownLatch count = new CountDownLatch(100);
					// 创建工作线程
                    for (int i = 0; i < 100; i++) {
                        new Thread(() -> {
							// try finally 保证线程结束拦截减一
                            try {
                                for (int j = 0; j < 1000; j++) {
                                    User.transferMoney(user, 100);
                                }
                            } finally {
                                count.countDown();
                            }
                        }).start();
                    }
                    try {
                        // 阻塞
                        count.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
					// 获取结果
                    System.out.println(user.money);
                }
        );
		// 关闭线程池
        pool.shutdown();
    }
}
Demo2
class Signal {
    public volatile Boolean initFlag = Boolean.FALSE;
    private static final AtomicReferenceFieldUpdater UPDATER =
            AtomicReferenceFieldUpdater.newUpdater(Signal.class, Boolean.class, "initFlag");
    public static void init(Signal signal) {
        if (UPDATER.compareAndSet(signal, Boolean.FALSE, Boolean.TRUE)) {
            System.out.println(Thread.currentThread().getName() + "tinit Signal class");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "tinit success");
        } else {
            System.out.println(Thread.currentThread().getName() + "tslow");
        }
    }
    public static void main(String[] args) {
        Signal signal = new Signal();
        ThreadPoolExecutor pool = MyThreadPool.getPool();
        for (int i = 0; i < 10; i++) {
            pool.submit(() -> {
                try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
                Signal.init(signal);
            });
        }
        pool.shutdown();
    }
}
原子操作增强类

DoubleAccumulator

DoubleAdder

LongAccumulator

LongAdder

Long 相关 初识Demo
// LongAdder 只能做累加
// LongAccumulator 可以自定义操作

public static void main(String[] args) {

    LongAdder longAdder = new LongAdder();

    longAdder.increment();
    longAdder.increment();
    longAdder.increment();

    System.out.println(longAdder.sum());

    LongAccumulator longAccumulator = new LongAccumulator(Long::sum, 0);

    longAccumulator.accumulate(3);
    longAccumulator.accumulate(7);

    System.out.println(longAccumulator.get());
}
LongAdder 性能对比
public class Counter {

    public long count = 0;

    public synchronized void adderBySynchronized() {
        count++;
    }

    private AtomicLong aLong = new AtomicLong(0);

    public void adderByAtomicLong() {
        aLong.getAndIncrement();
    }

    private LongAdder longAdder = new LongAdder();

    public void adderByLongAdder() {
        longAdder.increment();
    }

    private LongAccumulator longAccumulator = new LongAccumulator((x, y) -> x + y, 0);

    public void adderByLongAccumulator(){
        longAccumulator.accumulate(1);
    }

    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        long start, end;
        int threadNumber = 50;
        int _1W = 10000;

        CountDownLatch count1 = new CountDownLatch(threadNumber);
        start = System.currentTimeMillis();
        for (int i = 0; i < threadNumber; i++) {
            new Thread(() -> {
                try {
                    for (int j = 0; j < 100 * _1W; j++) {
                        counter.adderBySynchronized();
                    }
                } finally {
                    count1.countDown();
                }
            }).start();
        }
        count1.await();
        end = System.currentTimeMillis();
        System.out.println(counter.count);
        System.out.println("adderBySynchronized, time -> " + (end - start));

        CountDownLatch count2 = new CountDownLatch(threadNumber);
        start = System.currentTimeMillis();
        for (int i = 0; i < threadNumber; i++) {
            new Thread(() -> {
                try {
                    for (int j = 0; j < 100 * _1W; j++) {
                        counter.adderByAtomicLong();
                    }
                } finally {
                    count2.countDown();
                }
            }).start();
        }
        count2.await();
        end = System.currentTimeMillis();
        System.out.println(counter.aLong.get());
        System.out.println("adderByAtomicLong, time -> " + (end - start));

        CountDownLatch count3 = new CountDownLatch(threadNumber);
        start = System.currentTimeMillis();
        for (int i = 0; i < threadNumber; i++) {
            new Thread(() -> {
                try {
                    for (int j = 0; j < 100 * _1W; j++) {
                        counter.adderByLongAdder();
                    }
                } finally {
                    count3.countDown();
                }
            }).start();
        }
        count3.await();
        end = System.currentTimeMillis();
        System.out.println(counter.longAdder.sum());
        System.out.println("adderByLongAdder, time -> " + (end - start));
        
        CountDownLatch count4 = new CountDownLatch(threadNumber);
        start = System.currentTimeMillis();
        for (int i = 0; i < threadNumber; i++) {
            new Thread(() -> {
                try {
                    for (int j = 0; j < 100 * _1W; j++) {
                        counter.adderByLongAccumulator();
                    }
                } finally {
                    count4.countDown();
                }
            }).start();
        }
        count4.await();
        end = System.currentTimeMillis();
        System.out.println(counter.longAccumulator.get());
        System.out.println("adderByLongAccumulator, time -> " + (end - start));
    }
}


原理

内部有一个 base 变量, 一个 Cell[] 数组

使用热点分布的方式, 将多个线程访问一个 变量转为访问数组中的对象

获取数据时使用 sum() 将所有的数据加起来并且返回

源码解析
  1. 无竞争时只需要更新 base
  2. 更新 base 失败后, 首次创建一个 Cell[] 数组, 初始值为 2
  3. 多个线程竞争同一个 Cell 比较激烈是, 可能需要对Cell[] 扩容

LongAdder

public class LongAdder extends Striped64 implements Serializable {
 
    // ...
    
    
    public void add(long x) {
        // as : Cell[] 数组
        // b : beas 属性
        // v : 当前线程 hash 到 Cell 中存储的值
        // m : Cell[] 的长度 - 1
        // a : 当前线程 hash 到的 Cell 对象
        // x : 需要增加的值
        Cell[] as; long b, v; int m; Cell a;
        // 若还没有建立 Cell[] 数组, 进入到 casBase() 方法去 cas 更新 base 属性
        // 若多个线程更新 base 属性, 则由于争抢的原因其它线程返回 false 执行 if 体 
        if ((as = cells) != null || !casBase(b = base, b + x)) {
			// 竞争标识
            boolean uncontended = true;
            // 若 Cell[] 数组没有建立, 执行 longAccumulate()
            // 若 Cell[] 数组长度为 0, 执行 longAccumulate()
            // 若当前线程分配到的 Cell 为空, 则执行 longAccumulate() 初始化
            // 若多个线程同时 CAS 更新 Cell, 若更新失败, 执行 longAccumulate()
            if (as == null || (m = as.length - 1) < 0 ||
                // getProbe() 方法返回的是线程中 threadLocalRandomProbe 字段
                // 它通过随机数生成一个值, 对于一个确定的线程这个值是固定的, 除非刻意修改它
                (a = as[getProbe() & m]) == null ||
                !(uncontended = a.cas(v = a.value, v + x)))
                longAccumulate(x, null, uncontended);
        }
    }
    
    
    public long sum() {
        Cell[] as = cells; Cell a;
        long sum = base;
        if (as != null) {
            for (int i = 0; i < as.length; ++i) {
                if ((a = as[i]) != null)
                    sum += a.value;
            }
        }
        return sum;
    }
    
    // ...
    
}

Striped64

abstract class Striped64 extends Number {
	// ....

    
    transient volatile int cellsBusy;
    
    
    final boolean casCellsBusy() {
        return UNSAFE.compareAndSwapInt(this, CELLSBUSY, 0, 1);
    }
    
    
    static final int advanceProbe(int probe) {
        probe ^= probe << 13;   // xorshift
        probe ^= probe >>> 17;
        probe ^= probe << 5;
        UNSAFE.putInt(Thread.currentThread(), PROBE, probe);
        return probe;
    }
    
    
    // x : 需要增加的数值
    // fn: 一般为 null 
    // wasUncontended : 竞争标识 -> 出现 CAS 更新 Cell 失败时会传递 false
    final void longAccumulate(long x, LongBinaryOperator fn,
                              boolean wasUncontended) {
        // 存储线程的 Probe 值
        int h;
        // 若获取饿的 Probe 值为 0 则强制生成一个 Probe
        if ((h = getProbe()) == 0) {
            ThreadLocalRandom.current(); // force initialization
            h = getProbe();
            // 获取新的 Probe 值, 将竞争标识设置为 true
            wasUncontended = true;
        }
        boolean collide = false;                // True if last slot nonempty
        // 大自旋
        for (;;) {
            // as : Cell[]
            // a : Cell 对象
            // n : Cell[].length
            // v : 预期值
            Cell[] as; Cell a; int n; long v;
            // 多个线程同时命中一个 Cell , Cell 已经初始化完成
            if ((as = cells) != null && (n = as.length) 1> 0) {
                // 当前 Cells 数组中的 这个 Cell 对象被初始化
                if ((a = as[(n - 1) & h]) == null) {
                    // 检查 是否有线程正在 修改 / 创建 Cell [ 0 : 有 / 1 : 没有 ]
                    if (cellsBusy == 0) {       // Try to attach new Cell
                        // 创建 Cell 对象并赋值
                        Cell r = new Cell(x);   // Optimistically create
                        // 再次检查 cellsBusy 并且 CAS 获取 修改锁 cellsBusy ( 0 -> 1 )
                        if (cellsBusy == 0 && casCellsBusy()) {
                            // 创建成功标识
                            boolean created = false;
                            try {               // Recheck under lock
                                // 获取 cell 数组
                                Cell[] rs; int m, j;
                                // 检查 Cells 是否进行了初始化
                                if ((rs = cells) != null &&
                                    // 检查 Cells 数量是否大于 0
                                    (m = rs.length) > 0 &&
                                    // 检查当前现场命中的单元格是否为 null
                                    rs[j = (m - 1) & h] == null) {
                                    // 将创建好的 Cell 对象放置在数组相应位置
                                    rs[j] = r;
                                    // 更新 创建标识
                                    created = true;
                                }
                            } finally {
                                // 归还 修改锁
                                cellsBusy = 0;
                            }
                            // 创建成功跳出循环
                            if (created)
                                break;
                            continue;           // Slot is now non-empty
                        }
                    }
                    collide = false;
                }
                // CAS 失败, 修改 竞争标识, 调至最后, 重新 hash
                else if (!wasUncontended)       // CAS already known to fail
                    wasUncontended = true;      // Continue after rehash
                // 再次进行 CAS 更新, 若更新失败 则重新 hash, 成功退出
                else if (a.cas(v = a.value, ((fn == null) ? v + x :
                                             fn.applyAsLong(v, x))))
                    break;
                // 数组到达上限不再扩容
                else if (n >= NCPU || cells != as)
                    collide = false;            // At max size or stale
                else if (!collide)
                    collide = true;
                // 扩容
                // 获取 修改锁
                else if (cellsBusy == 0 && casCellsBusy()) {
                    try {
                        // 检查当前 cells 与 as 是否一致
                        if (cells == as) {      // Expand table unless stale
                            // 扩容 n 左移 1 位 -> n * 2
                            Cell[] rs = new Cell[n << 1];
                            // 拷贝数据
                            for (int i = 0; i < n; ++i)
                                rs[i] = as[i];
                            // 更新 cells 数组
                            cells = rs;
                        }
                    } finally {
                        // 归还修改锁
                        cellsBusy = 0;
                    }
                    collide = false;
                    continue;                   // Retry with expanded table
                }
                h = advanceProbe(h);
            }
            // cellsBusy == 0 检查是否有线程在创建 Cells 或者修改 Cells 大小
            // cells == as 获取 Cells
            // casCellsBusy() CAS 自旋设置 cellsBusy 为 1 (锁定)
            else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
                boolean init = false;
                try {                           // Initialize table
                    // 双端检索 -> 若不进行双端检索的话, 则会出现上一个线程被挂起后, 另一个线程在初始化之后又被上一个线程再次初始化导致数据出现异常
                    if (cells == as) {
                        // 创建一个容量为 2 的 Cell[]
                        Cell[] rs = new Cell[2];
                        // 将当前线程 probe 值与 1 运算初始化一个新的 Cell 对象
                        rs[h & 1] = new Cell(x);
                        // 初始化 Cells 数组
                        cells = rs;
                        // 将初始化标识设置为 true
                        init = true;
                    }
                } finally {
                    // 释放 Cells 初始化 / 修改 锁
                    cellsBusy = 0;
                }
                // 初始化成功退出循环
                if (init)
                    break;
            }
            // 多个线程在进行自旋, 若某个线程正在初始化 Cells 则会在这个分支尝试更新 base
            // fn 为参数运算规则, 默认为 相加
            else if (casBase(v = base, ((fn == null) ? v + x :
                                        fn.applyAsLong(v, x))))
                break;                          // Fall back on using base
        }
    }
    
    
    static final int getProbe() {
        return UNSAFE.getInt(Thread.currentThread(), PROBE);
    }
    
	// ....
}
对比

LongAdder 对比 AtomicLong

  • AtomicLong

    • 特点
      • 线程安全, 允许线程损耗, 追求高精度可用
      • 保证精度牺牲性能
      • 多个线程单点 CAS 更新
    • 原理
      • CAS + 自旋
      • incrementAndGet
    • 适用场景
      • 低并发全局运算
      • 并发下保证数据准确, CAS 解决并发问题
    • 缺陷
      • 高并发下性能下降
        • 自旋消耗 CPU
  • LongAdder

    • 特点
      • 高并发中高性能, 精度低, 最终结果正确
      • 保证性能, 牺牲精度和空间
      • 更新线程对应的 Cell
    • 原理
      • CAS + Base + Cells
      • 空间换取时间, 分散热点
    • 场景
      • 高并发下低精度全局计算
    • 缺陷
      • 中间结果不准确, 最终结果准确
ThreadLocal

线程局部变量

初识Demo
public class ThreadLocalTest {
    
    private int all;
    private ThreadLocal mySale = ThreadLocal.withInitial(() -> 0);

    public void mySaleHouse(int num){
        mySale.set(num + mySale.get());
    }

    public synchronized int saleHouse(int num){
        return all += num;
    }

    public int getAll() {
        return all;
    }

    public ThreadLocalTest setAll(int all) {
        this.all = all;
        return this;
    }

    public static void main(String[] args) {
        ThreadLocalTest threadLocalTest = new ThreadLocalTest();
        ThreadPoolExecutor pool = MyThreadPool.getPool();
        pool.submit(() -> {
            for (int i = 0; i < 5; i++) {
                pool.submit(() -> {
                    try {
                        int size = new Random().nextInt(5) + 1;
                        threadLocalTest.mySaleHouse(size);
                        System.out.println(Thread.currentThread().getName() + " 当前售出 : " + threadLocalTest.mySale.get() + " 共 : " + threadLocalTest.saleHouse(size));
                    } finally {
                        // 移除线程本地变量, 因为在线程池中, 线程会复用, 可能造成内存泄露和逻辑问题
                        threadLocalTest.mySale.remove();
                    }
                });
            }
        });
        try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println("共 : " + threadLocalTest.getAll());
        pool.shutdown();
    }
}
特点
  1. 每个线程内部有自己的实例副本, 而且该副本只能有当前线程自己可用
  2. 既然 Thread 不可访问, 没有线程安全问题
  3. 统一设置初识值, 但是每个线程对这个值的修改都是相互独立的
Thread / ThreadLocal / ThreadLocalMap 源码
public class Thread implements Runnable {
	// ...
    
    
    ThreadLocal.ThreadLocalMap threadLocals = null;

	// ...
}

public class ThreadLocal { 
	// ...

    static class ThreadLocalMap { 
        // ... 
        
        
        static class Entry extends WeakReference> {
            
            Object value;

            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }
        
        // ...
    }
    
    
    public T get() {
        // 获取当前线程
        Thread t = Thread.currentThread();
        // 获取 Map 对象
        ThreadLocalMap map = getMap(t);
        // 判空
        if (map != null) {
            // 获取 entry  ->  (key, value)
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        // map 为空时 初始化
        return setInitialValue();
    }
    
    //...
}

Thread 与 ThreadLocal

Thread 内部有一个 ThreadLocalMap 的变量

ThreadLocal 与 ThreadLocalMap

ThreadLocalMap 是 ThreadLocal 的静态内部类

ThreadLocalMap 内部有一个 Entry 静态内部类

使用 ThreadLocal 作为 key

使用 任意对象 作为 Value

总结

JVM 内部维护了一个 Map, 通过 ThreadLocal 中的 set 方法, 将 ThreadLocal 和 数据 放入到这个 Map 中, 这样每个线程都有了一个独立的变量.

ThreadLocal 内存泄露问题 内存泄露

不会被使用的对象或者变量占用的内存不能被收回

四种引用
  • 强引用
    • 除非手动取消连接, 或者超出作用域否则不会被 GC 回收
  • 软引用
    • 若内存足够, 不会回收对象, 若内存不足, GC 会回收软引用对象
  • 弱引用
    • 无论内存是否足够, 执行 GC 就会被回收
  • 虚引用
    • 必须和引用队列 ( ReferenceQueue ) 一起使用
      • 任何时间都会被 垃圾回收器 回收
    • PhantomReference 的 get 方法总是返回 null
    • 处理监控通知使用
      • 提供比 finalize 机制更为灵活的回收操作
为什么使用 弱引用
  1. thread -> threadLocal 强引用
  2. 若 threadLocalMap 中的 Entry -> threadLocal ( 作为 map 的 key ) 为强引用
  3. 则 thread 结束后 thread -> threadLocal 的强引用失效, 但是由于 threadLocalMap 中 Entry -> threadLocal 的强引用, 所以这个thread 指向的 threadLocal 不会被回收, 这样 threadLocal 和 threadLocalMap 中的 Entry 就会造成内存泄露
  4. 若 threadLocalMap 中的 Entry -> threadLocal ( 作为 map 的 key ) 为弱引用
  5. thread 结束后 thread -> threadLocal 的强引用失效, 并且由于 Entry -> threadLocal ( 作为 map 的 key ) 为弱引用, 所以在下一次垃圾回收时, 该 threadLocal 对象会被回收, Entry 中的 threadLocal 会指向一个 null

图片

不断变多的 Entry(null, value)
  1. 若不断有线程在结束前没有移除 ThreaLocal 对象在 ThreadLocalMap 中 Entry 对应的 Value
  2. 则 由于弱引用和线程的结束, 在 ThreadLocalMap 中会出现很多指向 null 的 Entry 对象
  3. 若使用线程池, 则会出现 ThreadPool -> Thread -> ThreadLocal -> ThreadLocalMap -> Entry -> Value
  4. 这样这些 Entry(null, value) 就无法被回收, 造成内存泄露

解决

ThreadLocal 内部的一些方法(如: set / get / expungeStaleEntry / … ) 回去清除内部的 无效Entry

**主要解决方式为: ** 不再使用某个 ThreadLocal 对象后, 手动调用 remove 方法进行删除

对象内存布局和对象头

对象的内存布局可以划分为三个部分, 对象头 ( Header ) / 实例数据 ( Instance Date ) / 对齐填充 ( Padding ) , 这三个构成一个对象实例

对象在堆内存中的存储布局
  • 对象实例
    • 对象头
      • 对象标记 MarkWord
        • 哈希码
        • GC 标记
        • GC 次数
        • 同步锁标记
        • 偏向锁持有者
      • 类元信息( 类型指针 )
    • 实例数据
    • 对齐填充
      • 保证 8 个字节的倍数
对象标记

HotSpot 虚拟机对象头 Mark Word

存储内容标志位状态
对象哈希码 / 对象分代年龄01未锁定
指向锁记录的指针00轻量级锁定
指向重量锁的指针10膨胀(重量级锁定)
空, 不需要记录信息11GC 标记
偏向线程 ID / 偏向时间戳 / 对象分代年龄01可偏向

64 位 系统中, MarkWrod 占了 8 个字节, 类型指针占了 8 个字节, 一共 16 个字节

类元信息( 类型指针 )

对象指向它的类元数据的指针, 虚拟机通过这个指针确定这个对象是那个类的实例

实例数据

存放类的属性( Field )数据信息, 包括父类的属性信息

对齐填充

虚拟机要求对象的起始地址必须是 8 字节的整数倍, 填充数据不是必须存在的, 仅仅是为了字节对齐这部分内存按 8 字节补充对齐

对象指针只有 4 字节?

java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

前两行 为 MarkWord 对象头 8位

第三行 为 类型指针 4 位

第四行 为 补充 4位

总共 16 位

**原因 : **JVM 启动时设置了压缩指针, 将类型指针由 8 位压缩为 4 位, 提高了性能.

GC 为什么最大为 15 次

GC 年龄采用 4 bit 存储 即存储范围 0000 -> 11111, 即 0 - 15

各个基本数据类型所占字节数
public class ObjectHeadTest {

    public static void main(String[] args) {

        System.out.println(ClassLayout.parseInstance(new MyObj()).toPrintable());

        

    }

}

class MyObj{

    boolean booleanField;
    byte byteField;
    char charField;
    short shortField;
    int intField;
    long longField;
    float floatField;
    double doubleField;

}
Synchronized 与 锁升级

锁的升级过程

无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁

synchronized 锁: 由对象头中的 Mark Word 根据锁标志为的不同而被复用及锁升级策略

synchronized 的性能变化

重量级锁, 竞争激烈的情况下性能下降

为什么每一个对象都可以成为一把锁

对象内部都有一个锁, Monitor 锁 ( 内部锁 )

Monitor 的本质是依赖于底层的操作系统的 MutexLock 实现, 操作系统实现线程之间的切换需要从用户态到内核态的转变, 成本高昂.

Monitor 与 java 对象是如何关联的
  1. 如果某个对象被线程锁住, 则该对象的 MarkWord 字段中的 LockWord 指向 monitor 的起始地址
  2. Monitor 的 Owner 字段会存放拥有相关联对象锁的线程 ID
JUC_Synchronized加锁过程

synchronized 锁种类以及升级步骤 多线程的访问情况
  1. 只有一个线程
  2. 多个线程交替访问
  3. 更多线程来争抢
升级流程

锁的升级主要依赖 MarkWord 中锁的标志位和释放偏向锁标志位

锁的指向

偏向锁: MarkWord 存储的是偏向的线程 ID

轻量锁: MarkWord 存储的是指向线程栈中 LockRecord 的指针

重量锁: MarkWord 存储的是指向队中的 monitor 对象的指针

无锁

初始状态, 一个对象被实例化后, 如果没有被任何线程竞争锁, name它就是无锁状态 001

public static void main(String[] args) {

    Object o = new Object();

    // 只用调用过 hashCode() 方法后才可以获取 hashCode 码
    System.out.println("二进制 : " + Integer.toBinaryString(o.hashCode()));
    System.out.println("十进制 : " + o.hashCode());
    System.out.println("十六进制 : " + Integer.toHexString(o.hashCode()));

    System.out.println(ClassLayout.parseInstance(o).toPrintable());

    // 二进制 : 1110100101000010100010010000010
    //         1110100101000010100010010000010

    
}

解读

偏向锁 什么是偏向锁

偏向锁: 单线程竞争

当线程 A 第一次竞争到锁时, 通过操作修改 MarkWord 中的偏向线程 ID / 偏向模式

如果不存在其他线程竞争, 那么持有偏向锁的线程将永远不需要进行同步

作用

当一段同步代码一直被同一个线程多次访问, 由于只有一个线程, 那么该线程在后续访问时便会自动获得锁

偏向锁会偏向于第一个访问锁的线程, 如果在接下来的运行过程中, 该锁没有被其他的线程访问, 则持有偏向锁的线程永远不需要触发同步. 也就偏向锁在资源没有竞争的情况下消除了同步语句.

64位标记图

持有

大多数情况下, “锁总是同一个线程持有, 很少发生竞争”, 锁总是被第一个占用他的线程拥有, 这个线程就是锁的偏向线程.

只要锁在第一次被拥有的时候, 记录下偏向线程 ID. 这样偏向线程就一直持有着锁 ( 后续这个线程进入和退出这段加了同步锁的代码块是, 不需要再次加锁和释放锁. 而是去检查锁的 MarkWord 是不是存放的自己的线程 ID )

如果相等, 标识偏向锁是偏向于当前线程的, 就不需要再尝试获得锁, 知道竞争发生才释放锁. 以后每次同步, 检查锁的偏向线程 ID 是否与当前 线程ID 一直, 如果一致直接进入同步. 无需每次加锁解锁都去 CAS 更新对象头. 如果自始至终使用锁的线程只有一个, 很明显偏向锁几乎没有额外开销, 性能极高.

如果不相等, 表示发生了竞争, 锁已经不是总是偏向于同一个线程了, 这个时候尝试使用 CAS 来替换 MarkWord 里面的线程 ID 为新线程的 ID.

竞争成功, 表示之前的线程不存在了, MarkWord 里面的线程 ID 为新线程的 ID, 锁不会升级, 仍为偏向锁.

竞争失败, 这时候可能需要升级变为轻量级锁, 才能保证线程间公平竞争锁.

偏向锁只有遇到其他线程尝试竞争偏向锁时, 持有偏向锁的线程才会释放锁, 线程是不会自动释放偏向锁的.

技术实现

一个 synchronized 方法被一个线程抢到了锁时, 这个方法所在的对象就会在其所在的 MarkWord 中奖偏向锁修改状态位, 同时还会有占用前 54 位来存储线程指针作为标识. 若线程再次访问同一个 synchronized 方法时, 该线程只需去对象头的 MarkWord 中去判断一下是否有偏向锁执行本身的 ID, 无需再进入 Monitor 去竞争对象了

偏向锁 JVM 命令

偏向锁在 1.6 之后就默认开启, 但是启动有时间限制( 4秒 )

Demo
public static void main(String[] args) {

    try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }

    Object o = new Object();

    System.out.println("获取锁前: ");
    System.out.println(ClassLayout.parseInstance(o).toPrintable());

    System.out.println("================================================================");
    System.out.println("获取锁后: ");
    new Thread(() -> {
        synchronized (o){
            System.out.println(ClassLayout.parseInstance(o).toPrintable());
        }
    }).start();
}

偏向锁的撤销

当其他线程逐步开始来竞争锁时, 就无法使用偏向锁, 将升级为轻量级锁

竞争线程尝试 CAS 更新对象头失败, 会等待到全局安全点( 此时不会执行任何代码 ) 撤销偏向锁

  1. 线程 A 正在执行同步方法( 处于同步块 ), 没有执行完, 其他线程进行争抢, 该偏向锁会被取消并出现锁升级. 此时轻量级锁有原持有偏向锁的线程持有, 继续执行其同步代码, 而正在竞争的线程会进入自旋等待获得该轻量级锁.
  2. 线程 A 执行完成同步方法( 退出同步块 ), 将对象头设置成无锁状态并撤销偏向锁, 重新偏向.
总体步骤

JAVA 15 已经逐步放弃偏向锁

原因开销大, 可以自己手动开启

轻量级锁 是啥

多线程竞争, 但是任意时刻最多只有一个线程竞争, 即不存在锁竞争太过激烈的情况, 也就没有线程阻塞

主要作用
  1. 存在线程参与锁的竞争, 但是获取锁的冲突时间极短
  2. 本质就是 CAS 自旋锁
64位标记图

轻量级锁的获取

轻量级锁是为了在线程近乎交替执行同步块时提高性能。

主要目的: 在没有多线程竞争的前提下,通过CAS减少重量级锁使用操作系统互斥量产生的性能消耗,说白了先自旋,不行才升级阻塞。

**升级时机:**当关闭偏向锁功能或多线程竞争偏向锁会导致偏向锁升级为轻量级锁

假如线程A已经拿到锁,这时线程B又来抢该对象的锁,由于该对象的锁已经被线程A拿到,当前该锁已是偏向锁了。

而线程B在争抢时发现对象头Mark Word中的线程ID不是线程B自己的线程ID(而是线程A),那线程B就会进行CAS操作希望能获得锁。

此时线程B操作中有两种情况:

如果锁获取成功,直接替换Mark Word中的线程ID为B自己的ID(A→B),重新偏向于其他线程(即将偏向锁交给其他线程,相当于当前线程"被"释放了锁),该锁会保持偏向锁状态,A线程Over,B线程上位;

**如果获取失败,**则偏向锁会升会级为轻量级锁, 设置偏向锁标致位为 0 , 设置锁的标记位为 00, 此时轻量级锁由原持有偏向锁的线程持有, 继续执行其同步代码, 而正在竞争的线程 B 会进入自旋等待获取该轻量级锁.

**补充 : **

轻量级锁的加锁

JVM会为每个线程在当前线程的栈帧中创建用于存储锁记录的空间,官方称为 Displaced Mark Word。若一个线程获得锁时发现是轻量级锁,会把锁的 MarkWord 复制到自己的 Displaced Mark Word 里面。然后线程尝试用CAS 将锁的 MarkWord 替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示 Mark Word 已经被替换成了其他线程的锁记录,说明在与其它线程竞争锁,当前线程就尝试使用自旋来获取锁。

自旋CAS: 不断尝试去获取锁,能不升级就不往上捅,尽量不要阻塞

轻量级锁的释放

在释放锁时,当前线程会使用 CAS 操作将 Displaced Mark Word 的内容复制回锁的 Mark Word 里面。如果没有发生竞争,那么这个复制的操作会成功。如果有其他线程因为自旋多次导致轻量级锁升级成了重量级锁,那么CAS操作会失败,此时会释放锁并唤醒被阻塞的线程。

Demo
public static void main(String[] args) {

    // 关闭睡眠, 自旋锁在程序运行 4 秒后才会生效
    // try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }

    Object o = new Object();

    System.out.println("获取锁前: ");
    System.out.println(ClassLayout.parseInstance(o).toPrintable());

    System.out.println("================================================================");
    System.out.println("获取锁后: ");

    new Thread(() -> {

        synchronized (o){
            System.out.println(ClassLayout.parseInstance(o).toPrintable());
        }

    }).start();


    
}
自旋达到一定的次数和程度
  1. java 6 之前默认为 10 次
  2. java 6 之后为 自适应次数
    1. 自适应意味着自选的次数不是固定不变的
    2. 根据
      1. 上一次自旋的时间
      2. 拥有锁线程的状态
与偏向锁的区别

争夺轻量级锁失败时, 自旋尝试抢占锁

轻量级锁每次退出同步块都需要释放锁, 而偏向锁是在竞争发生时才释放锁

重量级锁

大量的线程参与锁的竞争, 冲突性很高

锁的标志位

锁的标志位 -> 10

指向互斥量 ( 重量级锁) 的指针

原理

Java 中 synchronized 的重量级锁,是基于进入和退出 Monitor 对象实现的。在编译时会将同步块的开始位置插入 monitor enter 指令,在结束位置插入monitor exit 指令。

当线程执行到 monitor enter 指令时,会尝试获取对象所对应的 Monitor 所有权,如果获取到了,即获取到了锁,会在 Monitor 的 owner 中存放当前线程的 id,这样它将处于锁定状态,除非退出同步块,否则其他线程无法获取到这个 Monitor。

Demo
public static void main(String[] args) {

    Object o = new Object();

    new Thread(() -> {

        synchronized (o){
            // 休眠 3 秒保证自旋到达轻量锁上限, 使锁膨胀为重量锁
            try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println(ClassLayout.parseInstance(o).toPrintable());
            try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
        }

    }).start();

    new Thread(() -> {

        synchronized (o){
            System.out.println(ClassLayout.parseInstance(o).toPrintable());
        }

    }).start();

    

}
总结 锁升级后 hashCode 去哪了?

在无锁状态下,Mark Word 中可以存储对象的 identity hash code 值。当对象的 hashCode() 方法第一次被调用时,JVM 会生成对应的 identity hash code 值并将该值存储到 Mark Word 中。

对于偏向锁,在线程获取偏向锁时,会用 Thread lD 和 epoch 值覆盖 identity hash code 所在的位置。如果一个对象的 hashCode() 方法已经被调用过一次之后,这个对象不能被设置偏向锁。因为如果可以的话,那 Mark Word 中的 identity hash code 必然会被偏向线程 ld 给覆盖,这就会造成同一个对象前后两次调用 hashCode() 方法得到的结果不一致。

升级为轻量级锁时,JVM 会在当前线程的栈帧中创建一个锁记录 ( Lock Record ) 空间,用于存储锁对象的 Mark Word 拷贝,该拷贝中可以包含 identity hash code,所以轻量级锁可以和 identity hash code 共存,哈希码和GC 年龄自然保存在此,释放锁后会将这些信息写回到对象头。

升级为重量级锁之后, MarkWord 保存的重量级锁指针, 代表重量级锁的 ObjectMonitor 类里有字段记录非加锁状态下的 MarkWord, 锁释放后也会将信息写回到对象头.

各种锁的对比
锁优点缺点适用场景
偏向锁加锁解锁不需要额外消耗, 和执行非同步方法相比仅存在极小的差别线程间存在锁竞争, 会带来额外的偏性锁撤销的消耗只有一个线程访问同步块的场景
轻量级锁竞争的线程不会阻塞, 提高了程序的相应速度如果始终得不到锁竞争的线程会进行自旋消耗CUP追求响应时间, 同步块执行速度非常快
重量级锁线程竞争不使用自旋, 不会消耗 CPU线程阻塞, 响应时间慢追求吞吐量, 同步块执行时间长
synchronized 锁的升级过程

先自旋, 不行就阻塞

在一定条件下, 先使用偏向锁和自旋锁(CAS)

内部基于 MarkWord 实现

无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁

JIT 编译器对锁的优化 JIT

JIT ( Just In Time Compiler ) 及时编译器

锁消除
public class LockClearDemo {

    static Object lock = new Object();

    public static void method() {

        // 同步是为了多个线程只有一个进入同步代码块
        // 当前的做法为
        // 每来一个线程都给一把新锁, 这个同步没有任何意义
        // 相当于这个 synchronized 没有生效, 编译器底层也不会对这个代码块上锁
        Object o = new Object();
        synchronized (o) {
            System.out.println(Thread.currentThread().getName() + "t" + o.hashCode() + "t" + lock.hashCode());
        }

    }

    public static void main(String[] args) {

        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                method();
            }, Integer.toString(i)).start();
        }

        
    }

}

锁粗化
public class LockCoarseningDemo {

    static Object lock = new Object();

    public static void main(String[] args) {

        new Thread(() -> {

            synchronized (lock){
                System.out.println("111");
            }
            synchronized (lock){
                System.out.println("222");
            }
            synchronized (lock){
                System.out.println("333");
            }
            synchronized (lock){
                System.out.println("444");
            }
			// 虽然没问题, 但是编译器底层会优化类似于下面的样子
//            synchronized (lock){
//                System.out.println("111");
//                System.out.println("222");
//                System.out.println("333");
//                System.out.println("444");
//            }

        }, "A").start();

    }

}
AQS ( AbstractQueuedSynchronizer ) 前置知识 公平锁与非公平锁 可重入锁 自旋 LockSupport 双向链表 模板设计模式 入门理论 简介

AbstractQueuedSynchronizer 抽象队列同步器

源码
public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
    
    // ...
    
}
技术解释

用来实现锁或者其他同步器组建的公共基础部分的抽象实现, 是重量级基础框架及整个 JUC 体系的基石, 主要用于解决锁分配给"谁"的问题

整体使用一个抽象的 FIFO 队列来完成资源获取线程的排队工作, 并通过一个 int 变量表示持有锁的状态

为什么称为 JUC 的基石

锁, 面向锁的使用者, 定义了程序员和锁交互的使用层 API, 隐藏了实现细节

同步器, 面向锁的实现者, 提出统一规范并且简化了锁的实现, 将其抽象出来屏蔽了 同步状态管理 / 同步队列的管理和 维护 / 阻塞线程排队 和 通知 / 唤醒机制 等, 是一切锁和同步组件实现的 公共基础部分

作用

加锁会导致阻塞, 有阻塞就需要排队, 实现排队必然需要队列

抢到资源的线程直接使用处理业务,抢不到资源的必然涉及一种排队等候机制。抢占资源失败的线程继续去等待(类似银行业务办理窗口都满了,暂时没有受理窗口的颐客只能去候客区排队等候),但等候线程仍然保留获取锁的可能且获取锁流程仍在继续(候客区的颐客也在等着叫号,轮到了再去受理窗口办理业务)。

既然说到了排队等候机制,那么就一定会有某种队列形成,这样的队列是什么数据结构呢?

如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的是 CLH 队列的变体实现的,将暂时获取不到锁的线程加入到队列中,这个队列就是 AQS 同步队列的抽象表现。它将要请求共享资源的线程及自身的等待状态封装成队列的结点对象 ( Node ),通过 CAS、自旋以及 LockSupport.park() 的方式,维护state 变量的状态,使并发达到同步的效果。

AQS 使用一个 volatile 的 int 类型的成员变量 state 来标识同步状态, 通过内置的 FIFO 队列完成资源获取的排队工作将每条要去抢占资源的线程封装成一个 Node 节点来实现锁的分配, 通过 CAS 完成对 state 值的修改

源码分析前置 AQS 组成 int 变量 state
private volatile int state;

// 0 表示空闲, state > 0 表示占用
CLH 队列
    

使用 CAS 自旋等待, 使用 State 变量判断阻塞, 新节点从尾部入队, 从头部出队.

总结

阻塞就去排队, 排队去队列

state + CLH 队列

内部类 Node

使用 waitStatus 作为节点状态标识

        
        volatile int waitStatus;
static final class Node {
    
    static final Node SHARED = new Node();
    
    static final Node EXCLUSIVE = null;

    static final int CANCELLED =  1;
   
    static final int SIGNAL    = -1;
    
    static final int CONDITION = -2;

    static final int PROPAGATE = -3;

    volatile int waitStatus;

    volatile Node prev;

    volatile Node next;

    volatile Thread thread;

    Node nextWaiter;

    final boolean isShared() {
        return nextWaiter == SHARED;
    }

    final Node predecessor() throws NullPointerException {
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }

    Node() {    
    }

    Node(Thread thread, Node mode) {     
        this.nextWaiter = mode;
        this.thread = thread;
    }
 
    Node(Thread thread, int waitStatus) { 
        this.waitStatus = waitStatus;
        this.thread = thread;
    }
}
源码
// Debug源码
package aqs;

import java.util.Scanner;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;


public class DebugDemo {

    public static ReentrantLock lock = new ReentrantLock();
    public static volatile int input = 0;
    public static Scanner scanner = new Scanner(System.in);
    public static CountDownLatch count = new CountDownLatch(10);


    public static void main(String[] args) throws InterruptedException {

        while (true) {
            System.out.println("tmenut" +
                    " -999 : main break n" +
                    " .... n" +
                    " -2   : thread B start n" +
                    " -1   : thread A start n" +
                    " 0    : default n" +
                    " 1    : thread A break n" +
                    " 2    : thread B break n" +
                    " .... n" +
                    " now : " + input);

            input = scanner.nextInt();

            if(input == -999){
                System.out.println("main break...");
                break;
            }

            switch (input){
                case -4:
                    startThread("D", 4);
                    break;
                case -3:
                    startThread("C", 3);
                    break;
                case -2:
                    startThread("B", 2);
                    break;
                case -1:
                    startThread("A", 1);
                    break;
                case 0:
                    break;
                case 1:
                case 2:
                case 3:
                case 4:
                    break;
                default:
                    System.out.println("irregular number: " + input);
                    System.out.println("resetInput: input = " + (input = 0));
            }

        }

        long remain = count.getCount();
        for (int i = 0; i < remain; i++) {
            System.out.println();
            count.countDown();
        }

        // 阻塞主线程
        count.await();

    }

    public static void startThread(String threadName, int breakFlag){

        new Thread(() -> {

            lock.lock();
            try {

                while (true){
                    try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

                    if(input == breakFlag){
                        System.out.println(Thread.currentThread().getName() + "t break ... ");
                        break;
                    }

                    System.out.println(Thread.currentThread().getName() + "t running ... ");
                }

            } finally {
                lock.unlock();
                count.countDown();
            }

        }, threadName).start();

    }

}





// 1. 创建 lock 对象 非公平
public static ReentrantLock lock = new ReentrantLock();

// 2. 线程开启, lock 对象加锁, 调用方法 lock() 
lock.lock();

// 3. ReenterLock.lock() 
public void lock() { // implements Lock
    sync.lock(); // ReentrantLock.Sync.lock()
}

// 4. sync.lock(); 非公平锁 ReentrantLock.NonfairSync.lock() 进行实现
// ReentrantLock.NonfairSync extend ReentrantLock.Sync
// ReentrantLock.Sync -> abstract static class Sync extends AbstractQueuedSynchronizer
// sync.lock(); 为抽象方法

// java.util.concurrent.locks.ReentrantLock.NonfairSync#lock
final void lock() {
    // 5 CAS 设置 state 状态
    if (compareAndSetState(0, 1)) 
        // true CAS 成功 获取锁成功, 设置独占线程为当前线程
        setExclusiveOwnerThread(Thread.currentThread()); // 6 设置独占线程
    else
        // false CAS 失败 获取锁失败, 进入 acquire(1) 再次尝试获取锁
        acquire(1); // 7 尽量去获取锁
}

// 5. CAS 更改 state 状态
// java.util.concurrent.locks.AbstractQueuedSynchronizer#compareAndSetState
protected final boolean compareAndSetState(int expect, int update) {
    // See below for intrinsics setup to support this
    // 通过 Unsafe 类进行操作, Unsafe 自己去查
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

// 6. 设置独占线程
// java.util.concurrent.locks.AbstractOwnableSynchronizer#setExclusiveOwnerThread
// AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer
protected final void setExclusiveOwnerThread(Thread thread) {
    exclusiveOwnerThread = thread;
}

// 第一个线程: A 加锁完成 ------------------------------------------------------
// 第二个线程: B 开始加锁 ------------------------------------------------------
// 前置步骤一致( 2-5 ), 后续由于 CAS 更改失败, 进入 acquire(1); 分支
// 7. 尽量去获取锁
// java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire
public final void acquire(int arg) {
    // 尝试去获取锁
    if (!tryAcquire(arg) && // 8. 尝试去获取锁
        // 队列线程尝试获取锁↓      包装 Node 节点↓
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))// 9. 包装 Node 节点 10. 队列线程尝试获取锁
        // 线程中断
        selfInterrupt(); // 16. 设置当前线程中断 
}

// 8. 尝试获取锁

// java.util.concurrent.locks.ReentrantLock.NonfairSync#tryAcquire
// 这个方法为 非公平锁内部类的实现, 实际调用的是 Sync (ReenterLock 中 NofairSync 的父类) 中的实现
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires); // Sync 实现的方法
}
// java.util.concurrent.locks.ReentrantLock.Sync#nonfairTryAcquire 
final boolean nonfairTryAcquire(int acquires) {
    // 获取当前线程
    final Thread current = Thread.currentThread();
    // 获取 AQS 内部的 state 属性值, state 表示当前是否有线程正在持有锁
    int c = getState();
    // c = state , 0 空闲, > 0 有线程正在占用
    if (c == 0) {
        // 若没有线程持有锁, 通过 CAS 更改 state 的值, 具体实现转 5.
        if (compareAndSetState(0, acquires)) {
            // CAS 成功后表示获取了锁, 设置独占线程为自己, 具体实现转 6.
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 若有线程正在占用锁 c != 0
    else if (current == getExclusiveOwnerThread()) { // 查看当前线程是否是独占线程
        // 重入次数 + 1
        int nextc = c + acquires;
        // 检查重入超限
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        // 设置 state 数值, 由于当前线程获取了锁, 直接设置即可
        setState(nextc); // 11. 设置 state
        return true;
    }
    // 线程未持有锁 返回 false
    return false;
}

// 9. 包装 Node 节点
// java.util.concurrent.locks.AbstractQueuedSynchronizer#addWaiter
private Node addWaiter(Node mode) {
    // 将当前线程包装为 Node 节点等待入队
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail; // 获取前驱节点
    // 若前驱节点不为 null 
    if (pred != null) {
        // 设置当前节点的前驱节点为 尾结点, 尾插
        node.prev = pred;
        // CAS 更新尾结点
        if (compareAndSetTail(pred, node)) { // 12. CAS 设置尾结点
            // 设置前驱节点的后继节点为自己
            pred.next = node;
            // 返回包装好的 node 对象
            return node;
        }
    }
    // 前驱节点为 null 时, 当前节点为第一个入队的节点
    enq(node); // 13. 入队
    // 13. 之后当前节点加入阻塞队列
    return node;
}

// 10. 队列线程尝试获取锁
// java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireQueued
// Acquires in exclusive uninterruptible mode for thread already in queue. Used by condition wait methods as well as acquire.
final boolean acquireQueued(final Node node, int arg) {
    // 失败标志位
    boolean failed = true;
    try {
        // 中断标志位
        boolean interrupted = false;
        // 自旋
        for (;;) {
            // 获取当前节点的前驱节点
            final Node p = node.predecessor();// 15. 获取当前节点的前驱节点 
            // 若前驱节点为 head 节点 且 尝试获取锁成功 
            if (p == head && tryAcquire(arg)) { // 尝试获取锁, 具体实现转 7.
                // 将当前节点设置为头结点
                setHead(node); // 17. 设置当前节点为头结点
                p.next = null; // help GC 将前驱节点的 后继节点指向 null, 由于17. 已经将一些信息置空, 原头结点就会被出队并且 GC 
                failed = false; // 设置更新成功
                return interrupted; // 返回中断标识
            }
            // 获取锁失败后将线程阻塞
            if (shouldParkAfterFailedAcquire(p, node) && // 18. 获取锁失败后将线程阻塞
                // 阻塞线程, 检查中断
                parkAndCheckInterrupt()) // 20. 线程阻塞, 检查中断
                // 20. 之后线程被阻塞, 等待唤醒
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

// 11. 设置 state
// java.util.concurrent.locks.AbstractQueuedSynchronizer#setState
protected final void setState(int newState) {
    state = newState;
}

// 12. CAS 设置尾结点
// java.util.concurrent.locks.AbstractQueuedSynchronizer#compareAndSetTail
// CAS tail field. Used only by enq.
private final boolean compareAndSetTail(Node expect, Node update) {
    return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}

// 13. 入队
// java.util.concurrent.locks.AbstractQueuedSynchronizer#enq
// Inserts node into queue, initializing if necessary. See picture above.
private Node enq(final Node node) {
    // 自旋
    for (;;) {
        // 获取尾节点
        Node t = tail;
        // 当尾结点为 null 时表示当前节点为第一个入队节点, 这时队列需要初始化
        if (t == null) { // Must initialize
            // CAS 设置头结点, 头结点是一个占位节点 new Node()
            if (compareAndSetHead(new Node())) // 14. CAS 设置头结点
                // 设置尾结点指向, 这时只有一个节点, 头结点变量和尾结点变量同时指向这一个对象
                tail = head; // 设置好后进入下一次循环
        } else {
            // 尾结点不为 null 时
            // 设置传递来的节点的前驱节点为 尾结点
            node.prev = t;
            // CAS 更新 尾结点变量的指向, 具体实现转 12.
            if (compareAndSetTail(t, node)) {
                // 设置前驱节点的后继节点为自己
                t.next = node;
                // 返回前驱节点
                return t;
            }
        }
    }
}

// 14. CAS 设置头结点
// java.util.concurrent.locks.AbstractQueuedSynchronizer#compareAndSetHead
// CAS head field. Used only by enq.
private final boolean compareAndSetHead(Node update) {
    return unsafe.compareAndSwapObject(this, headOffset, null, update);
}

// 15. 获取当前节点的前驱节点 
// java.util.concurrent.locks.AbstractQueuedSynchronizer.Node#predecessor

final Node predecessor() throws NullPointerException {
    // 获取 前驱节点变量指向的对象
    Node p = prev;
    if (p == null)
        throw new NullPointerException();
    else
        return p;
}

// 16. 设置当前线程中断 
// java.util.concurrent.locks.AbstractQueuedSynchronizer#selfInterrupt
// Convenience method to interrupt current thread.
static void selfInterrupt() {
    Thread.currentThread().interrupt();
}

// 17. 设置当前节点为头结点
// java.util.concurrent.locks.AbstractQueuedSynchronizer#setHead

private void setHead(Node node) {
    // 设置 head 变量的指向
    head = node;
    // 设置当前节点存储线程为 null
    node.thread = null;
    // 设置当前节点的前驱节点为 null
    node.prev = null;
    
    // 就是将当前节点中存储的信息置空, 当前线程已经抢到锁了, 没必要继续保留节点内部信息
    // 释放原本的头结点连接, 将当前节点内部置空, 相当于第一个节点出队
    // 转 13. 初始化队列时, 头结点就是一个 new Node(), 空节点
}

// 18. 获取锁失败后将线程阻塞
// java.util.concurrent.locks.AbstractQueuedSynchronizer#shouldParkAfterFailedAcquire 

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    // 获取前驱节点的等待状态 waitStatus, 具体参数转 19. 
    int ws = pred.waitStatus;
    // 检查当前节点的 等待状态 
    if (ws == Node.SIGNAL)
        
        return true;
    if (ws > 0) {
        
        // 多个节点同时取消等待时获取不再取消的节点
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        
        // CAS 更新节点的 等待状态 waitState
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

// 19. Node 节点的等待状态 

static final int CANCELLED =  1;

static final int SIGNAL    = -1;

static final int CONDITION = -2;

static final int PROPAGATE = -3;

// 20. 线程阻塞, 检查中断
// java.util.concurrent.locks.AbstractQueuedSynchronizer#parkAndCheckInterrupt

private final boolean parkAndCheckInterrupt() {
    // 阻塞线程
    LockSupport.park(this);
    return Thread.interrupted();
}

// 第二个线程: B 阻塞结束 ------------------------------------------------------
// 后续开启 C D 线程后与 B 流程相似
// 线程 A 解锁 ---------------------------------------------------------------
// 21. 解锁
// java.util.concurrent.locks.ReentrantLock#unlock
public void unlock() 
    // 释放锁
    sync.release(1); // 22. 释放锁 
}

// 22. 释放锁
// java.util.concurrent.locks.AbstractQueuedSynchronizer#release
public final boolean release(int arg) {
    // 尝试释放锁
    if (tryRelease(arg)) { // 23. 尝试释放锁
        Node h = head;
        // 队列中存在 node 节点(存在被阻塞线程), 头结点的状态不为初始状态( 存在后继节点 ), 转 10 去寻找原因
        if (h != null && h.waitStatus != 0)
            // 唤醒线程
            unparkSuccessor(h); // 24. 唤醒线程
        return true;
    }
    return false;
}

// 23. 尝试释放锁

// java.util.concurrent.locks.ReentrantLock.Sync#tryRelease
protected final boolean tryRelease(int releases) {
    // 减少 state 的值, 这个是可重入锁
    int c = getState() - releases;
    // 检查当前线程是否是独占线程
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    // 释放锁的标识
    boolean free = false;
    // 若 c = state 的值为 0, 表示所有重入都已经退出, 锁释放
    if (c == 0) {
        // 修改标识
        free = true;
        // 设置独占线程为 null
        setExclusiveOwnerThread(null);
    }
    // 设置 state 的值
    setState(c);
    // 返回释放是否成功
    return free;
}

// 24. 唤醒线程
// java.util.concurrent.locks.AbstractQueuedSynchronizer#unparkSuccessor

// 这个传入的 node 对象在 22. 是头结点
private void unparkSuccessor(Node node) {
    
    // 获取当前节点的等待状态
    int ws = node.waitStatus;
    // < 0, CAS 设置节点的状态为 0
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    
    // 获取当前节点的后继节点
    Node s = node.next;
    // 检查节点状态, 当前节点为 null, 当前节点放弃等待锁
    if (s == null || s.waitStatus > 0) {
        // 设置节点指向为 null
        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);
}

// 线程 A 解锁完成 ------------------------------------------------------------
// 后续线程开始争抢锁 ----------------------------------------------------------

// 线程被唤醒
// 续接 20. 返回中断标识
// 转到 10. 去尝试获取锁, 因为是非公平锁, 所以可能会出现新的线程来争抢
// 争抢失败继续阻塞
// end

各种锁

无锁 -> 独占锁 -> 读写锁 -> 邮戳锁

ReentrantReadWriteLock

读写互斥, 写独占, 读共享

缺点
  • 写锁饥饿
    • 写锁远远小于读锁时, 写锁抢不到锁
  • 锁降级
Demo
public class ReadWriteLockDemo {

    private Map map = new HashMap<>();

    private ReentrantLock lock = new ReentrantLock();
    private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    public Integer read(int key) {

//        lock.lock();
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "t开始读取");
            // 模拟读取耗时, 读取线程量大
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + "t读取结果: " + map.get(key));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
//            lock.unlock();
            readWriteLock.readLock().unlock();
        }

        return null;
    }

    public void write(int key, int value) {
//        lock.lock();
        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "t开始写入");
            map.put(key, value);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "t写入结束");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
//            lock.unlock();
            readWriteLock.writeLock().unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {

        ReadWriteLockDemo demo = new ReadWriteLockDemo();

        long start = System.currentTimeMillis();
        CountDownLatch count1 = new CountDownLatch(3);
        for (int i = 0; i < 3; i++) {
            int finalI = i;
            new Thread(() -> {
                demo.write(finalI, finalI);
                count1.countDown();
            }, Integer.toString(i)).start();
        }
        count1.await();
        long end = System.currentTimeMillis();
        System.out.println("第一波写入消耗时间 : " + (end - start));

        start = System.currentTimeMillis();
        CountDownLatch count2 = new CountDownLatch(3);
        for (int i = 0; i < 3; i++) {
            int finalI = i;
            new Thread(() -> {
                demo.read(finalI);
                count2.countDown();
            }, Integer.toString(i)).start();
        }
        count2.await();
        end = System.currentTimeMillis();
        System.out.println("第一波读取消耗时间 : " + (end - start));

        start = System.currentTimeMillis();
        CountDownLatch count3 = new CountDownLatch(3);
        for (int i = 0; i < 3; i++) {
            int finalI = i;
            new Thread(() -> {
                demo.write(finalI, finalI);
                count3.countDown();
            }, "新" + Integer.toString(i)).start();
        }
        count3.await();
        end = System.currentTimeMillis();
        System.out.println("第二波写入消耗时间 : " + (end - start));

        

    }

}

锁降级

步骤

  1. 获得写锁
  2. 获得读锁
  3. 释放写锁
  4. 释放读锁
Oracle 读写锁 源码总结
public class LockDegradationDemo {

    private Object value;
    private volatile boolean cacheValid;
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public LockDegradationDemo(Object value) {
        this.value = value;
    }

    public void use(Object value) {
        System.out.println(value);
    }

    public Object getValue() {
        return value;
    }

    public LockDegradationDemo setValue(Object value) {
        this.value = value;
        return this;
    }

    public void processCachedDate(Object data) {

        lock.readLock().lock();
        if (!cacheValid) {
            lock.readLock().unlock();
            lock.writeLock().lock();
            try {
                if(!cacheValid){
                    value = data;
                    cacheValid = true;
                }
                // 先上读锁
                lock.readLock().lock();
            } finally {
                // 再下写锁, 你不知道下了写锁后能否直接获取读锁
                // 若存在一个线程直接获取了写锁, 修改数据后, 当前线程无法感知数据修改, 就会出现数据错误的问题
                lock.writeLock().unlock();
            }
        }

        try {
            use(value);
        } finally {
            lock.readLock().unlock();
        }
    }

}
StampedLock 简介

对 读写锁 的优化, 1.8 新增

存在一个变量 stamp , long 类型, 当 stamp 返回 0 时, 标识线程获取锁失败

当释放锁或者转换锁时, 都需要传入最初获取的 stamp 的值

优化

传统 读写锁, 读锁并发, 写锁独占, 读写互斥

StampedLock 采用乐观获取锁后, 其他线程获取写锁时不会被阻塞, 但是在获取乐观读锁时会对结果进行校验.

在短读的场景下, stampedLock 会明显提高吞吐量

特点
  1. 获取锁会返回一个 stamp , stamp 为 0 时表示获取失败, 其余都为成功
  2. 释放锁需要一个 stamp, 这个 stamp 必须和获取时的 stamp 一致
  3. StampedLock 是不可重入的 , 如果一个线程已经持有了写锁, 再去获取写锁的话就会造成死锁
  4. StampedLock 有三种访问模式
    1. Reading (读悲观模式) : 与 读写锁 的读锁相似
    2. Writing (写模式) : 与 读写锁 的写锁相似
    3. Optimistic (乐观读模式) : 无锁机制, 类似于数据库中的 乐观锁, 支持读写并发, 乐观认为读取时没有修改, 如果被修改再实现升级为悲观读模式
Demo

读的过程中允许写的介入.

package lock;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.StampedLock;

public class StampedLockDemo {

    private static int number = 1;

    private static StampedLock lock = new StampedLock();

    public void write(){

        long stamp = lock.writeLock();

        System.out.println("write: start ...");

        try {

            number += 2;

        }finally {
            lock.unlockWrite(stamp);
        }

        System.out.println("write: end ...");

    }

    public void commonRead(){

        long stamp = lock.readLock();

        try {

            System.out.println("read: start ...");

            for (int i = 0; i < 4; i++) {

                try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

                System.out.println("read: reading ...");

            }

            System.out.println("read: value = " + number);

        }finally {
            lock.unlockRead(stamp);
        }

        System.out.println("read: end ...");

    }

    public void optimisticRead(){

        long stamp = lock.tryOptimisticRead();
        int result = number;

        System.out.println("optimisticRead: start ...");

        for (int i = 0; i < 4; i++) {

            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

            System.out.println("optimisticRead: reading ... " + lock.validate(stamp));

        }

        if(!lock.validate(stamp)){

            System.out.println("optimisticRead: failed ... ");

            stamp = lock.readLock();

            try {

                System.out.println("optimisticRead: read again ...");
                result = number;

            }finally {
                lock.unlockRead(stamp);
            }

        }

        System.out.println("optimisticRead: value = " + result);


        System.out.println("optimisticRead: end ...");

    }

    public static void main(String[] args) {

        StampedLockDemo demo = new StampedLockDemo();

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " Thread : coming ...");
            demo.optimisticRead();
            System.out.println(Thread.currentThread().getName() + " Thread : end ...");
        }, "optimisticRead").start();
        
        try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " Thread : coming ...");
            demo.write();
            System.out.println(Thread.currentThread().getName() + " Thread : end ...");
        }, "write").start();

        try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }

        System.out.println("number = " + number);

    }

}
缺点
  1. 不支持重入
  2. 不支持条件变量 Condition
  3. 不能使用中断, 即不要调用 interrupt() 方法
总结
转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号