0%

JUC

并发编程的目的:充分利用CPU资源

JUC:java.util.concurrent

进程与线程

Java代码无权直接创建线程,需要调用原生的C++方法start0()才能创建线程

线程状态

  • 创建
  • 就绪
  • 运行
  • 阻塞
  • 死亡

实现多线程的两个接口:Callable和Runnable,一般来说Callable性能更高

Java中Thread类中的枚举类型:

1
2
3
4
5
6
7
8
public enum State {
NEW, //初始
RUNNABLE, //运行
BLOCKED, //阻塞
WAITING, //等待
TIMED_WAITING, //超时等待
TERMINATED; //终止
}

wait/sleep的区别

  • wait是Object的方法,sleep是Thread类的方法
  • wait会释放锁,sleep不会释放锁
  • wait必须在同步代码块中使用,而sleep可以在任何地方使用

synchronized

synchronized的本质:锁+队列

给方法添加synchronized锁的对象为方法的调用者,即方法所在的类

在使用synchronized的时候,有些情况下会出现虚假唤醒,为了防止这个问题,应该使用while来代替if判断,如:

1
2
3
while(number != 0){
this.wait();
}

使用方法

在定义接口方法时不能使用synchronized关键字

  • 修饰方法

    1
    2
    3
    class Phone{
    public synchronized void sendSms(){...};
    }

    1
    2
    3
    4
    5
    class Phone{
    public void sendSms(){
    synchronized (this) {...}
    };
    }
  • 修饰static方法

    1
    2
    3
    class Phone{
    public static synchronized void sendSms(){...};
    }
  • 修饰类

    1
    2
    3
    4
    5
    class Phone{
    public void sendSms(){
    synchronized (Phone.class) {...}
    };
    }

static锁与方法锁的区别

1
2
3
4
class Phone{
public synchronized void sendSms(){...};
public synchronized void sendSms(){...};
}

这里的synchronized锁的是phone的实例对象,同一个对象调用不同方法会被锁

1
2
3
4
class Phone{
public static synchronized void sendSms(){...};
public static synchronized void sendSms(){...};
}

这里的synchronized锁的是phone的静态模板phone.class,调用同时带有static的和synchronized的方法会被锁

Lock

  • 公平锁:必须按先来后到顺序执行

    1
    ReentrantLock reentrantLock = new ReentrantLock(true);
  • 非公平锁:可以插队

    lock锁默认非公平锁

lock锁

常用实现类ReentrantLock

使用流程:

  1. 创建锁ReentrantLock lock = new ReentrantLock();
  2. 加锁lock.lock();lock.tryLock();
  3. 编写业务代码
  4. 解锁lock.unlock();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class demo {
public synchronized static void main(String[] args) {
TicketClass ticket = new TicketClass();
//使用函数式编程的方式多线程调用方法
new Thread(()-> {
for (int i = 0; i < 20; i++) {
ticket.getTicket();
}
}, "消费者A").start();
new Thread(()-> {
for (int i = 0; i < 20; i++) {
ticket.getTicket();
}
}, "消费者B").start();
new Thread(()-> {
for (int i = 0; i < 20; i++) {
ticket.getTicket();
}
}, "消费者C").start();
}
}

class TicketClass {
int i = 50;
//声明并使用Lock锁
ReentrantLock lock = new ReentrantLock();

public void getTicket(){
lock.lock();
if (i > 0)
System.out.println(Thread.currentThread().getName() + "买到了第"+ (50- --i) +"张票,剩余" + i + "张票");
lock.unlock();
}
}

synchronized与Lock的区别

  • synchronized是内置的java关键字,Lock是一个java类
  • synchronized无法判断锁的状态,Lock可以判断是否获得到了锁
  • synchronized会自动释放,Lock必须手动释放
  • 如果使用synchronized,正在运行的线程阻塞了,正在等待的线程会持续等待,而Lock不一定(使用tryLock方法)
  • synchronized可重入,不能中断,非公平;lock可重入,可以判断锁,可以自己设置是否公平锁
  • synchronized适合锁少量同步代码,lock适合锁大量同步代码

Condition

lock.newCondition()创建信号量

1
2
lock.await(); //信号量--
lock.signal() //信号量++

可以通过信号量来指定线程执行顺序

读写锁

  • 独占锁:写锁,只允许一个线程操作
  • 共享锁:读锁,只允许读不允许写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class Demo06 {
public static void main(String[] args) {
ReadWriteTest readWriteTest = new ReadWriteTest();
for (int i = 0; i < 6; i++) {
final int temp = i;
new Thread(() -> {
readWriteTest.put(temp+"",temp+"");
}, String.valueOf(i)).start();
}
for (int i = 0; i < 6; i++) {
final int temp = i;
new Thread(() -> {
readWriteTest.get(temp+"");
}, String.valueOf(i)).start();
}
}
}

