快速鸟瞰并发编程, 呕心沥血整理的架构技术【1】

做者:享学课堂James老师java

转载请声明出处!程序员

Java程序员,你必须得知道并发编程概念面试

你们好,我是享学课堂风骚走位的James, 并发编程做为Java编程的核心灵魂, 无论在面试仍是在工做中,都是很是重要的, 花了很多时间我整理出了并发编程的一个核心知识, 但愿可以帮助更多的Java开发人员,在工做中合理使用多线程, 让你的代码更高效, 更直观。大概分为如下板块。编程

目录数组

► 简介安全

► 概念bash

►Java内存模型:Happens-before 关系数据结构

► 标准同步功能多线程

► 安全发布并发

► 对象不可变

► 线程Thread类

► 线程的活跃度:死锁与活锁

► java.util.concurrent包

第1节 简介

所谓并发编程是指在一台处理器上“同时”处理多个任务。并发是在同一实体上的多个事件。这段是从百度百科找到的解释, 而个人解释, 你所写的任何一行代码, 它的执行都是用线程来运行的, 也就是说并发编程直接决定了你系统性能运行状况, 能让你的系统起飞, 也能让你的系统性能变成蜗牛。

第2节 概念

从JDK的最低版本开始,Java就支持了线程和锁等关键并发概念。那么这些概念务必记到你脑海深处,哈哈.

前提条件

当多个线程对共享资源执行一系列操做时, 它们会对竞争共享资源, 每一个线程的操做顺序不同, 会致使多个不可预期的操做结果。好比如下的代码就为非线程安全的,其中 value能够屡次初始化,由于 if(value==null)作了null 判断, 而后初始化的, 延迟初始化的字段不是原子的

class  JamesLazy <T>  {
	private  volatile T value;
	T get() {
		if (value == null)//这里作了null判断, 延迟初始化的字段它不是原子的
		value = initialize();
		return value;
	}
}

复制代码

数据竞争

当两个或多个线程在没有同步的状况下尝试访问相同的非final变量时,就会发生数据竞争。不使用同步可能致使你的全部操做对其它线程是不可见的,所以能够读取过期数据,但若反过来可能会产生无限循环,损坏的数据结构或不许确的计算等后果。此代码可能会致使无限循环,由于读者线程可能永远不会观察到写入器线程所作的更改:

class  JamesWaiter  implements  Runnable {
	private  Boolean shouldFinish;
	void finish() {
		shouldFinish = true;
	}
	public  void run() {
		long iteration = 0;
		while (!shouldFinish) {
			iteration++;
		}
		System.out.println("完成后的结果: " + iteration);
	}
}
class  JamesDataRace {
	public  static  void main(String[] args) throws  InterruptedException {
		JamesWaiter waiter = new  JamesWaiter();
		Thread waiterThread = new  Thread(waiter);
		waiterThread.start();
		waiter.finish();
		waiterThread.join();
	}
}
复制代码

运行结果为:

第3节 Java内存模型:Happens-before 关系

Java内存模型是根据读取和写入字段以及在监听器上同步等操做定义的。能够经过Happens-before关系对操做进行排序,通常用于推断线程什么时候看到另外一个线程的操做结果,以及用来分析同步的程序构成情况。

在下图中,Thread 1的 ActionX操做在 ActionY操做以前就调用了,所以在 Thread2中全部操做 ActionY的右侧业务操做时, 将会看到Thread 1中Action X前的全部操做。

第4节 标准同步功能

synchronized关键字

synchronized关键字用于防止不一样的线程同时执行相同的代码块, 其实就是指这个代码块只被一个线程执行。当线程A得到synchronized锁后,只有线程A访问synchronized代码块,是一种独占式模式操做, 例如: 13号技师被王根基同窗带进屋后,王根基同窗在门上挂了把锁, 其它线程得等待, 保证了13号技师只能与王根基(线程)进行业务操做 ,不难看出这个操做原子操做(只有王根基线程操做13号技师)。此外,它保证其它线程在获取相同的锁以后将观察正在操做线程的结果,什么时候释放锁。

class  JamesAtomicOperation {
	private  int counter0 ;
	private  int counter1 ;
	void increment(){
		synchronized(this){
			counter0 ++ ;
			counter1 ++ ;
		}
	}
}
复制代码

synchronized关键字能够在方法级来指定。

锁是可重入的,所以若是线程已经拥有锁,能够再次成功获取它。

