最近看到网上流传着,各类面试经验及面试题,每每都是一大堆技术题目贴上去,而没有答案。html
无论你是新程序员仍是老手,你必定在面试中遇到过有关线程的问题。Java语言一个重要的特色就是内置了对并发的支持,让Java大受企业和程序员的欢迎。大多数待遇丰厚的Java开发职位都要求开发者精通多线程技术而且有丰富的Java程序开发、调试、优化经验,因此线程相关的问题在面试中常常会被提到。
在典型的Java面试中, 面试官会从线程的基本概念问起java
如:为何你须要使用线程, 如何建立线程,用什么方式建立线程比较好(好比:继承thread类仍是调用Runnable接口),而后逐渐问到并发问题像在Java并发编程的过程当中遇到了什么挑战,Java内存模型,JDK1.5引入了哪些更高阶的并发工具,并发编程经常使用的设计模式,经典多线程问题如生产者消费者,哲学家就餐,读写器或者简单的有界缓冲区问题。仅仅知道线程的基本概念是远远不够的, 你必须知道如何处理死锁,竞态条件,内存冲突和线程安全等并发问题。掌握了这些技巧,你就能够轻松应对多线程和并发面试了。
许多Java程序员在面试前才会去看面试题,这很正常。程序员
由于收集面试题和练习很花时间,因此我从许多面试者那里收集了Java多线程和并发相关的50个热门问题。面试
下面是Java线程相关的热门面试题,你能够用它来好好准备面试。算法
前25题想进大厂?50个多线程面试题,你会多少?(一)编程
前25题 想进大厂?50个多线程面试题,你会多少?(一)设计模式
CyclicBarrier和CountDownLatch 都位于java.util.concurrent 这个包下api
CountDownLatch | CyclicBarrier |
---|---|
减计数方式 | 加计数方式 |
计算为0时释放全部等待的线程 | 计数达到指定值时释放全部等待线程 |
计数为0时,没法重置 | 计数达到指定值时,计数置为0从新开始 |
调用countDown()方法计数减一,调用await()方法只进行阻塞,对计数没任何影响 | 调用await()方法计数加1,若加1后的值不等于构造方法的值,则线程阻塞 |
不可重复利用 | 可重复利用 |
CountDownLatch类只提供了一个构造器:数组
public CountDownLatch(int count) { }; //参数count为计数值
而后下面这3个方法是CountDownLatch类中最重要的方法:缓存
public void await() throws InterruptedException { }; //调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行 public boolean await(long timeout, TimeUnit unit) throws InterruptedException { }; //和await()相似,只不过等待必定的时间后count值还没变为0的话就会继续执行 public void countDown() { }; //将count值减1
CountDownLatch, 一个同步辅助类,在完成一组正在其余线程中执行的操做以前,它容许一个或多个线程一直等待。
下面举个例子说明:
package main.java.CountDownLatch; import java.util.concurrent.CountDownLatch; /** * PROJECT_NAME:downLoad * Author:lucaifang * Date:2016/3/18 */ public class countDownlatchTest { public static void main(String[] args) throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(5); for(int i=0;i<5;i++){ new Thread(new readNum(i,countDownLatch)).start(); } countDownLatch.await(); System.out.println("线程执行结束。。。。"); } static class readNum implements Runnable{ private int id; private CountDownLatch latch; public readNum(int id,CountDownLatch latch){ this.id = id; this.latch = latch; } @Override public void run() { synchronized (this){ System.out.println("id:"+id); latch.countDown(); System.out.println("线程组任务"+id+"结束,其余任务继续"); } } } }
输出结果:
id:1 线程组任务1结束,其余任务继续 id:0 线程组任务0结束,其余任务继续 id:2 线程组任务2结束,其余任务继续 id:3 线程组任务3结束,其余任务继续 id:4 线程组任务4结束,其余任务继续 线程执行结束。。。。
线程在countDown()以后,会继续执行本身的任务
CyclicBarrier会在全部线程任务结束以后,才会进行后续任务,具体能够看下面例子。
CyclicBarrier提供2个构造器:
public CyclicBarrier(int parties, Runnable barrierAction) { } public CyclicBarrier(int parties) { }
参数parties指让多少个线程或者任务等待至barrier状态;参数barrierAction为当这些线程都达到barrier状态时会执行的内容。
CyclicBarrier中最重要的方法就是await方法
//挂起当前线程,直至全部线程都到达barrier状态再同时执行后续任务; public int await() throws InterruptedException, BrokenBarrierException { }; //让这些线程等待至必定的时间,若是还有线程没有到达barrier状态就直接让到达barrier的线程执行后续任务 public int await(long timeout, TimeUnit unit)throws InterruptedException,BrokenBarrierException,TimeoutException { };
举例说明
package main.java.countOff; import java.util.concurrent.CyclicBarrier; /** * PROJECT_NAME:downLoad * Author:lucaifang * Date:2016/3/18 */ public class cyclicBarrierTest { public static void main(String[] args) throws InterruptedException { CyclicBarrier cyclicBarrier = new CyclicBarrier(5, new Runnable() { @Override public void run() { System.out.println("线程组执行结束"); } }); for (int i = 0; i < 5; i++) { new Thread(new readNum(i,cyclicBarrier)).start(); } //CyclicBarrier 能够重复利用, // 这个是CountDownLatch作不到的 // for (int i = 11; i < 16; i++) { // new Thread(new readNum(i,cyclicBarrier)).start(); // } } static class readNum implements Runnable{ private int id; private CyclicBarrier cyc; public readNum(int id,CyclicBarrier cyc){ this.id = id; this.cyc = cyc; } @Override public void run() { synchronized (this){ System.out.println("id:"+id); try { cyc.await(); System.out.println("线程组任务" + id + "结束,其余任务继续"); } catch (Exception e) { e.printStackTrace(); } } } } }
输出结果:
id:1 id:2 id:4 id:0 id:3 线程组执行结束 线程组任务3结束,其余任务继续 线程组任务1结束,其余任务继续 线程组任务4结束,其余任务继续 线程组任务0结束,其余任务继续 线程组任务2结束,其余任务继续
http://blog.csdn.net/tolcf/article/details/50925145
一、LockSupport基本介绍与基本使用
LockSupport是JDK中比较底层的类,用来建立锁和其余同步工具类的基本线程阻塞。java锁和同步器框架的核心 AQS: AbstractQueuedSynchronizer,就是经过调用 LockSupport .park()和 LockSupport .unpark()实现线程的阻塞和唤醒 的。
LockSupport 很相似于二元信号量(只有1个许可证可供使用),若是这个许可尚未被占用,当前线程获取许可并继 续 执行;若是许可已经被占用,当前线 程阻塞,等待获取许可。
当前线程须要唤醒另外一个线程,可是只肯定它会进入阻塞,但不肯定它是否已经进入阻塞,所以无论是否已经进入阻塞,仍是准备进入阻塞,都将发放一个通行准许。
把LockSupport视为一个sleep()来用,只是sleep()是定时唤醒,LockSupport既能够定时唤醒,也能够由其它线程唤醒。
public static void main(String[] args) { LockSupport.park(); System.out.println("block."); }
运行该代码,能够发现主线程一直处于阻塞状态。由于 许可默认是被占用的 ,调用park()时获取不到许可,因此进入阻塞状态。
以下代码:先释放许可,再获取许可,主线程可以正常终止。LockSupport许可的获取和释放,通常来讲是对应的,若是屡次unpark,只有一次park也不会出现什么问题,结果是许可处于可用状态。
public static void main(String[] args) { Thread thread = Thread.currentThread(); LockSupport.unpark(thread);//释放许可 LockSupport.park();// 获取许可 System.out.println("b"); }
LockSupport是不可重入 的,若是一个线程连续2次调用 LockSupport .park(),那么该线程必定会一直阻塞下去。
public static void main(String[] args) throws Exception { Thread thread = Thread.currentThread(); LockSupport.unpark(thread); System.out.println("a"); LockSupport.park(); System.out.println("b"); LockSupport.park(); System.out.println("c"); }
这段代码打印出a和b,不会打印c,由于第二次调用park的时候,线程没法获取许可出现死锁。
LockSupport基本介绍与基本使用
http://www.javashuo.com/article/p-kxfabitl-dp.html
LockSupport基本介绍与基本使用
http://www.tianshouzhi.com/api/tutorials/mutithread/303
ReentrantLock和Condition的使用方式一般是这样的:
public static void main(String[] args) { final ReentrantLock reentrantLock = new ReentrantLock(); final Condition condition = reentrantLock.newCondition(); Thread thread = new Thread((Runnable) () -> { try { reentrantLock.lock(); System.out.println("我要等一个新信号" + this); condition.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("拿到一个信号!!" + this); reentrantLock.unlock(); }, "waitThread1"); thread.start(); Thread thread1 = new Thread((Runnable) () -> { reentrantLock.lock(); System.out.println("我拿到锁了"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } condition.signalAll(); System.out.println("我发了一个信号!!"); reentrantLock.unlock(); }, "signalThread"); thread1.start(); }
运行后,结果以下:
我要等一个新信号lock.ReentrantLockTest$1@a62fc3 我拿到锁了 我发了一个信号!! 拿到一个信号!!
能够看到
Condition的执行方式,是当在线程1中调用await方法后,线程1将释放锁,而且将本身沉睡,等待唤醒,
线程2获取到锁后,开始作事,完毕后,调用Condition的signal方法,唤醒线程1,线程1恢复执行。
以上说明Condition是一个多线程间协调通讯的工具类,使得某个,或者某些线程一块儿等待某个条件(Condition),只有当该条件具有( signal 或者 signalAll方法被带调用)时 ,这些等待线程才会被唤醒,从而从新争夺锁。
Condition本身也维护了一个队列,该队列的做用是维护一个等待signal信号的队列,两个队列的做用是不一样,事实上,每一个线程也仅仅会同时存在以上两个队列中的一个,流程是这样的
怎么理解Condition
http://www.importnew.com/9281.html
深刻理解Condition
https://www.jianshu.com/p/6b5aa7b7684c
Oracle的官方给出的定义是:Fork/Join框架是一个实现了ExecutorService接口的多线程处理器。它能够把一个大的任务划分为若干个小的任务并发执行,充分利用可用的资源,进而提升应用的执行效率。
咱们再经过Fork和Join这两个单词来理解下Fork/Join框架,Fork就是把一个大任务切分为若干子任务并行的执行,Join就是合并这些子任务的执行结果,最后获得这个大任务的结果。
好比计算1+2+。。+10000,能够分割成10个子任务,每一个子任务分别对1000个数进行求和,最终汇总这10个子任务的结果。
工做窃取算法是指线程从其余任务队列中窃取任务执行(可能你会很诧异,这个算法有什么用。待会你就知道了)。考虑下面这种场景:有一个很大的计算任务,为了减小线程的竞争,会将这些大任务切分为小任务并分在不一样的队列等待执行,而后为每一个任务队列建立一个线程执行队列的任务。那么问题来了,有的线程可能很快就执行完了,而其余线程还有任务没执行完,执行完的线程与其空闲下来不如帮助其余线程执行任务,这样也能加快执行进程。因此,执行完的空闲线程从其余队列的尾部窃取任务执行,而被窃取任务的线程则从队列的头部取任务执行(这里使用了双端队列,既不影响被窃取任务的执行过程又能加快执行进度)。
从以上的介绍中,可以发现工做窃取算法的优势是充分利用线程提升并行执行的进度。固然缺点是在某些状况下仍然存在竞争,好比双端队列只有任务须要执行的时候
分割任务:首先须要建立一个ForkJoin任务,执行该类的fork方法能够对任务不断切割,直到分割的子任务足够小
合并任务执行结果:子任务执行的结果同一放在一个队列中,经过启动一个线程从队列中取执行结果。
Fork/Join实现了ExecutorService,因此它的任务也须要放在线程池中执行。它的不一样在于它使用了工做窃取算法,空闲的线程能够从满负荷的线程中窃取任务来帮忙执行。
下面是计算1+2+3+4为例演示如何使用使用Fork/Join框架:
package com.rhwayfun.concurrency.r0406; import java.util.concurrent.ExecutionException; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinTask; import java.util.concurrent.RecursiveTask; /** * Created by rhwayfun on 16-4-6. */ public class CountTask extends RecursiveTask<Integer>{ //阈值 private static final int THRESHOLD = 2; //起始值 private int start; //结束值 private int end; public CountTask(int start, int end) { this.start = start; this.end = end; } @Override protected Integer compute() { boolean compute = (end - start) <= THRESHOLD; int res = 0; if (compute){ for (int i = start; i <= end; i++){ res += i; } }else { //若是长度大于阈值,则分割为小任务 int mid = (start + end) / 2; CountTask task1 = new CountTask(start,mid); CountTask task2 = new CountTask(mid + 1, end); //计算小任务的值 task1.fork(); task2.fork(); //获得两个小任务的值 int task1Res = task1.join(); int task2Res = task2.join(); res = task1Res + task2Res; } return res; } public static void main(String[] args) throws ExecutionException, InterruptedException { ForkJoinPool pool = new ForkJoinPool(); CountTask task = new CountTask(1,5); ForkJoinTask<Integer> submit = pool.submit(task); System.out.println("Final result:" + submit.get()); } }
代码执行结果为:
15
代码中使用了FokJoinTask,其与通常任务的区别在于它须要实现compute方法,在方法须要判断任务是否在阈值区间内,若是不是则须要把任务切分到足够小,直到可以进行计算。
每一个被切分的子任务又会从新进入compute方法,再继续判断是否须要继续切分,若是不须要则直接获得子任务执行的结果,若是须要的话则继续切分,如此循环,直到调用join方法获得最终的结果。
**
能够发现Fork/Join框架的须要把提交给ForkJoinPool,ForkJoinPool由ForkJoinTask数组和ForkJoinWorkerThread数组组成,前者负责将存放程序提交给ForkJoinPool的任务,后者则负责执行这些任务。关键在于在于fork方法与join方法**
Java并发编程系列之二十:Fork/Join框架
http://blog.csdn.net/u011116672/article/details/51073683
sleep()
方法是线程类(Thread)的静态方法,让调用线程进入睡眠状态,让出执行机会给其余线程,等到休眠时间结束后,线程进入就绪状态和其余线程一块儿竞争cpu的执行时间。
由于sleep() 是static静态的方法,他不能改变对象的机锁,当一个synchronized块中调用了sleep() 方法,线程虽然进入休眠,可是对象的机锁没有被释放,其余线程依然没法访问这个对象。
wait()
wait()是Object类的方法,当一个线程执行到wait方法时,它就进入到一个和该对象相关的等待池,同时释放对象的机锁,使得其余线程可以访问,能够经过notify,notifyAll方法来唤醒等待的线程
线程一般都有五种状态,建立、就绪、运行、阻塞和死亡。
每一个线程都是经过某个特定Thread对象所对应的方法run()来完成其操做的,方法run()称为线程体。经过调用Thread类的start()方法来启动一个线程。
start()方法来启动一个线程,真正实现了多线程运行。这时无需等待run方法体代码执行完毕,能够直接继续执行下面的代码;
这时此线程是处于就绪状态, 并无运行。 而后经过此Thread类调用方法run()来完成其运行状态, 这里方法run()称为线程体,它包含了要执行的这个线程的内容, Run方法运行结束, 此线程终止。而后CPU再调度其它线程。
run()方法是在本线程里的,只是线程里的一个函数,而不是多线程的。
若是直接调用run(),其实就至关因而调用了一个普通函数而已,直接待用run()方法必须等待run()方法执行完毕才能执行下面的代码,因此执行路径仍是只有一条,根本就没有线程的特征,因此在多线程执行时要使用start()方法而不是run()方法。
有点深的问题了,也看出一个Java程序员学习知识的广度。
这实际上是颇有用的一个特性,由于多线程相比单线程更难、更复杂的一个重要缘由就是由于多线程充满着未知性,某条线程是否执行了?某条线程执行了多久?某条线程执行的时候咱们指望的数据是否已经赋值完毕?没法得知,咱们能作的只是等待这条多线程的任务执行完毕而已。而Callable+Future/FutureTask却能够获取多线程运行的结果,能够在等待时间太长没获取到须要的数据的状况下取消该线程的任务,真的是很是有用。
volatile关键字的做用主要有两个:
(1)多线程主要围绕可见性和原子性两个特性而展开,使用volatile关键字修饰的变量,保证了其在多线程之间的可见性,即每次读取到volatile变量,必定是最新的数据
(2)代码底层执行不像咱们看到的高级语言—-Java程序这么简单,它的执行是Java代码–>字节码–>根据字节码执行对应的C/C++代码–>C/C++代码被编译成汇编语言–>和硬件电路交互,现实中,为了获取更好的性能JVM可能会对指令进行重排序,多线程下可能会出现一些意想不到的问题。使用volatile则会对禁止语义重排序,固然这也必定程度上下降了代码执行效率
从实践角度而言,volatile的一个重要做用就是和CAS结合,保证了原子性,详细的能够参见java.util.concurrent.atomic包下的类,好比AtomicInteger。
死循环、死锁、阻塞、页面打开慢等问题,打线程dump是最好的解决问题的途径。所谓线程dump也就是线程堆栈,获取到线程堆栈有两步:
(1)获取到线程的pid,能够经过使用jps命令,在Linux环境下还可使用ps -ef | grep java
(2)打印线程堆栈,能够经过使用jstack pid命令,在Linux环境下还可使用kill -3 pid
另外提一点,Thread类提供了一个getStackTrace()方法也能够用于获取线程堆栈。这是一个实例方法,所以此方法是和具体线程实例绑定的,每次获取获取到的是具体某个线程当前运行的堆栈,
虚拟机性能监控与故障处理工具 详解
http://www.ymq.io/2017/08/01/jvm-4/
前面两种能够归结为一类:无返回值,缘由很简单,经过重写run方法,run方式的返回值是void,因此没有办法返回结果
后面两种能够归结成一类:有返回值,经过Callable接口,就要实现call方法,这个方法的返回值是Object,因此返回的结果能够放在Object对象中
public class ThreadDemo03 { public static void main(String[] args) { // TODO Auto-generated method stub Callable<Object> oneCallable = new Tickets<Object>(); FutureTask<Object> oneTask = new FutureTask<Object>(oneCallable); Thread t = new Thread(oneTask); System.out.println(Thread.currentThread().getName()); t.start(); } } class Tickets<Object> implements Callable<Object>{ //重写call方法 @Override public Object call() throws Exception { // TODO Auto-generated method stub System.out.println(Thread.currentThread().getName()+"-->我是经过实现Callable接口经过FutureTask包装器来实现的线程"); return null; } }
程序运行结果:
main
Thread-0–>我是经过实现Callable接口经过FutureTask包装器来实现的线程
public class ThreadDemo05{ private static int POOL_NUM = 10; //线程池数量 public static void main(String[] args) throws InterruptedException { // TODO Auto-generated method stub ExecutorService executorService = Executors.newFixedThreadPool(5); for(int i = 0; i<POOL_NUM; i++) { RunnableThread thread = new RunnableThread(); //Thread.sleep(1000); executorService.execute(thread); } //关闭线程池 executorService.shutdown(); } } class RunnableThread implements Runnable { @Override public void run() { System.out.println("经过线程池方式建立的线程:" + Thread.currentThread().getName() + " "); } }
程序运行结果:
经过线程池方式建立的线程:pool-1-thread-3 经过线程池方式建立的线程:pool-1-thread-4 经过线程池方式建立的线程:pool-1-thread-1 经过线程池方式建立的线程:pool-1-thread-5 经过线程池方式建立的线程:pool-1-thread-2 经过线程池方式建立的线程:pool-1-thread-5 经过线程池方式建立的线程:pool-1-thread-1 经过线程池方式建立的线程:pool-1-thread-4 经过线程池方式建立的线程:pool-1-thread-3 经过线程池方式建立的线程:pool-1-thread-2
ExecutorService、Callable都是属于Executor框架。返回结果的线程是在JDK1.5中引入的新特征,还有Future接口也是属于这个框架,有了这种特征获得返回值就很方便了。
经过分析能够知道,他一样也是实现了Callable接口,实现了Call方法,因此有返回值。这也就是正好符合了前面所说的两种分类
执行Callable任务后,能够获取一个Future的对象,在该对象上调用get就能够获取到Callable任务返回的Object了。get方法是阻塞的,即:线程无返回结果,get方法会一直等待。
newCachedThreadPool建立一个可缓存线程池,若是线程池长度超过处理须要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 建立一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 建立一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 建立一个单线程化的线程池,它只会用惟一的工做线程来执行任务,保证全部任务按照指定顺序(FIFO, LIFO, 优先级)执行。
Java多线程实现的四种方式
http://blog.csdn.net/u011480603/article/details/75332435
这是我在并发编程网上看到的一个问题,把这个问题放在最后一个,但愿每一个人都能看到而且思考一下,由于这个问题很是好、很是实际、很是专业。关于这个问题,我的见解是:
(1)高并发、任务执行时间短的业务,线程池线程数能够设置为CPU核数+1,减小线程上下文的切换
(2)并发不高、任务执行时间长的业务要区分开看:
a)假如是业务时间长集中在IO操做上,也就是IO密集型的任务,由于IO操做并不占用CPU,因此不要让全部的CPU闲下来,能够加大线程池中的线程数目,让CPU处理更多的业务
b)假如是业务时间长集中在计算操做上,也就是计算密集型任务,这个就没办法了,和(1)同样吧,线程池中的线程数设置得少一些,减小线程上下文的切换
(3)并发高、业务执行时间长,解决这种类型任务的关键不在于线程池而在于总体架构的设计,看看这些业务里面某些数据是否能作缓存是第一步,增长服务器是第二步,至于线程池的设置,设置参考(2)。最后,业务执行时间长的问题,也可能须要分析一下,看看能不能使用中间件对任务进行拆分和解耦。
若是你使用的LinkedBlockingQueue,也就是无界队列的话,不要紧,继续添加任务到阻塞队列中等待执行,由于LinkedBlockingQueue能够近乎认为是一个无穷大的队列,能够无限存听任务;若是你使用的是有界队列比方说ArrayBlockingQueue的话,任务首先会被添加到ArrayBlockingQueue中,ArrayBlockingQueue满了,则会使用拒绝策略RejectedExecutionHandler处理满了的任务,默认是AbortPolicy。
方法锁(synchronized修饰方法时)
经过在方法声明中加入 synchronized关键字来声明 synchronized 方法。
synchronized 方法控制对类成员变量的访问:
每一个类实例对应一把锁,每一个 synchronized 方法都必须得到调用该方法的类实例的锁方能执行,不然所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能得到该锁,从新进入可执行状态。这种机制确保了同一时刻对于每个类实例,其全部声明为 synchronized 的成员函数中至多只有一个处于可执行状态,从而有效避免了类成员变量的访问冲突。
对象锁(synchronized修饰方法或代码块)
当一个对象中有synchronized method或synchronized block的时候调用此对象的同步方法或进入其同步区域时,就必须先得到对象锁。若是此对象的对象锁已被其余调用者占用,则须要等待此锁被释放。(方法锁也是对象锁)
java的全部对象都含有1个互斥锁,这个锁由JVM自动获取和释放。线程进入synchronized方法的时候获取该对象的锁,固然若是已经有线程获取了这个对象的锁,那么当前线程会等待;synchronized方法正常返回或者抛异常而终止,JVM会自动释放对象锁。这里也体现了用synchronized来加锁的1个好处,方法抛异常的时候,锁仍然能够由JVM来自动释放。
类锁(synchronized 修饰静态的方法或代码块)
因为一个class不论被实例化多少次,其中的静态方法和静态变量在内存中都只有一份。因此,一旦一个静态的方法被申明为synchronized。此类全部的实例化对象在调用此方法,共用同一把锁,咱们称之为类锁。
对象锁是用来控制实例方法之间的同步,类锁是用来控制静态方法(或静态变量互斥体)之间的同步
这个问题坑了不少Java程序员,若你能想到锁是否释放这条线索来回答还有点但愿答对。不管你的同步块是正常仍是异常退出的,里面的线程都会释放锁,因此对比锁接口我更喜欢同步块,由于它不用我花费精力去释放锁,该功能能够在finally block里释放锁实现。
并发(concurrency)和并行(parallellism)是:
因此并发编程的目标是充分的利用处理器的每个核,以达到最高的处理性能。
根据volatile特性来用1000个线程不断的累加数字,每次累加1个,到最后值确不是1000.
volatile只能保证你数据的可见性(获取到的是最新的数据,不能保证原子性,说白了,volatile跟原子性不要紧
import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; public class Counter { public static AtomicInteger count = new AtomicInteger();//原子操做 public static CountDownLatch latch= new CountDownLatch(1000);//线程协做处理 public static volatile int countNum = 0;//volatile 只能保证可见性,不能保证原子性 public static int synNum = 0;//同步处理计算 public static void inc() { try { Thread.sleep(1); } catch (InterruptedException e) { } countNum++; int c = count.addAndGet(1); add(); System.out.println(Thread.currentThread().getName() + "------>" + c); } public static synchronized void add(){ synNum++; } public static void main(String[] args) { //同时启动1000个线程,去进行i++计算,看看实际结果 for (int i = 0; i < 1000; i++) { new Thread(new Runnable() { @Override public void run() { Counter.inc(); latch.countDown(); } },"thread" + i).start(); } try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()); System.out.println("运行结果:Counter.count=" + count.get() + ",,," + countNum + ",,," + synNum); }
count.get()是AtomicInteger的值;
count是用volatile修饰的变量的值;
synNum是用synchronized修饰的值;
用synchronized和AtomicInteger能保证是你想要的数据,volatile并不能保证。
第一次运行结果:
main
运行结果:Counter.count=1000,,,991,,,1000
第二次运行结果:
main
运行结果:Counter.count=1000,,,998,,,1000
第三次运行结果:
main
运行结果:Counter.count=1000,,,993,,,1000
可见,就算用了volatile,也不能保证数据是你想要的数据,volatile只能保证你数据的可见性(获取到的是最新的数据,不能保证原子性,说白了,volatile跟原子性不要紧)
要保证原子性,对数据的累加,能够用AtomicInteger类;
也能够用synchronized来保证数据的一致性
若是这个异常没有被捕获的话,这个线程就中止执行了。另外重要的一点是:若是这个线程持有某个某个对象的监视器,那么这个对象监视器会被当即释放
经过在线程之间共享对象就能够了,而后经过wait/notify/notifyAll、await/signal/signalAll进行唤起和等待,比方说阻塞队列BlockingQueue就是为线程之间共享数据而设计的
这个问题很理论,可是很重要:
(1)经过平衡生产者的生产能力和消费者的消费能力来提高整个系统的运行效率,这是生产者消费者模型最重要的做用
(2)解耦,这是生产者消费者模型附带的做用,解耦意味着生产者和消费者之间的联系少,联系越少越能够独自发展而不须要收到相互的制约
若是线程是由于调用了wait()、sleep()或者join()方法而致使的阻塞,能够中断线程,而且经过抛出InterruptedException来唤醒它;若是线程遇到了IO阻塞,无能为力,由于IO是操做系统实现的,Java代码并无办法直接接触到操做系统。
抢占式。一个线程用完CPU以后,操做系统会根据线程优先级、线程饥饿状况等数据算出一个总的优先级并分配下一个时间片给某个线程执行。
老生常谈的问题了,首先要说的是单例模式的线程安全意味着:某个类的实例在多线程环境下只会被建立一次出来。单例模式有不少种的写法,我总结一下:
(1)饿汉式单例模式的写法:线程安全
(2)懒汉式单例模式的写法:非线程安全
(3)双检锁单例模式的写法:线程安全
这是一个很是刁钻和狡猾的问题。请记住:线程类的构造方法、静态块是被new这个线程类所在的线程所调用的,而run方法里面的代码才是被线程自身所调用的。
若是说上面的说法让你感到困惑,那么我举个例子,假设Thread2中new了Thread1,main函数中new了Thread2,那么:
(1)Thread2的构造方法、静态块是main线程调用的,Thread2的run()方法是Thread2本身调用的
(2)Thread1的构造方法、静态块是Thread2调用的,Thread1的run()方法是Thread1本身调用的
同步块是更好的选择,由于它不会锁住整个对象(固然也可让它锁住整个对象)。同步方法会锁住整个对象,哪怕这个类中有多个不相关联的同步块,这一般会致使他们中止执行并须要等待得到这个对象上的锁。
public class SynObj{
public synchronized void showA(){ System.out.println("showA.."); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } public void showB(){ synchronized (this) { System.out.println("showB.."); } }
}
所谓死锁:是指两个或两个以上的进程在执行过程当中,因争夺资源而形成的一种互相等待的现象,若无外力做用,它们都将没法推动下去。此时称系统处于死锁
通俗地讲就是两个或多个进程被无限期地阻塞、相互等待的一种状态
死锁产生的缘由?
1.因竞争资源发生死锁 现象:系统中供多个进程共享的资源的数目不足以知足所有进程的须要时,就会引发对诸资源的竞争而发生死锁现象
2.进程推动顺序不当发生死锁
死锁的四个必要条件:
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之
一不知足,就不会发生死锁。
检测死锁
有两个容器,一个用于保存线程正在请求的锁,一个用于保存线程已经持有的锁。每次加锁以前都会作以下检测:
死锁的解除与预防:
理解了死锁的缘由,尤为是产生死锁的四个必要条件,就能够最大可能地避免、预防和
解除死锁。
因此,在系统设计、进程调度等方面注意如何不让这四个必要条件成立,如何确
定资源的合理分配算法,避免进程永久占据系统资源。
此外,也要防止进程在处于等待状态的状况下占用资源。所以,对资源的分配要给予合理的规划。
http://blog.csdn.net/yyf_it/a...
http://blog.csdn.net/yyf_it/article/details/52412071