class ReadWriteTest {
private volatile Map<String, String> map = new HashMap<>();
//读写锁
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
//写锁,同时只允许一个线程操作
public void put(String key, String value){
lock.writeLock().lock();
System.out.println(Thread.currentThread().getName() + "正在准备写入");
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "正在准备写入");
lock.writeLock().unlock();
}
//读锁,只允许读不允许写
public void get(String key){
lock.readLock().lock();
System.out.println(Thread.currentThread().getName() + "正在准备读出");
String s = map.get(key);
System.out.println(Thread.currentThread().getName() + "读出"+ s);
lock.readLock().unlock();
}
}

可重入锁

某个线程已经获取锁了,仍然可以获取锁而不死锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Demo15 {
public static void main(String[] args) {
new Thread(() -> PhoneService.sms()).start();
new Thread(() -> PhoneService.call()).start();
}
}

class PhoneService {
public static synchronized void sms(){
try {
call();//再次获取锁
System.out.println(Thread.currentThread().getName() + " is sending sms...");
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

public static synchronized void call(){
System.out.println(Thread.currentThread().getName() + " is calling...");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

这里的测试中,在0号线程保持sms方法锁的时间内,1号线程不能获取到call方法的锁

lock锁同理,可以对同一个lock多次加锁,但必须释放同样多次数的锁才能完全释放锁

自旋锁

以Unsafe类中的getAndSetInt为例,不断进行CAS循环直到成功的代码块即为自旋锁

1
2
3
4
5
6
7
8
public final int getAndSetInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var4));

return var5;
}

死锁排查

  1. 使用jps -l查看进程号

  2. 使用jstack 进程号查看详细信息

    image-20211005194044091

集合类不安全

在使用ArrayList,HashSet或HashMap进行多线程操作时是不安全的,报错ConcurrentModificationException,如何解决?

  • 使用Vector代替ArrayList,本质synchronized修饰

  • 使用Collections集合类中的方法添加synchronized关键词

    1
    List<Object> list = Collections.synchronizedList(new ArrayList<>());
  • 底层使用的是Lock锁的实现类

    • CopyOnWriteArrayList
    • CopyOnWriteArraySet
  • 底层使用synchronized锁node结点的实现类ConcurrentHashMap

Callable

相较于Runnable:

  • 可以有返回值
  • 可以抛出异常
  • 多次调用有缓存,比如下面这个例子call方法只会执行一次

使用方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Demo02 {
public static void main(String[] args) {
//实例化Callable实现类
TestCallable testThread = new TestCallable();
//交由FutureTask处理
FutureTask<String> task = new FutureTask<>(testThread);
//FutureTask是Runnable的一个实现类,可以直接start()
new Thread(task, "A").start();
new Thread(task, "B").start();
//可以捕捉异常
try {
//通过get()方法获取call()方法的返回值
System.out.println(task.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}

class TestCallable implements Callable<String>{
@Override
public String call() throws Exception {
return Thread.currentThread().getName() + "test";
}
}

常用辅助类(AQS)

AQS即AbstractQueuedSynchronizer,是一个常用的同步锁模板框架,常见的ReentrantLock、ReadWriteLock、CountDownLatch、CyclicBarrier、Semaphore都是AQS的实现类

  • 维护一个state变量,只有state=0时其他线程才能成功获取到这个锁,并根据具体情况将state更改为不同的值
  • 条件队列+同步队列:当前线程获取同步失败,会将当前线程作为Node结点添加到队列尾部并阻塞、当state可用时,从同步队列的头部获取线程并进行同步

资源共享的方式:

  • 独占型:ReentrantLock、ReadWriteLock
  • 共享型:CountDownLatch、CyclicBarrier、Semaphore

CountDownLatch

减法计数器,一般用于控制线程等待

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Demo03 {
public static void main(String[] args) throws InterruptedException {
//初始化值为6
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 0; i < 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " => gou out");
//数值--
countDownLatch.countDown();
}, String.valueOf(i)).start();
}
//等待所有线程执行完毕再向下放行
try {
countDownLatch.await();
System.out.println("Close door");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

CyclicBarrier

与CountDownLatch类似的功能,但更为强大,一般用于同步一组线程,设置一个屏障点,当一组线程中的最后一个线程到达屏障点时所有线程才能够继续执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Demo04 {
public static void main(String[] args) {
//线程计数器达到7才会执行
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println("7777777777777!");
});
for (int i = 0; i < 7; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName());
try {
//计数器++
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();
}
}
}

Semaphore

java中的信号量,一般在限流中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Demo05 {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 6; i++) {
new Thread(() -> {
try {
//p操作,获得资源
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"停入车位");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//v操作,释放资源
semaphore.release();
}
}, String.valueOf(i)).start();
}
}
}