class  JamesReentrantcy {
	synchronized  void doAll(){
		doFirst();
		doSecond();
	}
	synchronized  void doFirst(){
		System.out.println(“第一次操做成功。”);
	}
	synchronized  void doSecond(){
		System.out.println(“第二次操做成功。”);
	}
}
复制代码

等待/通知

wait/notify/notifyAll方法在 Object类中声明。wait的做用可使线程状态变成 WAITINGTIMED_WAITING(若是已等待超时)状态。为了唤醒一个线程,能够执行如下任何操做:

  • 另外一个线程调用 notify,唤醒在监视器上等待的任意线程。

  • 另外一个线程调用 notifyAll,唤醒监视器上等待的全部线程。

  • 若是调用 Thread#interrupt。在这种状况下,会抛出 InterruptedException

最多见的模式是条件循环:

class  JamesConditionLoop {
	private  Boolean condition;
	synchronized  void waitForCondition()throws  InterruptedException {
		while(!condition){
			wait();
		}
	}
	synchronized  void satisfCondition(){
		condition  = true ;
		notifyAll();
	}
}
复制代码
  • 同志们请记住,若是 wait/notify/notifyAll要在你的对象上使用,须要首先获取此对象锁。

  • 老是在一个循环中等待检查正在等待的条件 - 若是另外一个线程在等待开始以前知足条件,这就解决了时间问题。此外,它还能够保护您的代码免受可能(而且确实发生)的虚假唤醒。

  • 在打电话以前,请务必确保您知足等待条件 notify/notifyAll。若是不这样作将致使通知,但没有线程可以逃脱其等待循环。

volatile关键字

volatile解决了多线程之间的资源可见性问题,有这么一层关系你们须要知道:对某个volatile字段的写操做happens-before后续对同一个volatile字段的读操做,好比线程1写入了volatile变量v(这里和后续的“变量”都指的是对象的字段、类字段和数组元素),接着线程2读取了v,那么,线程1写入v及以前的写操做都对线程2可见(线程1和线程2能够是同一个线程)。所以,它保证字段的任何后续读取都将看到由最近写入设置的值。

class  JamesVolatileFlag  implements  Runnable {
	private  volatile  Boolean shouldStop;
	public  void run() {
		while (!shouldStop) {
			// TODO业务代码
		}
		System.out.println("中止.");
	}
	void stop() {
		shouldStop = true;
	}
	public  static  void main(String[] args) throws  InterruptedException {
		JamesVolatileFlag flag = new  JamesVolatileFlag();
		Thread thread = new  Thread(flag);
		thread.start();
		flag.stop();
		thread.join();
	}
}
复制代码

原子操做

java.util.concurrent.atomic包路径下的一些类以无锁方式支持单个值上的原子复合操做,相似于volatile。

使用AtomicXXX类,能够实现原子 check-then-act操做:

class  JamesCheckThenAct {
	private  final  AtomicReference<String> value = new  AtomicReference<>();
	void initialize() {
		if (value.compareAndSet(null, "value")) {
			System.out.println("仅初始化一次.");
		}
	}
}

复制代码

jdk8中新增的 AtomicIntegerAtomicLong具备 increment/decrement(自增自减)的原子操做:

class  JamesIncrement {
	private  final  AtomicInteger state = new  AtomicInteger();
	void advance() {
		int oldState = state.getAndIncrement();
		System.out.println("Advanced: '" + oldState + "' -> '" + (oldState + 1) + "'.");
	}
}
复制代码

若计数器不须要原子操做来获取其值,请考虑使用 LongAdder而不是 AtomicLong/ AtomicInteger类。LongAdder其实就是把一个变量分解为多个变量,让一样多的线程去竞争多个资源,性能问题就能够解决了,所以它在高争用下表现更好。

ThreadLocal

从概念上讲,ThreadLocal就好像在每一个Thread中都有一个具备本身版本的变量。ThreadLocal一般用于存储每一个线程的值,如“当前事务”或其余资源。此外,它们还用于维护每线程计数器,统计信息或ID生成器。

class  JamesTransactionManager {
	private  final  ThreadLocal<Transaction> currentTransaction = ThreadLocal.withInitial(NullTransaction::new);
	Transaction currentTransaction() {
		Transaction current = currentTransaction.get();
		if (current.isNull()) {
			current = new  TransactionImpl();
			currentTransaction.set(current);
		}
		return current;
	}
}
复制代码

好了, 第1篇就到这里,但愿你们持续关注更新…………

相关文章
相关标签/搜索