并发:同时拥有两个或者多个线程,如果程序在单核处理器上运行,多个线程将交替地换入或者换出内存,这些线程是同时“存在”的,每个线程都处于执行过程中的某个状态。如果运行在多核处理器上,此时,程序中的每个线程都将分配到一个处理器核上,因此可以同时运行。
缓存模式的转换
为什么需要 CPU Cache
:CPU 的频率太快了,快到主存跟不上,这样在处理器时钟周期内,CPU 常常需要等待主存,浪费资源。所以 Cache 的出现,是为了缓解 CPU 和内存之间速度不匹配的问题(结构:CPU -> Cache -> Memory)
CPU Cache的意义:
CPU 多级缓存的乱序执行优化(重排序)原理:处理器为提高运算速度而做出违背代码原有顺序的优化
JMM:内存模型描述了程序中的各个变量之间的关系,以及在实际计算机系统中将变量储存到内存和从内存中取出变量这样的底层细节。JMM 定义了多线程之间共享变量的可见性以及如何在需要的时候对共享变量进行同步。Java 线程之间的通信由 Java 内存模型控制,JMM 决定一个线程对共享变量的写入何时对另一个线程可见。
JMM 控制示意图:
Java 内存模型 — 同步的八种操作:
优势:
风险:
使用 CountDownLatch
类、Semaphore
类
线程不安全情况测试
package Test3;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
/**
* 通过 CountDownLatch 、 Semaphore 测试线程不安全
* @author JJJiker
*
*/
public class ConcurrencyTest2 {
//请求总数
public static int clientTotal = 5000;
//同时并发执行的线程数
public static int threadTotal = 200;
public static int count = 0;
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0;i {
try {
semaphore.acquire();
add();
semaphore.release();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
System.out.println("count: " + count);
}
public static void add() {
count++;
}
}
可见,每一次运行程序,count 总数始终无法到达请求总数 clientTotal
5000
定义:当多个线程访问某个类时,不管运行时环境采用何种调度方式,或者这些进程如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么这个类就是线程安全的
线程安全性三大特征:
1、原子性:提供了互斥访问,同一时刻只能有一个线程来对它进行操作
2、可见性:一个线程对主内存的修改可以及时的被其他线程观察到
3、有序性:一个线程观察其他线程中的指令执行顺序,由于指令的重排序的存在,该观察结果一般杂乱无序
代码演示
1、AtomicInteger CAS(compareAndSet) 保证线程安全
public static AtomicInteger count = new AtomicInteger(0);
public static void add() {
count.incrementAndGet();
}
原理:incrementAndGet 方法将调用 U.getAndAddInt,该方法大致原理是不断尝试将一个比当前值大1的新值赋给自己,如果失败则说明在执行"获取-设置"操作的时已经被其它线程修改过了,于是便再次进入循环下一次操作,直到成功为止
public final int incrementAndGet() {
return U.getAndAddInt(this, VALUE, 1) + 1;
}
@HotSpotIntrinsicCandidate
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!weakCompareAndSetInt(o, offset, v, v + delta));
return v;
}
2、AtomicReference类 代码测试
AtomicReference函数列表
// 使用 null 初始值创建新的 AtomicReference。
AtomicReference()
// 使用给定的初始值创建新的 AtomicReference。
AtomicReference(V initialValue)
// 如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。
boolean compareAndSet(V expect, V update)
// 获取当前值。
V get()
// 以原子方式设置为给定值,并返回旧值。
V getAndSet(V newValue)
// 最终设置为给定值。
void lazySet(V newValue)
// 设置为给定值。
void set(V newValue)
// 返回当前值的字符串表示形式。
String toString()
// 如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。
boolean weakCompareAndSet(V expect, V update)
测试代码
package Test3;
import java.util.concurrent.atomic.AtomicReference;
public class AtomicReferenceExample {
private static AtomicReference count = new AtomicReference<>(0);
public static void main(String[] args) {
count.compareAndSet(0, 2);
count.compareAndSet(0, 1);
count.compareAndSet(1, 3);
count.compareAndSet(2, 4);
count.compareAndSet(3, 5);
System.out.println(count.get());
}
}
原理:
AtomicReference 是通过 “volatile” 和 "Unsafe“ 提供的CAS函数实现"原子操作。
(01) value是volatile类型。这保证了:当某线程修改value的值时,其他线程看到的value值都是最新的value值,即修改之后的volatile的值。
(02) 通过CAS设置value。这保证了:当某线程池通过CAS函数(如compareAndSet函数)设置value时,它的操作是原子的,即线程在操作value时不会被中断。
Synchronized
同步锁,修饰对象:
1、修饰代码块:
public void test() {
synchronized (this) {
for (int i = 0;i < 10;i++) {
System.out.println("test: " + i);
}
}
}
同一个对象测试
public static void main(String[] args) {
SynchronizedExample example1 = new SynchronizedExample();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> {
example1.test();
});
executorService.execute(() -> {
example1.test();
});
}
public static void main(String[] args) {
SynchronizedExample example1 = new SynchronizedExample();
SynchronizedExample example2 = new SynchronizedExample();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> {
example1.test();
});
executorService.execute(() -> {
example2.test();
});
}
2、修饰方法
public synchronized void test() {
for (int i = 0;i < 10;i++) {
System.out.println("test: " + i);
}
}
3、修饰静态方法
public static synchronized void test() {
for (int i = 0;i < 10;i++) {
System.out.println("test: " + i);
}
}
4、修饰类
public static void test() {
synchronized (SynchronizedExample.class){
for (int i = 0;i < 10;i++) {
System.out.println("test: " + i);
}
}
}
导致共享变量在线程间不可见的原因:
可见性 — volatile
通过加入内存屏障和禁止重排序优化来实现
可见性 volatile 的使用
volatile boolean inited = false;
//线程1
context =loadContext();
inited = true;
//线程2
while( !inited){
sleep();
}
doSomethingWithConfig(context)
注:volatile 不具有原子性,不适用于计数,不能保证线程安全
Java 内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行却会影响到多线程程序的并发执行的正确性
有序性 :happens — before 原则
时间:2019.6.23 20:09