阻塞队列

BlockingQueue接口:和List和Set同级,实现Collection接口

BlockingQueue为FIFO类型的队列,先进先出

常用类结构

image-20211003235128371

ArrayBlockingQueue

数组阻塞队列,常用api:

image-20211004104730429

使用实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static void main(String[] args) {
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.add("1");
System.out.println(blockingQueue.element());
System.out.println(blockingQueue.remove());

blockingQueue.offer("2");
System.out.println(blockingQueue.peek());
System.out.println(blockingQueue.poll());

try {
blockingQueue.put("3");
System.out.println(blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}

try {
//这里指定时间单位和时间长度,超过指定时间返回false,poll()同理
blockingQueue.offer("4",2, TimeUnit.SECONDS);
System.out.println(blockingQueue.poll(2, TimeUnit.SECONDS));
} catch (InterruptedException e) {
e.printStackTrace();
}
}

SynchronousQueue

同步队列:没有容量,进入一个元素必须等待取出之后才能在此存放,类似于容量为1的阻塞队列

线程池

三大方法

创建线程池的三个api

  • Executors.newSingleThreadExecutor() 单线程实例
  • Executors.newFixedThreadPool(n) 最大线程数量为n
  • Executors.newCachedThreadPool() 伸缩缓存线程池
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Demo08 {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newSingleThreadExecutor();
// ExecutorService threadPool = Executors.newFixedThreadPool(5);
// ExecutorService threadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int temp = i;
//开启线程
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + " => " + temp);
});
}
//关闭线程池
threadPool.shutdown();
}
}

七大参数

上面的三个api方法都是同过创建ThreadPoolExecutor对象来实现的,包括调用一些他们的重载方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public ThreadPoolExecutor(int corePoolSize, //核心线程池大小
int maximumPoolSize, //最大线程池大小
long keepAliveTime, //超时时间
TimeUnit unit, //超时单位
BlockingQueue<Runnable> workQueue, //阻塞队列
ThreadFactory threadFactory, //线程工厂
RejectedExecutionHandler handler) { //拒绝策略
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}

线程池的线程运行过程

  • 可运行的线程数量最初只有corePoolSize个,先进入的线程开始运行
  • 当corePoolSize的线程都正在运行,不能添加新的线程时,这时进入的线程进入到阻塞队列workQueue中进行等待
  • 当workQueue的空间也被占满,仍有新的线程进入时,线程池会开放可同时运行的线程数量上限,最大为maximumPoolSize
  • 当同时运行maximumPoolSize个线程,而workQueue的所有空间也都被占满时,线程池会遵循拒绝策略RejectedExecutionHandler,拒绝新线程的进入

拒绝策略的实现类

image-20211004151143390

  • AbortPolicy:直接拒绝并抛出异常
  • CallerRunsPolicy:让调用线程的线程来运行(比如main线程新建的线程超过了上限,就会使用main线程来运行新线程的内容)
  • DiscardOldestPolicy:尝试让新线程来替代线程池中最老的线程,不会抛出异常
  • DiscardPolicy:直接拒绝但不抛出异常

设置maximumPoolSize的策略

  • CPU密集型:maximumPoolSize=CPU核数,效率最高

    1
    Runtime.getRuntime().availableProcessors() //获取CPU核数
  • IO密集型:判断程序中有几个十分消耗IO资源的线程,只要maximumPoolSize大于这个数即可

    一般可以设置为2倍

自定义线程池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Demo09 {
public static void main(String[] args) {
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(2, //核心线程池大小
5, //最大线程池大小
2, //超时时间
TimeUnit.SECONDS, //超时单位
blockingQueue, //阻塞队列
Executors.defaultThreadFactory(), //线程工厂
new ThreadPoolExecutor.AbortPolicy());//拒绝策略

for (int i = 0; i < 9; i++) {
poolExecutor.execute(()->{
System.out.println(Thread.currentThread().getName() + " 正在运行");
});
}

poolExecutor.shutdown();
}
}

函数式接口

只有一个方法的接口,如Runnable接口

1
2
3
4
@FunctionalInterface
public interface Runnable {
public abstract void run();
}

作用:简化编程模型,新版框架底层大量应用

Function

函数型接口:指定一个输入和一个输出

1
2
3
4
5
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
...
}

Predicate

