5.共享模型之内存
5.1 Java 内存模型
JMM 即 Java Memory Model,它定义了主存、工作内存抽象概念,底层对应着 CPU 寄存器、缓存、硬件内存、CPU 指令优化等。
JMM 体现在以下几个方面:
原子性 - 保证指令不会受到线程上下文切换的影响
可见性 - 保证指令不会受 cpu 缓存的影响
有序性 - 保证指令不会受 cpu 指令并行优化的影响
5.2 可见性
退不出的循环
先来看一个现象,main 线程对 run 变量的修改对于 t 线程不可见,导致了 t 线程无法停止:
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
while(run){
}
});
t.start();
sleep(1);
run = false; // 线程t不会如预想的停下来
}
分析一下:
- 初始状态, t 线程刚开始从主内存读取了 run 的值到工作内存。
- 因为 t 线程要频繁从主内存中读取 run 的值,JIT 编译器会将 run 的值缓存至自己工作内存中的高速缓存中,减少对主存中 run 的访问,提高效率
- 1 秒之后,main 线程修改了 run 的值,并同步至主存,而 t 是从自己工作内存中的高速缓存中读取这个变量的值,结果永远是旧值
解决方法: volatile(易变关键字) 它可以用来修饰成员变量和静态成员变量,他可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作 volatile 变量都是直接操作主存
注意 :synchronized 语句块既可以保证代码块的原子性,也同时保证代码块内变量的可见性。但缺点是synchronized 是属于重量级操作,性能相对更低
volatile:
public class Test1 {
volatile static boolean flag=true;
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
while (flag){
}
}).start();
Thread.sleep(3000);
flag=false;
}
}
synchronized
public class Test1 {
static boolean flag=true;
static Object lock=new Object();
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
while (true){
synchronized (lock){
if (!flag){
break;
}
}
}
}).start();
Thread.sleep(3000);
synchronized (lock){
flag=false;
}
}
}
- 模式之两阶段终止
@Slf4j
public class Test2 {
public static void main(String[] args) throws InterruptedException {
TwoPhaseTermination tt = new TwoPhaseTermination();
tt.start();
Thread.sleep(3000);
log.debug("停止监控");
tt.stop();
}
}
@Slf4j(topic = "c.asjdhwu")
class TwoPhaseTermination{
private Thread monitorThread;
private volatile static boolean flag=true;
public void start(){
monitorThread=new Thread(()->{
while (true){
if (!flag){
log.debug("被打断了,料理后事");
break;
}
try {
Thread.sleep(1000);
log.debug("打断监控中");
} catch (InterruptedException e) {
}
}
});
monitorThread.start();
}
public void stop(){
flag=false;
monitorThread.interrupt();
}
}
- 设计模式--犹豫模式
- 用在一个线程发现另一个线程也在做同样相同的事那么本线程就无需再做了,直接结束返回.
@Slf4j
public class Test3 {
public static void main(String[] args) throws InterruptedException {
PhaseThread pt=new PhaseThread();
pt.start();
pt.start();
}
}
@Slf4j(topic = "c.PhaseThread")
class PhaseThread{
private Thread monitorThread;
private volatile static boolean flag=true;
private boolean starting=false;
public void start(){
synchronized (this){
if (starting){
return;
}
starting=true;
}
monitorThread=new Thread(()->});
monitorThread.start();
}
public void stop(){
flag=false;
monitorThread.interrupt();
}
}
volatile 原理
volatile 的底层实现原理是内存屏障,Memory Barrier(Memory Fence)
- 对 volatile 变量的写指令后会加入写屏障
- 对 volatile 变量的读指令前会加入读屏障
1. 如何保证可见性
写屏障(sfence)保证在该屏障之前的,对共享变量的改动,都同步到主存当中
public void actor2(I_Result r) {
num = 2;
ready = true; // ready 是 volatile 赋值带写屏障
// 写屏障
}
而读屏障(lfence)保证在该屏障之后,对共享变量的读取,加载的是主存中最新数据
public void actor1(I_Result r) {
// 读屏障
// ready 是 volatile 读取值带读屏障
if(ready) {
r.r1 = num + num;
} else {
r.r1 = 1;
}
}
2. 如何保证有序性
写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后
public void actor2(I_Result r) {
num = 2;
ready = true; // ready 是 volatile 赋值带写屏障
// 写屏障
}
读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前
public void actor1(I_Result r) {
// 读屏障
// ready 是 volatile 读取值带读屏障
if(ready) {
r.r1 = num + num;
} else {
r.r1 = 1;
}
}
注意: 还是那句话,不能解决指令交错: 写屏障仅仅是保证之后的读能够读到最新的结果,但不能保证读跑到它前面去 而有序性的保证也只是保证了本线程内相关代码不被重排序
3. double-checked locking 问题
public final class Singleton {
private Singleton() { }
private static Singleton INSTANCE = null;
public static Singleton getInstance() {
if(INSTANCE == null) { // t2
// 首次访问会同步,而之后的使用没有 synchronized
synchronized(Singleton.class) {
if (INSTANCE == null) { // t1
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
以上的实现特点是:
- 懒惰实例化
- 首次使用 getInstance() 才使用 synchronized 加锁,后续使用时无需加锁
- 有隐含的,但很关键的一点:第一个 if 使用了 INSTANCE 变量,是在同步块之外
解决方法:
对 INSTANCE 使用 volatile 修饰即可,可以禁用指令重排
4. double-checked locking 解决
public final class Singleton {
private Singleton() { }
private static volatile Singleton INSTANCE = null;
public static Singleton getInstance() {
// 实例没创建,才会进入内部的 synchronized代码块
if (INSTANCE == null) {
synchronized (Singleton.class) { // t2
// 也许有其它线程已经创建实例,所以再判断一次
if (INSTANCE == null) { // t1
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
线程安全单例习题
单例模式有很多实现方法,饿汉、懒汉、静态内部类、枚举类,试分析每种实现下获取单例对象(即调用getInstance)时的线程安全,并思考注释中的问题
饿汉式:类加载就会导致该单实例对象被创建
懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建
实现1
// 问题1:为什么加 final 防止父类方法被覆盖
// 问题2:如果实现了序列化接口, 还要做什么来防止反序列化破坏单例
public final class Singleton implements Serializable {
// 问题3:为什么设置为私有? 是否能防止反射创建新的实例?
private Singleton() {}
// 问题4:这样初始化是否能保证单例对象创建时的线程安全?
//能,静态成员变量初始化操作是在类加载阶段完成的
private static final Singleton INSTANCE = new Singleton();
// 问题5:为什么提供静态方法而不是直接将 INSTANCE 设置为 public, 说出你知道的理由 封装性,泛型支持等
public static Singleton getInstance() {
return INSTANCE;
}
//2.防止反序列化破坏单例
public Object readResolve() {
return INSTANCE;
}
}
实现2
// 问题1:枚举单例是如何限制实例个数的 final static
// 问题2:枚举单例在创建时是否有并发问题 没有
// 问题3:枚举单例能否被反射破坏单例 不能
// 问题4:枚举单例能否被反序列化破坏单例 默认实现序列化接口,不会被破坏
// 问题5:枚举单例属于懒汉式还是饿汉式 饿汉式
// 问题6:枚举单例如果希望加入一些单例创建时的初始化逻辑该如何做
enum Singleton {
INSTANCE;
}
实现3
懒汉式
public final class Singleton {
private Singleton() { }
private static Singleton INSTANCE = null;
// 分析这里的线程安全, 并说明有什么缺点 每次都加锁 双重检查
public static synchronized Singleton getInstance() {
if( INSTANCE != null ){
return INSTANCE;
}
INSTANCE = new Singleton();
return INSTANCE;
}
}
实现4
public final class Singleton {
private Singleton() { }
// 问题1:解释为什么要加 volatile ? 防止指令重排序
private static volatile Singleton INSTANCE = null;
// 问题2:对比实现3, 说出这样做的意义
public static Singleton getInstance() {
if (INSTANCE != null) {
return INSTANCE;
}
synchronized (Singleton.class) {
// 问题3:为什么还要在这里加为空判断, 之前不是判断过了吗
if (INSTANCE != null) { // t2
return INSTANCE;
}
INSTANCE = new Singleton();
return INSTANCE; //t1
}
}
}
6.共享模型之无锁(乐观锁)
6.1 CAS 与 volatile
public class Test6 {
public static void main(String[] args) {
AccountCas accountCas = new AccountCas(10000);
Account.demo(accountCas);
}
}
class AccountCas implements Account{
private AtomicInteger balance;
public AccountCas(Integer money) {
this.balance=new AtomicInteger(money);
}
@Override
public Integer getBalance() {
return balance.get();
}
@Override
public void withdraw(Integer amount) {
while (true){
//获取余额最新值
int prev = balance.get();
//要修改余额
int next = prev - amount;
//prev与最新balance.get()的值最对比一样就修改为next
//compareAndSet 正是做这个检查,在 set 前,先比较 prev 与当前值,不一致了,next作废,返回 false 表示失败,比如,别的线程已经做了减法,当前值已经被减成了 990,那么本线程的这次 990 就作废了,进入while 下次循环重试,一致,以next 设置为新值,返回 true 表示成功
if (balance.compareAndSet(prev,next)){
break;
}
}
}
}
interface Account{
//获取金额
Integer getBalance();
//取款
void withdraw(Integer amount);
static void demo(Account account){
ArrayList<Thread> ts = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
ts.add(new Thread(()->{
account.withdraw(10);
}));
}
long start = System.nanoTime();
ts.forEach(Thread::start);
ts.forEach(t->{
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long end = System.nanoTime();
System.out.println(account.getBalance()+"---cost-->"+(end-start)/1000000+"ms");
}
}
volatile
获取共享变量时,为了保证该变量的可见性,需要使用 volatile 修饰。
它可以用来修饰成员变量和静态成员变量,他可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取
它的值,线程操作 volatile 变量都是直接操作主存。即一个线程对 volatile 变量的修改,对另一个线程可见。
注意:volatile仅仅保证了共享变量的可见性,让其它线程能够看到最新值,但不能解决指令交错问题(不能保证原子性)
CAS 必须借助 volatile 才能读取到共享变量的最新值来实现【比较并交换】的效果
为什么无锁效率高
无锁情况下,即使重试失败,线程始终在高速运行,没有停歇,而 synchronized 会让线程在没有获得锁的时 候,发生上下文切换,进入阻塞。
打个比喻线程就好像高速跑道上的赛车,高速运行时,速度超快,一旦发生上下文切换,就好比赛车要减速、熄火,等被唤醒又得重新打火、启动、加速... 恢复到高速运行,代价比较大但无锁情况下,因为线程要保持运行,需要额外 CPU 的支持,CPU 在这里就好比高速跑道,没有额外的跑道,线程想高速运行也无从谈起,虽然不会进入阻塞,但由于没有分到时间片,仍然会进入可运行状态,还是会导致上下文切换
CAS的特点
结合 CAS 和 volatile 可以实现无锁并发,适用于线程数少、多核 CPU 的场景下。
- CAS 是基于乐观锁的思想:最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系,我吃亏点再 -重试呗。
- synchronized 是基于悲观锁的思想:最悲观的估计,得防着其它线程来修改共享变量,我上了锁你们都别想改,我改完了解开锁,你们才有机会。
- CAS 体现的是无锁并发、无阻塞并发,请仔细体会这两句话的意思因为没有使用
synchronized,所以线程不会陷入阻塞,这是效率提升的因素之一
但如果竞争激烈,可以想到重试必然频繁发生,反而效率会受影响
6.2原子整数
J.U.C 并发包提供了:
- AtomicBoolean
- AtomicInteger
- AtomicLong
以 AtomicInteger 为例
AtomicInteger i = new AtomicInteger(0);
// 获取并自增(i = 0, 结果 i = 1, 返回 0),类似于 i++
System.out.println(i.getAndIncrement());
// 自增并获取(i = 1, 结果 i = 2, 返回 2),类似于 ++i
System.out.println(i.incrementAndGet());
// 自减并获取(i = 2, 结果 i = 1, 返回 1),类似于 --i
System.out.println(i.decrementAndGet());
// 获取并自减(i = 1, 结果 i = 0, 返回 1),类似于 i--
System.out.println(i.getAndDecrement());
// 获取并加值(i = 0, 结果 i = 5, 返回 0)
System.out.println(i.getAndAdd(5));
// 加值并获取(i = 5, 结果 i = 0, 返回 0)
System.out.println(i.addAndGet(-5));
// 获取并更新(i = 0, p 为 i 的当前值, 结果 i = -2, 返回 0)
// 其中函数中的操作能保证原子,但函数需要无副作用
System.out.println(i.getAndUpdate(p -> p - 2));
// 更新并获取(i = -2, p 为 i 的当前值, 结果 i = 0, 返回 0)
// 其中函数中的操作能保证原子,但函数需要无副作用
System.out.println(i.updateAndGet(p -> p + 2));
// 获取并计算(i = 0, p 为 i 的当前值, x 为参数1, 结果 i = 10, 返回 0)
// 其中函数中的操作能保证原子,但函数需要无副作用
// getAndUpdate 如果在 lambda 中引用了外部的局部变量,要保证该局部变量是 final 的
// getAndAccumulate 可以通过 参数1 来引用外部的局部变量,但因为其不在 lambda 中因此不必是 final
System.out.println(i.getAndAccumulate(10, (p, x) -> p + x));
// 计算并获取(i = 10, p 为 i 的当前值, x 为参数1, 结果 i = 0, 返回 0)
// 其中函数中的操作能保证原子,但函数需要无副作用
System.out.println(i.accumulateAndGet(-10, (p, x) -> p + x));
6.3原子引用
为什么需要原子引用类型?
- AtomicReference
- AtomicMarkableReference +ture,false 可知是否被改动
- AtomicStampedReference +版本号 可知被改动多少次
有如下方法:
public class Test8 {
public static void main(String[] args) {
DecimalAccountCas decimalAccountCas = new DecimalAccountCas(new BigDecimal(10000));
DecimalAccount.demo(decimalAccountCas);
}
}
class DecimalAccountCas implements DecimalAccount{
private AtomicReference<BigDecimal> balance;
public DecimalAccountCas(BigDecimal balance) {
this.balance = new AtomicReference<>(balance);
}
@Override
public BigDecimal getBalance() {
return balance.get();
}
@Override
public void withdraw(BigDecimal amount) {
while (true){
BigDecimal prev = balance.get();
BigDecimal next = prev.subtract(amount);
if (balance.compareAndSet(prev,next)){
break;
}
}
}
}
interface DecimalAccount{
//获取余额
BigDecimal getBalance();
//取款
void withdraw(BigDecimal amount);
static void demo(DecimalAccount account){
ArrayList<Thread> ts = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
ts.add(new Thread(()->{
account.withdraw(BigDecimal.TEN);
}));
}
ts.forEach(Thread::start);
ts.forEach(t->{
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(account.getBalance());
}
}
ABA 问题及解决
AtomicStampedReference +版本号 可知被改动多少次
A-B->true B-A->true A-C->true
static AtomicReference<String> ref=new AtomicReference<>("A");
public static void main(String[] args) throws InterruptedException {
String s = ref.get();
other();
Thread.sleep(3000);
log.debug("change A-C:{}",ref.compareAndSet(s,"c"));
}
private static void other() throws InterruptedException {
new Thread(() -> {
log.debug("change A-B:{}", ref.compareAndSet(ref.get(), "B"));
}).start();
Thread.sleep(100);
new Thread(() -> {
log.debug("change B-A:{}", ref.compareAndSet(ref.get(), "A"));
}).start();
}
解决 AtomicStampedReference 添加一个版本号
static AtomicStampedReference<String> ref=new AtomicStampedReference<>("A",0);
public static void main(String[] args) throws InterruptedException {
String reference = ref.getReference();
int stamp = ref.getStamp();
other();
Thread.sleep(3000);
log.debug("A-C:{}",ref.compareAndSet(reference,"C",stamp,stamp+1));
log.debug("ref:{},stamp:{}",ref.getReference(),ref.getStamp());
}
public static void other() throws InterruptedException {
new Thread(()->{
log.debug("A-B:{}",ref.compareAndSet(ref.getReference(),"B",ref.getStamp(),ref.getStamp()+1));
}).start();
Thread.sleep(100);
new Thread(()->{
log.debug("B-A:{}",ref.compareAndSet(ref.getReference(),"A", ref.getStamp(), ref.getStamp()+1));
}).start();
}
AtomicMarkableReference 是否更改过
public static void main(String[] args) throws InterruptedException {
GarbageBag gb = new GarbageBag("装满了垃圾");
AtomicMarkableReference<GarbageBag> ref = new AtomicMarkableReference<GarbageBag>(gb,true);
log.debug("start");
GarbageBag prev = ref.getReference();
log.debug(prev.toString());
new Thread(()->{
log.debug("保姆换垃圾代:{}",ref.compareAndSet(prev,new GarbageBag("空垃圾"),true,false));
}).start();
Thread.sleep(1000);
log.debug("想换一只新垃圾袋?");
boolean success = ref.compareAndSet(prev, new GarbageBag("空垃圾"), true, false);
log.debug("换了么,{}",success);
log.debug(ref.getReference().toString());
}
6.4原子数组
- AtomicIntegerArray
- AtomicLongArray
- AtomicReferenceArray 可指定泛型
public static void main(String[] args) {
demo(
()->new AtomicIntegerArray(10),
(arr)->arr.length(),
(arr,index)->arr.getAndIncrement(index),
arr-> System.out.println(arr)
);
}
/*
参数1,提供数组、可以是线程不安全数组或线程安全数组
参数2,获取数组长度的方法
参数3,自增方法,回传 array, index
参数4,打印数组的方法
supplier 提供者 无中生有 ()->结果
function 函数 一个参数一个结果 (参数)->结果,BiFunction (参数1,参数2)->结果
consumer 消费者 一个参数没结果 (参数)->void,BiConsumer (参数1,参数2)->
*/
private static <T> void demo(
Supplier<T> arraySupplier,
Function<T, Integer> lengthFun,
BiConsumer<T, Integer> putConsumer,
Consumer<T> printConsumer ) {
List<Thread> ts = new ArrayList<>();
T array = arraySupplier.get();
int length = lengthFun.apply(array);
for (int i = 0; i < length; i++) {
// 每个线程对数组作 10000 次操作
ts.add(new Thread(() -> {
for (int j = 0; j < 10000; j++) {
putConsumer.accept(array, j%length);
}
}));
}
ts.forEach(t -> t.start()); // 启动所有线程
ts.forEach(t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}); // 等所有线程结束
printConsumer.accept(array);
}
6.5字段更新器
- AtomicReferenceFieldUpdater // 域 字段
- AtomicIntegerFieldUpdater
- AtomicLongFieldUpdater
利用字段更新器,可以针对对象的某个域(Field)进行原子操作,只能配合 volatile 修饰的字段使用,否则会出现异常
public class Test13 {
public static void main(String[] args) {
Student student = new Student();
AtomicReferenceFieldUpdater<Student, String> updater =
AtomicReferenceFieldUpdater.newUpdater(Student.class, String.class, "name");
//预期值 //修改成
updater.compareAndSet(student, "王五", "李四");
System.out.println(student.name);
}
}
class Student{
volatile String name="张三";
@Override
public String toString() {
return "Student{" +
"name='" + name + ''' +
'}';
}
}
6.6原子累加器
LongAdder累加比AtomicLong系能更搞
public static void main(String[] args) {
demo(
()->new AtomicLong(0),
(adder)->adder.getAndIncrement()
);
demo(
()->new LongAdder(),
(adder)->adder.increment()
);
}
private static <T> void demo(Supplier<T> adderSupplier, Consumer<T> action){
T arr = adderSupplier.get();
List<Thread> ts=new ArrayList<>();
for (int i = 0; i < 4; i++) {
ts.add(new Thread(()->{
for (int j = 0; j < 50000; j++) {
action.accept(arr);
}
}));
}
long start = System.nanoTime();
ts.forEach(Thread::start);
ts.forEach(t->{
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long end = System.nanoTime();
System.out.println(arr+"->cast:"+(end-start)/1000_000);
}
性能提升的原因很简单,就是在有竞争时,设置多个累加单元,Therad-0 累加 Cell[0],而 Thread-1 累加Cell[1]... 最后将结果汇总。这样它们在累加时操作的不同的 Cell 变量,因此减少了 CAS 重试失败,从而提高性能。
6.6Unsafe
Unsafe 对象提供了非常底层的,操作内存、线程的方法,Unsafe 对象不能直接调用,只能通过反射获得
Unsafe CAS 操作
public class Test16 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
//允许访问私有成员变量
theUnsafe.setAccessible(true);
Unsafe unsafe=(Unsafe) theUnsafe.get(null);
System.out.println(unsafe);
//获取域的偏移量
long idOffset = unsafe.objectFieldOffset(Teacher.class.getDeclaredField("id"));
long nameOffset = unsafe.objectFieldOffset(Teacher.class.getDeclaredField("name"));
long ageOffset = unsafe.objectFieldOffset(Teacher.class.getDeclaredField("age"));
long addressOffset = unsafe.objectFieldOffset(Teacher.class.getDeclaredField("address"));
System.out.println("idOffset:"+idOffset+",nameOffset:"+nameOffset);
System.out.println("idOffset:"+idOffset+",nameOffset:"+nameOffset+",ageOffset:"+ageOffset+",addressOffset:"+addressOffset);
Teacher t = new Teacher();
//执行cas操作
unsafe.compareAndSwapInt(t,idOffset,0,1);
unsafe.compareAndSwapObject(t,nameOffset,null,"张三");
unsafe.compareAndSwapInt(t,ageOffset,0,18);
unsafe.compareAndSwapObject(t,addressOffset,null,"湖北");
System.out.println(t);
}
}
@Data
class Teacher{
volatile int id;
volatile int age;
volatile String name;
volatile String address;
}
// sun.misc.Unsafe@5e481248
// idOffset:12,nameOffset:20
// idOffset:12,nameOffset:20,ageOffset:16,addressOffset:24
// Teacher(id=1, age=18, name=张三, address=湖北)
6.7自定义原子整数
获得初始化Unsafe
public class UnsafeAccessor {
private static final Unsafe unsafe;
static {
try {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
unsafe=(Unsafe) theUnsafe.get(null);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new Error(e);
}
}
public static Unsafe getUnsafe(){
return unsafe;
}
}
public class Test17 {
public static void main(String[] args) {
MyAtomicInteger myAtomicInteger = new MyAtomicInteger(10000);
MyAccount.demo(myAtomicInteger);
}
}
//自定义原子整数
class MyAtomicInteger implements MyAccount{
private static final long valueOffset;
private volatile int value;
private static final Unsafe UNSAFE;
public MyAtomicInteger(int value) {
this.value = value;
}
static {
UNSAFE=UnsafeAccessor.getUnsafe();
try {
valueOffset=UNSAFE.objectFieldOffset(MyAtomicInteger.class.getDeclaredField("value"));
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
}
public int getValue() {
return value;
}
public void decrement(int amount) {
while (true){
int prev=this.value;
int next= prev-amount;
if( UNSAFE.compareAndSwapInt(this,valueOffset,prev,next) ){
break;
}
}
}
@Override
public Integer getBalance() {
return getValue();
}
@Override
public void withDraw(Integer amount) {
decrement(amount);
}
}
interface MyAccount{
Integer getBalance();
void withDraw(Integer amount);
static void demo(MyAccount account){
List<Thread> ts=new ArrayList<>();
for (int i = 0; i < 10000; i++) {
ts.add(new Thread(()->{
account.withDraw(1);
}));
}
ts.forEach(Thread::start);
ts.forEach(t->{
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(":"+account.getBalance());
}
}
7.共享模型之不可变
7.1享元模式
(当需要重用数量有限的同一类对象时)
包装类: 在JDK中 Boolean,Byte,Short,Integer,Long,Character 等包装类提供了 valueOf 方法,例如 Long 的valueOf 会缓存 -128~127 之间的 Long 对象,在这个范围之间会重用对象,大于这个范围,才会新建 Long 对象
(String串池,BigDecimal BigInteger)