java并发编程理论部分

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原则:
    1. 程序前面对某个变量的操作一定是对后续的操作可见的.
    2. volatile变量的写操作对于后续的读操作是可见的
    3. 传递性,案例中 v的写操作发生在前, —>a>b,b>c => a>c 恍然大明白的赶脚
    4. 管程中的锁,一个锁的解锁操作会优先于后续对这个锁的加锁,串行化的使用资源
    5. 线程的start()原则 主线程A启动子线程B,那么B启动之前A的操作对B可见
    6. 线程的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++;
	}
}

如何使用多把锁来保护一个资源

未完待续…

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值