断定性接口:指定一个输入,返回布尔值

1
2
3
4
5
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
...
}

Consumer

消费型接口:只有输入,没有返回值

1
2
3
4
5
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
...
}

Supplier

供给型接口:没有输入,只有输出

1
2
3
4
@FunctionalInterface
public interface Supplier<T> {
T get();
}

Stream流式计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Demo10 {
public static void main(String[] args) {
User user1 = new User(1, "a", 21);
User user2 = new User(2, "b", 22);
User user3 = new User(3, "c", 23);
User user4 = new User(4, "d", 24);
User user5 = new User(5, "e", 25);

List<User> list = Arrays.asList(user1, user2, user3, user4, user5);

list.stream()
.filter(u->u.getId()%2==0)
.filter(u->u.getAge()>23)
.map(u->u.getName().toUpperCase())
.sorted((u1,u2)->u2.compareTo(u1))
.limit(1)
.forEach(System.out::println);
}
}

ForkJoin

大数据量的情境下并行执行任务,提高效率

特点:工作窃取

假设两个线程:A处理速度较慢,B处理速度较快,当B处理完他自己队列中的所有任务之后,会尝试“窃取”A未完成队列中的任务进行工作

image-20211004183447161

用法示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public class Demo11 {
// 这里可以调整临界值的大小,方便调优
private static final Integer MAX = 1000;

static class MyForkJoinTask extends RecursiveTask<Integer> {
// 子任务开始计算的值
private Integer startValue;

// 子任务结束计算的值
private Integer endValue;

public MyForkJoinTask(Integer startValue , Integer endValue) {
this.startValue = startValue;
this.endValue = endValue;
}

// 递归调用计算方法,知道两侧的值之差小于MAX
@Override
protected Integer compute() {
// 如果条件成立,说明这个任务所需要计算的数值分为足够小了
// 可以正式进行累加计算了
if(endValue - startValue < MAX) {
System.out.println("开始计算的部分:startValue = " + startValue + ";endValue = " + endValue);
Integer totalValue = 0;
for(int index = this.startValue ; index <= this.endValue ; index++) {
totalValue += index;
}
return totalValue;
}
// 否则再进行任务拆分,拆分成两个任务
else {
MyForkJoinTask subTask1 = new MyForkJoinTask(startValue, (startValue + endValue) / 2);
subTask1.fork();
MyForkJoinTask subTask2 = new MyForkJoinTask((startValue + endValue) / 2 + 1 , endValue);
subTask2.fork();
return subTask1.join() + subTask2.join();
}
}
}

public static void main(String[] args) {
// 这是Fork/Join框架的线程池
ForkJoinPool pool = new ForkJoinPool();
long startTime = System.currentTimeMillis();
ForkJoinTask<Integer> taskFuture = pool.submit(new MyForkJoinTask(1,100_0000));
try {
Integer result = taskFuture.get();
long endTime = System.currentTimeMillis();
System.out.println("执行花费时间:" + (float)(endTime-startTime)/1000 + "s");
System.out.println("result = " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace(System.out);
}
}
}

CompletableFuture

针对Future接口做了改进,可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class Demo12 {
public static void main(String[] args) throws Exception {
// 创建异步执行任务:
CompletableFuture<Double> cf = CompletableFuture.supplyAsync(Demo12::fetchPrice);
// 如果执行完成的回调函数:
// 传入BiConsumer类型(可以传入两个参数)
cf.whenComplete((t,u)->{
System.out.println("执行结果:" + t);
System.out.println("异常信息:" + u);
});
// 传入Consumer类型
cf.thenAccept((result) -> {
System.out.println("price: " + result);
});
// 传入异常:
cf.exceptionally((e) -> {
e.printStackTrace();
return null;
});
// 主线程不要立刻结束,否则CompletableFuture默认使用的线程池会立刻关闭:
Thread.sleep(200);
}

static Double fetchPrice() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
if (Math.random() < 0.3) {
throw new RuntimeException("fetch price failed!");
}
return 5 + Math.random() * 20;
}
}

JMM

Java内存模型,是一种抽象概念

一些JMM同步约定:

  • 线程解锁前,必须把共享变量立即写回主存中
  • 线程加锁前,必须读取主存中最新的值保存在自身的工作内存中

八种内存间的交互操作

下图中除了6个操作,还有lock和unlock两个关于锁的操作

