1.为何会用到并发java
充分利用多核CPU的计算能力程序员
方便进行业务拆分,提高应用性能面试
面对复杂业务模型,并行程序会比串行程序更适应业务需求,而并发编程更能吻合这种业务拆分算法
2.并发编程缺点数据库
频繁上下文切换编程
无锁并发编程:能够参照concurrentHashMap锁分段的思想,不一样的线程处理不一样段的数据,这样在多线程竞争的条件下,能够减小上下文切换的时间。数组
CAS算法,利用Atomic下使用CAS算法来更新数据,使用了乐观锁,能够有效的减小一部分没必要要的锁竞争带来的上下文切换缓存
使用最少线程:避免建立不须要的线程,好比任务不多,可是建立了不少的线程,这样会形成大量的线程都处于等待状态安全
协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换性能优化
那么,一般能够用以下方式避免死锁的状况:
3.须要了解的概念
3.1 同步与异步
同步和异步一般用来形容一次方法调用。同步方法调用一开始,调用者必须等待被调用的方法结束后,调用者后面的代码才能执行。而异步调用,指的是,调用者不用管被调用方法是否完成,都会继续执行后面的代码,当被调用的方法完成后会通知调用者。
3.2 并发与并行
并发和并行是十分容易混淆的概念。并发指的是多个任务交替进行,而并行则是指真正意义上的“同时进行”。实际上,若是系统内只有一个CPU,而使用多线程时,那么真实系统环境下不能并行,只能经过切换时间片的方式交替进行,而成为并发执行任务。真正的并行也只能出如今拥有多个CPU的系统中。
3.3 阻塞和非阻塞
阻塞和非阻塞一般用来形容多线程间的相互影响,好比一个线程占有了临界区资源,那么其余线程须要这个资源就必须进行等待该资源的释放,会致使等待的线程挂起,这种状况就是阻塞,而非阻塞就刚好相反,它强调没有一个线程能够阻塞其余线程,全部的线程都会尝试地往前运行。
3.4 临界区
临界区用来表示一种公共资源或者说是共享数据,能够被多个线程使用。可是每一个线程使用时,一旦临界区资源被一个线程占有,那么其余线程必须等待。
4.新建线程
一个java程序从main()方法开始执行,而后按照既定的代码逻辑执行,看似没有其余线程参与,但实际上java程序天生就是一个多线程程序,包含了:(1)分发处理发送给给JVM信号的线程;(2)调用对象的finalize方法的线程;(3)清除Reference的线程;(4)main线程,用户程序的入口。那么,如何在用户程序中新建一个线程了,只要有三种方式:
经过继承Thread类,重写run方法;
经过实现runable接口;
经过实现callable接口这三种方式,下面看具体demo。
public class CreateThread { public static void main(String[] args) { //继承Thread Thread thread = new Thread(){ @Override public void run() { System.out.println("继承Thread"); super.run(); } }; thread.start(); //实现Runnable接口 Thread thread1 = new Thread(new Runnable() { @Override public void run() { System.out.println("实现Runnable接口"); } }); thread1.start(); //经过callable接口实现 ExecutorService service = Executors.newSingleThreadExecutor(); Future<String> future = service.submit(new Callable<String>() { @Override public String call() throws Exception { return "经过callable接口实现"; } }); try { String result = future.get(); System.out.println(result); }catch (InterruptedException e){ e.printStackTrace(); }catch (ExecutionException e){ e.printStackTrace(); } } }
5. 线程的状态
此图来源于《JAVA并发编程的艺术》一书中,线程是会在不一样的状态间进行转换的,java线程线程转换图如上图所示。线程建立以后调用start()方法开始运行,当调用wait(),join(),LockSupport.lock()方法线程会进入到WAITING状态,而一样的wait(long timeout),sleep(long),join(long),LockSupport.parkNanos(),LockSupport.parkUtil()增长了超时等待的功能,也就是调用这些方法后线程会进入TIMED_WAITING状态,当超时等待时间到达后,线程会切换到Runable的状态,另外当WAITING和TIMED _WAITING状态时能够经过Object.notify(),Object.notifyAll()方法使线程转换到Runable状态。当线程出现资源竞争时,即等待获取锁的时候,线程会进入到BLOCKED阻塞状态,当线程获取锁时,线程进入到Runable状态。线程运行结束后,线程进入到TERMINATED状态,状态转换能够说是线程的生命周期。另外须要注意的是:
用一个表格将上面六种状态进行一个总结概括。
6.线程状态的基本操做
6.1 interrupted
6.2 join
join方法能够看作是线程间协做的一种方式,不少时候,一个线程的输入可能很是依赖于另外一个线程的输出.若是一个线程实例A执行了threadB.join(),其含义是:当前线程A会等待threadB线程终止后threadA才会继续执行。关于join方法一共提供以下这些方法:
public final synchronized void join(long millis) public final synchronized void join(long millis, int nanos) public final void join() throws InterruptedException
public class TestJoin { public static void main(String[] args) { Thread previousThread = Thread.currentThread(); for (int i = 0; i <= 10; i++) { Thread curThread = new JoinThread(previousThread, i); curThread.start(); previousThread = curThread; } } static class JoinThread extends Thread { private Thread thread; private int i; public JoinThread(Thread thread, int i) { this.thread = thread; this.i = i; } @Override public void run() { try { thread.join(); System.out.println(thread.getName() + " terminated" + ">>>" + "i=" + i); } catch (InterruptedException e) { e.printStackTrace(); } } } } 输出结果: main terminated>>>i=0 Thread-0 terminated>>>i=1 Thread-1 terminated>>>i=2 Thread-2 terminated>>>i=3 Thread-3 terminated>>>i=4 Thread-4 terminated>>>i=5 Thread-5 terminated>>>i=6 Thread-6 terminated>>>i=7 Thread-7 terminated>>>i=8 Thread-8 terminated>>>i=9 Thread-9 terminated>>>i=10
在上面的例子中一个建立了10个线程,每一个线程都会等待前一个线程结束才会继续运行。能够通俗的理解成接力,前一个线程将接力棒传给下一个线程,而后又传给下一个线程......
6.3 sleep
二者主要的区别:
6.5 守护线程Daemon
如图为JMM抽象示意图,线程A和线程B之间要完成通讯的话,要经历以下两步:
从横向去看看,线程A和线程B就好像经过共享变量在进行隐式通讯。这其中有颇有意思的问题,若是线程A更新后数据并无及时写回到主存,而此时线程B读到的是过时的数据,这就出现了“脏读”现象。能够经过同步机制(控制不一样线程间操做发生的相对顺序)来解决或者经过volatile关键字使得每次volatile变量都可以强制刷新到主存,从而对每一个线程都是可见的。
10.1 .happens-before定义
happens-before的概念最初由Leslie Lamport在其一篇影响深远的论文(《Time,Clocks and the Ordering of Events in a Distributed System》)中提出,有兴趣的能够google一下。JSR-133使用happens-before的概念来指定两个操做之间的执行顺序。因为这两个操做能够在一个线程以内,也能够是在不一样线程之间。所以,JMM能够经过happens-before关系向程序员提供跨线程的内存可见性保证(若是A线程的写操做a与B线程的读操做b之间存在happens-before关系,尽管a操做和b操做在不一样的线程中执行,但JMM向程序员保证a操做将对b操做可见)。具体的定义为:
1)若是一个操做happens-before另外一个操做,那么第一个操做的执行结果将对第二个操做可见,并且第一个操做的执行顺序排在第二个操做以前。
2)两个操做之间存在happens-before关系,并不意味着Java平台的具体实现必需要按照happens-before关系指定的顺序来执行。若是重排序以后的执行结果,与按happens-before关系来执行的结果一致,那么这种重排序并不非法(也就是说,JMM容许这种重排序)。
上面的1)是JMM对程序员的承诺。从程序员的角度来讲,能够这样理解happens-before关系:若是A happens-before B,那么Java内存模型将向程序员保证——A操做的结果将对B可见,且A的执行顺序排在B以前。注意,这只是Java内存模型向程序员作出的保证!
上面的2)是JMM对编译器和处理器重排序的约束原则。正如前面所言,JMM实际上是在遵循一个基本原则:只要不改变程序的执行结果(指的是单线程程序和正确同步的多线程程序),编译器和处理器怎么优化都行。JMM这么作的缘由是:程序员对于这两个操做是否真的被重排序并不关心,程序员关心的是程序执行时的语义不能被改变(即执行结果不能被改变)。所以,happens-before关系本质上和as-if-serial语义是一回事。as-if-serial语义的意思是:无论怎么重排序(编译器和处理器为了提供并行度),(单线程)程序的执行结果不能被改变。编译器,runtime和处理器都必须遵照as-if-serial语义。as-if-serial语义把单线程程序保护了起来,遵照as-if-serial语义的编译器,runtime和处理器共同为编写单线程程序的程序员建立了一个幻觉:单线程程序是按程序的顺序来执行的。
as-if-serial VS happens-before
10.2 具体规则
具体的一共有六项规则:
11. 总结
JMM是语言级的内存模型,在个人理解中JMM处于中间层,包含了两个方面:(1)内存模型;(2)重排序以及happens-before规则。同时,为了禁止特定类型的重排序会对编译器和处理器指令序列加以控制。而上层会有基于JMM的关键字和J.U.C包下的一些具体类用来方便程序员可以迅速高效率的进行并发编程。站在JMM设计者的角度,在设计JMM时须要考虑两个关键因素:
另外还要一个特别有意思的事情就是关于重排序问题,更简单的说,重排序能够分为两类:
JMM对这两种不一样性质的重排序,采起了不一样的策略,以下。
JMM的设计图为:
从图能够看出:
一个happens-before规则对应于一个或多个编译器和处理器重排序规则。对于Java程序员来讲,happens-before规则简单易懂,它避免Java程序员为了理解JMM提供的内存可见性保证而去学习复杂的重排序规则以及这些规则的具体实现方法
原文地址
https://www.jianshu.com/p/959cf355b574https://www.jianshu.com/p/f65ea68a4a7fhttps://www.jianshu.com/p/d52fea0d6ba5