java并发编程基础理论
Chapter1 并发编程的幕后
背景
1.计算机的发展历程上,电脑的性能一直在提升,但是核心的矛盾一直存在,我们的cpu,内存,磁盘之间的巨大速度差异
为了解决这个问题,最大的获取计算机的性能,那么就需要去平衡硬件的性能,方案如下:
- cpu添加缓存,L1,L2,L3的缓存(电脑任务管理器的CPU栏可以看到),L1,L2是非共享的缓存,L3是共享
- 操作系统添加了进程,线程,用来进行分时复用cpu,用来均衡cpu和io的速度差异
- 编译程序的指令可能会进行优化来更加充分的利用缓存(双循环的懒汉式单例代码写法,变量添加volatile)
我们用这段代码来证明cpuCache的存在,遍历数组,对比遍历的时间
public static void main(String[] args) {
ArrayList<Long> result1 =new ArrayList<>();//记录完全遍历
ArrayList<Long> result2 =new ArrayList<>();//记录跳跃遍历
int[] arr = new int[128 * 1024 * 1024];
for (int i = 0; i < 10; i++) {
long stamp1 = System.currentTimeMillis();
for (int j = 0; j < arr.length; j++) {
arr[j] *= 3;
}
//如果我们将计算的元素进行改变一下,那就很有意思了,能说明缓存的存在???
long stamp2 = System.currentTimeMillis();
//如果把这个跳跃值改掉? 为什么是16,换个别的数据 cpu的cacheline是64个字节
for (int k = 0; k < arr.length; k += 16) {
arr[k] *= 3; //把j或者k改成常量,那就很有意思了
}
long stamp3 = System.currentTimeMillis();
result1.add(stamp2-stamp1); //记录遍历时间
result2.add(stamp3-stamp2)
}
Long total = result1.stream().reduce(0L, (a,b)->a+b);
Long total1 = result2.stream().reduce(0L, (a,b)->a+b);
result1.forEach(t->{
System.out.print(t+" ");
});
System.out.println();
System.out.println("-------------");
result2.forEach(t->{
System.out.print(t+" ");
});
}
public class SingleTon {
//volatile
private static SingleTon singleInstance;
private SingleTon() {
}
//1、分配一个区域
//2、对应区域上初始化对象
//3、然后将内存区域指向引用 singleInstance
//万一编译器优化一下?指令重排
public static SingleTon getSingleInstance(){
if (singleInstance != null){
synchronized (SingleTon.class){
if (singleInstance != null) {
singleInstance=new SingleTon();
}
}
}
return singleInstance;
}
}
缓存导致的可见性问题
关于volatitle的描述,禁用CPU缓存,怎么证明呢?两个线程.一个改变变量.另外一个监听变量的值,怎么让他即时监听到呢(涉及到java内存模型happens-before)
public class CpuCacheTest {
// 修改volatile的关键字,验证volatile对这个程序的影响
private static volatile Integer Counter = 0;
public static void main(String[] args) {
new changeListener().start();
new Changer().start();
}
static class changeListener extends Thread {
@Override
public void run() {
int threadValue = Counter;
while (threadValue < 5) {
if (threadValue != Counter) {
System.out.println("改变值" + threadValue);
threadValue = Counter;
}
}
}
}
static class Changer extends Thread {
Integer threadValue = Counter;
@Override
public void run() {
while (threadValue < 5) {
System.out.println("添加值: "+threadValue);
Counter=++threadValue;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
volatile能让一个变量线程安全吗? 为什么??
public class VolatitleTest {
private static volatile int num=0;
private static final int count=1000;
private static final int client=20;
public static void main(String[] args) throws InterruptedException {
ExecutorService pool = Executors.newCachedThreadPool();
CountDownLatch count=new CountDownLatch(1000);
for (int i = 0; i < 1000; i++) {
pool.execute(()->{
increLockNum();
count.countDown();
});
}
count.await();
pool.shutdown();
System.out.println(num);
}
private static void increNum(){
num++;
}
private synchronized static void increLockNum(){
num++;
}
}
答案必然是否定的.volatile只能保证读到的数据是最新的数据,对数据的操作是会有并发问题的!!!
Chapter2 java的内存模型 (注意和JVM的内存模型的区分)
并发程序出现各种问题的原因,问题根源就在于可见性和有序性
那么我们解决并发的最优解决方案应该就是,禁用缓存和编译优化,so? 不太现实,牺牲浪费了性能
解决方案就应该是,按需禁用缓存和编译优化,对此,java内存模型规范了JVM的方法:
-
volatile
-
synchronized
-
final (推荐使用,可以提升代码的性能,也可以控制代码的规范,局部变量只能赋值一次,增加可读性)
-
六项happens-before原则
public class VolatileExample { int x =0; volatile boolean v=false; public void writer(){ x=42; v=true; } public void reader(){ if (v){ System.out.println(x); } } }
happens-before原则:
- 程序前面对某个变量的操作一定是对后续的操作可见的.
- volatile变量的写操作对于后续的读操作是可见的
- 传递性,案例中 v的写操作发生在前, —>a>b,b>c => a>c 恍然大明白的赶脚
- 管程中的锁,一个锁的解锁操作会优先于后续对这个锁的加锁,串行化的使用资源
- 线程的start()原则 主线程A启动子线程B,那么B启动之前A的操作对B可见
- 线程的join原则,A中joinB, B结束后的操作对A是可见的
线程中的一个变量,对另外的线程可见:
volatile
加锁
join
chapter3 解决原子性问题
原子性的问题根本原因在于线程的切换
为了安全,那我们应该让同一时刻永远只有一个线程去执行,互斥(加锁)
加锁的那段代码我们叫做临界区 javap -v
我们可以使用一把锁来保护多个资源
public class TestSynLock {
//请求总数
public static int clienTotal=10000;
public static int threadTotal=200;
public static int count =0;
public static void main(String[] args) throws InterruptedException {
ExecutorService threadPool= Executors.newCachedThreadPool();
//信号量
final Semaphore semaphore=new Semaphore(threadTotal);
//锁
final CountDownLatch countDownLatch=new CountDownLatch(clienTotal);
TestSynLock demo=new TestSynLock();
for (int i = 0; i < clienTotal; i++) {
final int index=i;
threadPool.execute(()->
{
try {
semaphore.acquire();
//if (index%2==0){
// demo.add2();
//}else{
// demo.add1();
//}
demo.add3();
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
countDownLatch.countDown();
});
}
countDownLatch.await();
System.out.println(count);
threadPool.shutdown();
}
public synchronized static void add(){
count++;
}
public synchronized void add1(){
count++;
}
public synchronized void add2(){
count++;
}
public void add3(){
Object object=new Object(); 标量替换,同步消除,栈上分配
synchronized (object){
count++;
}
}
public void add4(){
count++;
}
}
如何使用多把锁来保护一个资源
未完待续…