image-20211004203751132

  • lock(锁定):作用于主内存的变量;它把这一个变量标识为一个线程独占的状态
  • unlock(解锁):作用域主内存的变量,把一个变量释放
  • read(读取):作用于主内存的变量,把一个变量的值从主内存传输到工作内存中,以便随后的load动作使用
  • load(载入):作用于工作内存变量,它把read曹祖从主内存中得到的变量值放入工作内存的变量副本中
  • use(适应):作用于工作内存的变量,把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作
  • assign(赋值):作用于工作内存的变量,把一个执行引擎接收到值赋给工作的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作
  • store(存储):作用域工作内存的变量,它把工作内存中一个变量的值传递到主内存,以便随后的write操作
  • write(写入):作用于主内存的变量,它把store操作从工作内存得到的变量的值放入主内存的变量中

这八种操作中,read和load,store和write需要成对出现

Volatile

是JVM提供的轻量级的同步机制

  • 保证可见性

    添加Volatile关键字的属性对所有线程可见

  • 不保证原子性

  • 保证有序性(禁止指令重排)

如何保证原子性

使用原子性的类来替代原本的类,如:

1
private volatile static AtomicInteger num = new AtomicInteger();

原子类的方法都是调用native方法的CAS

指令重排

计算机在执行指令时可能会对程序代码的执行顺序作一定的修改,提高运行效率。

下面这种情况中,线程A和线程B自身的两行代码的执行顺序对于自身线程的结果来说没有任何影响,然而多线程的情况下可能会造成解决过的错误:

image-20211005150251656

Volatile实现有序性的原理

Volatile关键字会添加一个lock锁的前缀指令

lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:

  1. 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成
  2. 它会强制将对缓存的修改操作立即写入主存
  3. 如果是写操作,它会导致其他CPU中对应的缓存行无效

单例模式

最大的特点:构造器私有

饿汉式

提前实例化对象,可能会浪费空间

1
2
3
4
5
6
7
8
public class Hungry {
//加载类的时候就进行实例化
private final static Hungry HUNGRY = new Hungry();
private Hungry(){}
public static Hungry getInstance(){
return HUNGRY;
}
}

懒汉式

在获取实例时,对象不存在再实例化对象

1
2
3
4
5
6
7
8
9
10
11
public class Lazy {
private static Lazy lazy;
private Lazy(){}
public static Lazy getInstance(){
//实例不存在则实例化
if (lazy == null){
lazy = new Lazy();
}
return lazy;
}
}

内部类模式

能达到双检锁方式一样的功效,但实现更简单

这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。

1
2
3
4
5
6
7
8
9
10
public class Holder {
private Holder(){}
public static Holder getInstance(){
return InnerClass.HOLDER;
}
//内部类内实例化对象
public static class InnerClass{
private static final Holder HOLDER = new Holder();
}
}

DCL懒汉式

采用双锁机制,安全且在多线程情况下能保持高性能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class LazyDCL {
private static LazyDCL lazyDCL;
private LazyDCL(){}
public static LazyDCL getInstance(){
//实例不存在则实例化
if (lazyDCL == null){
//对类加锁,使其获得原子性
synchronized (LazyDCL.class){
if (lazyDCL == null){
lazyDCL = new LazyDCL();
}
}
}
return lazyDCL;
}
}

枚举

简洁,自动支持序列化机制,绝对防止多次实例化。

1
2
3
4
5
6
public enum  EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}

测试:

1
2
3
4
5
6
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<EnumSingle> constructor = EnumSingle.class.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
EnumSingle instance = constructor.newInstance();
System.out.println(instance);
}

结果:不允许利用反射获取实例:java.lang.IllegalArgumentException: Cannot reflectively create enum objects

原子性操作相关问题

CAS

原子类的方法compareAndSet,底层是使用Unsafe类的操作内存单元中的内容,具有原子性

1
2
//如果值为expect,就更新为update
public final boolean compareAndSet(int expect, int update)

使用

1
2
AtomicInteger atomicInteger = new AtomicInteger(2021);
atomicInteger.compareAndSet(0,1);

ABA问题

int num = 0

  1. 假设A线程最初读取num为0,
  2. 这时B线程读取并修改num修为1,然后经过一些操作后又将num修改为0
  3. 这时A线程读取num仍然为0,但是这时的num已经不是当初未作任何修改的num了,A线程对于num经历的修改毫不知情

解决方法:使用带版本号的原子操作

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Demo14 {
public static void main(String[] args) {
//这里注意atomicInteger值必须在-127~128之间才能成功
//日常使用会直接传入对象,不会使用Integer
AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1, 1);
//获取版本号
int stamp = atomicStampedReference.getStamp();
System.out.println("初始版本号:" + stamp);
atomicStampedReference.compareAndSet(1,2,
stamp,atomicStampedReference.getStamp() + 1);
System.out.println("修改后的版本号:" + atomicStampedReference.getStamp());
}
}