java线程池说明 http://www.oschina.net/question/565065_86540php
java中断机制 http://ifeve.com/java-interrupt-mechanism/java
Ask、如今有T一、T二、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行?linux
join方法程序员
若是一个线程A执行了thread.join()语句,其含义是当前线程A等待thread线程终止后才从thread.join()返回面试
join有两个超时特性的方法,若是在超时时间内thread尚未执行结束,则从该超时方法返回算法
Ask、在Java中Lock接口比synchronized块的优点是什么?你须要实现一个高效的缓存,它容许多个用户读,但只容许一个用户写,以此来保持它的完整性,你会怎样去实现它?编程
java se 5以后,并发包中新增了Lock接口用来实现锁功能,提供与synchronized关键字相似的同步功能,只是在使用时须要显式地获取和释放锁。虽然缺乏了synchronized的便捷性,单拥有了锁获取与释放的可操做性、可中断的获取锁以及超时获取锁等多种synchronized不具有的特性。windows
lock接口在多线程和并发编程中最大的优点是它们为读和写分别提供了锁,它能知足你写像ConcurrentHashMap这样的高性能数据结构和有条件的阻塞。数组
咱们能够分析一下jdk8中的读写锁的源码缓存
在这以前,咱们须要了解一下AbstractQueuedSynchronizer队列同步器,是用来构建锁或者其余同步组件的基础框架,它使用一个int成员变量表示同步状态,经过内置的FIFO队列完成资源获取线程的排队工做。
同步器的主要使用方式是继承,子类经过继承同步器并实现它的抽象方法来管理同步状态,在抽象方法的实现过程当中免不了要对同步状态进行更改,这时就须要使用同步器提供的3个方法来进行操做,getState()、setState(int newState)、compareAndSetState(int expect,int update),由于它们能保证状态的改变是安全的。
同步器通常是做为子类的内部静态类(待会儿详见读写锁实现),同步器自身没有实现任何同步接口,仅仅定义了若干同步状态获取和释放的方法来供自定义同步组件使用,同步器既能够支持独占式地获取同步状态,也能够支持共享式地获取同步状态,这样就能够方便实现不一样类型的同步组件(ReentrantLock、ReentrantReadWriteLock和CountDownLatch等)。
同步器有一些能够重写的方法,好比 tryAcquire独占式获取同步状态 tryRealease独占式释放同步状态 tryAcquireShared共享式获取同步状态 tryRealeaseShared共享式释放同步状态 isHeldExclusively 是否被当前线程所独占
还提供了一些模板方法,独占式获取同步状态、独占式释放同步状态、响应中断的、响应超时的等,还有共享式的一系列模板方法。
这些都是不一样类型同步组件的基础。
咱们来看一下ReentrantReadWriteLock的源码
public class ReentrantReadWriteLock
implements ReadWriteLock, java.io.Serializable {
private static final long serialVersionUID = -6992448646407690164L;
/** Inner class providing readlock */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** Inner class providing writelock */
private final ReentrantReadWriteLock.WriteLock writerLock;
/** Performs all synchronization mechanics */
final Sync sync;
读写锁成员有readerLock、writeLock以及sync,都是ReentrantReadWriteLock的内部类
sync就是继承实现了同步器中的 tryAcquire、tryRealease、tryAcquireShared、tryRealeaseShared等方法,分别用于readerLock、writeLock使用
abstract static class Sync extends AbstractQueuedSynchronizer
protected final boolean tryRelease(int releases) {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
int nextc = getState() - releases;
boolean free = exclusiveCount(nextc) == 0;
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}
以readLock为例
public static class ReadLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = -5992448646407690164L;
private final Sync sync;
protected ReadLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
public void lock() {
sync.acquireShared(1);
}
public void lockInterruptibly() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public boolean tryLock() {
return sync.tryReadLock();
}
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
public void unlock() {
sync.releaseShared(1);
}
public Condition newCondition() {
throw new UnsupportedOperationException();
}
public String toString() {
int r = sync.getReadLockCount();
return super.toString() +
"[Read locks = " + r + "]";
}
}
以上,咱们能够了解,要实现高效缓存,多人读,一人写,就能够用ReentrantReadWriteLock,读取用读锁,写用写锁
既然读的时候能够多人访问,那么为何还要加读锁呢?固然要加锁了,不然在写时去读,可能不正确-(写的时候不能去读)
读写锁的做用为,当咱们加上写锁时,其余线程被阻塞,只有一个写操做在执行,当咱们加上读锁后,它是不会限制多个读线程去访问的。也就是get和put之间是互斥的,put与任何线程均为互斥,可是get与get线程间并非互斥的。其实加读写锁的目的是同一把锁的读锁既能够与写锁互斥,读锁之间还能够共享。
Ask、在java中wait和sleep方法的不一样?
sleep()方法,属于Thread类中的。而wait()方法,则是属于Object类中的。
在调用sleep()方法的过程当中,线程不会释放对象锁。
而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备
Wait一般被用于线程间交互,sleep一般被用于暂停执行
Ask、用Java实现阻塞队列
阻塞队列是一个支持两个附加操做的队列,即支持阻塞的插入和移除方法
java最新jdk中目前有以下几种阻塞队列
ArrayBlockingQueue 一个由数组结构组成的有界阻塞队列,按照FIFO原则对元素进行排序
LinkedBlockingQueue 一个由链表结构组成的有界阻塞队列,FIFO
PriorityBlockingQueue 一个支持优先级排序的无界阻塞队列,能够自定义类实现compareTo()方法指定元素排序规则,或者促使或PriorityBlockingQueue时,指定构造参数Comparator来对元素进行排序
DelayQueue 一个使用优先级队列实现的无界阻塞队列,使用PriorityQueue实现,队列中元素必须实现Delayed接口,建立元素时能够指定多久才能从队列中获取当前元素,只有在延迟期满才能从队列中提取元素。用于缓存系统的设计(保存缓存元素的有效期)、定时任务调度(保存当天将会执行的任务以及执行时间,一旦从DelayQueue中获取到任务就开始执行。TimerQueue就是使用DelayQueue实现的)
SynchronousQueue 一个不存储元素的阻塞队列,每一个put操做必须等待一个take操做,不然不能继续添加元素。支持公平访问队列,默认状况下线程采用非公平策略访问队列,构造时能够经过构造参数指定公平访问
LinkedTransferQueue 一个由链表结构组成的无界阻塞队列,多了tryTransfer和transfer方法。
transfer方法,若是当前有消费者正在等待接收元素,transfer方法能够把生产者传入的元素马上transfer给消费者。若是没有消费者在等待,transfer方法会将元素存放在队列的tail节点,并等到该元素被消费者消费了才返回。
tryTransfer方法,用来试探生产者传入的元素是否能直接传给消费者
LinkedBlockingDeque 一个由链表结构组成的双向阻塞队列。队列两端均可以插入和移除元素,双向队列由于多了一个操做队列的入口,在多线程同时入队时,也就减小了一半的竞争。初始化时能够设置容量防止其过分膨胀。
本身实现阻塞队列时,能够用Object的wait()方法、notify()方法或者Lock中Condition的await()、signal()方法,他们均可以实现等待/通知模式
wait()和notify()必须在synchronized的代码块中使用 由于只有在获取当前对象的锁时才能进行这两个操做 不然会报异常
而await()和signal()通常与Lock()配合使用(Condition con = lock.newCondition(); lock.lock();con.await() ),也必须先lock.lock()或者lock.lockInterruptibly()获取锁以后,才能await或者signal,不然会报异常
Ask、用Java写代码来解决生产者——消费者问题
与阻塞队列相似,也能够直接用阻塞队列来实现
Ask、什么是原子操做,Java中的原子操做是什么?
原子操做的描述是: 多个线程执行一个操做时,其中任何一个线程要么彻底执行完此操做,要么没有执行此操做的任何步骤 ,那么这个操做就是原子的。
Java中的原子操做包括:
1)除long和double以外的基本类型的赋值操做
2)全部引用reference的赋值操做
3)java.concurrent.Atomic.* 包中全部类的一切操做。
可是java对long和double的赋值操做是非原子操做!!long和double占用的字节数都是8,也就是64bits。在32位操做系统上对64位的数据的读写要分两步完成,每一步取32位数据。这样对double和long的赋值操做就会有问题:若是有两个线程同时写一个变量内存,一个进程写低32位,而另外一个写高32位,这样将致使获取的64位数据是失效的数据。所以须要使用volatile关键字来防止此类现象。volatile自己不保证获取和设置操做的原子性,仅仅保持修改的可见性。可是java的内存模型保证声明为volatile的long和double变量的get和set操做是原子的,具体后面再分析。(from http://www.iteye.com/topic/213794)
jdk1.5开始提供atomic包,里面有13个原子操做类,4中类型,基本都是使用Unsafe实现的包装类。Unsafe是jni方法
原子更新基本类型类 AtomicBoolean 原子更新布尔类型 AtomicInteger 原子更新整型 AtomicLong 原子更新长整型
原子更新数组 AtomicIntegerArray 原子更新整型数组里的元素 AtomicLongArray 原子更新长整型数组里的元素 AtomicReferenceArray 原子更新引用类型数组里的元素
原子更新引用类型 AtomicReference 原子更新引用类型 AtomicReferenceFieldUpdater 原子更新引用类型里的字段 AtomicMarkableReference 原则更新带有标记位的引用类型
原子更新字段类 AtomicIntegerFieldUpdater 原子更新整型的字段的更新器 AtomicLongFieldUpdater 原子更新长整型的字段的更新器 AtomicStampedUpdater 原子更新带有版本号的引用类型
Ask、Java中的volatile关键是什么做用?怎样使用它?在Java中它跟synchronized方法有什么不一样?
若是一个字段被声明为volatile,java线程内存模型确保全部线程看到这个变量的值是一致的。保证了共享变量的可见性,当一个线程修改一个共享变量时,另一个线程能读到这个修改后的值。
JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存中,每一个线程都有一个私有的本地内存,本地内存中存储了该线程读/写共享变量的副本。(本地内存是JMM的一个抽象概念,并不真实存在,涵盖了缓存、写缓冲区、寄存器以及其余的硬件和编译器优化)
JMM经过控制主内存与每一个线程的本地内存之间的交互,来提供内存可见性。
执行程序时,为了提升性能,编译器和处理器经常会对指令作重排序。从java源代码到最终实际执行的指令序列,会分别经历3种重排序
1)编译器优化的重排序。编译器在不改变单线程程序语义的前提下,能够从新安排语句的执行顺序。
2)指令级并行的重排序。现代处理器采用指令级并行技术ILP,将多条指令重叠执行。若是不存在数据依赖性,处理器能够改变语句对应机器指令的执行顺序。
3)内存系统的重排序。因为处理器使用缓存和读/写缓冲区,使得加载和存储操做看上去多是在乱序执行。
对于编译器,JMM的编译器重排序规则会禁止特定类型的编译器重排序。对于处理器,JMM的处理器重排序规则会要求Java编译器在生成指令序列时,插入特定类型的内存屏障指令,来禁止特定类型的处理器重排序。
happens-before规则中有一条
volatile变量规则:对于一个volatile域的写,happens-before于任意后续对这个volatile域的读。
注:两个操做之间具备happens-before关系,并不意味着前一个操做要在后一个操做以前执行!仅仅要求前一个操做(执行的结果)对后一个操做可见,且前一个操做按顺序排在第二个操做以前。
即JMM容许的重排序能够发生在两个happens-before操做上。
理解volatile特性,能够把对volatile变量的单个读/写,当作是使用同一个锁对这些单个读/写操做作了同步。即get与set方法都加上synchronized
锁的happens-before规则保证释放锁和获取锁的两个线程之间的内存可见性。这意味着,对一个volatile变量的读,老是能看到任意线程对这个volatile变量最后的写入。
所得语义决定了临界区代码的执行具备原子性,这意味着,即便是64位的long型和double型变量,只要是volatile变量,对该变量的读/写就具备原子性。
简而言之,volatile具备以下特性
1)可见性,对一个volatile变量的读,老是能看到任意线程对这个volatile变量最后的写入
2)原子性,对任意单个volatile变量的读/写具备原子性,但相似于volatile++这种复合操做不具备原子性
jdk5开始,volatile写与锁的释放有相同内存语义,volatile读与锁的获取有相同内存语义。
volatile写的内存语义:当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存
volatile读的内存语义:当读一个valatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。
为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。(内存屏障,一组处理器指令,用于实现对内存操做的顺序限制)
每一个volatile写操做前面插入一个StoreStore屏障,保证在volatile写以前,其前面的全部普通写操做已经对任意处理器可见,由于StoreStore屏障会将上面全部的普通写在volatile写以前刷新到主内存。
每一个volatile写操做后面插入一个StoreLoad屏障,此屏障的做用是避免volatile写与后面可能有的volatile读/写操做重排序
每一个volatile读操做后面插入一个LoadLoad屏障,用来禁止处理器把上面的volatile读与下面的普通读重排序。
每一个volatile读操做后面插入一个LoadStore屏障,用来禁止处理器把上面的volatile读与下面的普通写重排序。
Ask、什么是竞争条件?你怎样发现和解决竞争?
多个线程或者进程在读写一个共享数据时结果依赖于它们执行的相对时间,这种情形叫作竞争。
竞争条件发生在当多个进程或者线程在读写数据时,其最终的的结果依赖于多个进程的指令执行顺序。
举一个例子:
咱们日常编程常常遇到的修改某个字段,这个操做在库存那里尤其突出,当两个单子同时修改库存的时候,这时就造成了竞争条件,若是不作同步处理,这里十有八九就是错误的了,由于若是两个单子同时出库,而出库的数量恰好大于库存数量,这里就会出现问题。(固然,还有几种状况会出现问题,咱们这里只是为了举一个竞争条件的例子)
再好比多个线程操做A帐户往B帐户转帐,若是没作同步处理,最后会发现,钱总帐对不上。
发现:有共享变量时会发生竞争
解决:进行同步处理,原子操做
Ask、你将如何使用thread dump?你将如何分析Thread dump?
在UNIX中你可使用kill -3,而后thread dump将会打印日志,在windows中你可使用”CTRL+Break”。很是简单和专业的线程面试问题,可是若是他问你怎样分析它,就会很棘手。
Ask、为何咱们调用start()方法时会执行run()方法,为何咱们不能直接调用run()方法?
这是另外一个很是经典的java多线程面试问题。这也是我刚开始写线程程序时候的困惑。如今这个问题一般在电话面试或者是在初中级Java面试的第一轮被问到。这个问题的回答应该是这样的,当你调用start()方法时你将建立新的线程,而且执行在run()方法里的代码。可是若是你直接调用run()方法,它不会建立新的线程也不会执行调用线程的代码。阅读我以前写的《start与run方法的区别》这篇文章来得到更多信息。
1) start:
用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码。经过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并无运行,一旦获得cpu时间片,就开始执行run()方法,这里方法 run()称为线程体,它包含了要执行的这个线程的内容,Run方法运行结束,此线程随即终止。
2) run:
run()方法只是类的一个普通方法而已,若是直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径仍是只有一条,仍是要顺序执行,仍是要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的。总结:调用start方法方可启动线程,而run方法只是thread的一个普通方法调用,仍是在主线程里执行。这两个方法应该都比较熟悉,把须要并行处理的代码放在run()方法中,start()方法启动线程将自动调用 run()方法,这是由jvm的内存机制规定的。而且run()方法必须是public访问权限,返回值类型为void.。
Ask、Java中你怎样唤醒一个阻塞的线程?
这是个关于线程和阻塞的棘手的问题,它有不少解决方法。若是线程遇到了IO阻塞,我而且不认为有一种方法能够停止线程。若是线程由于调用wait()、sleep()、或者join()方法而致使的阻塞,你能够中断线程,而且经过抛出InterruptedException来唤醒它。我以前写的《How to deal with blocking methods in java》有不少关于处理线程阻塞的信息。
Ask、在Java中CycliBarriar和CountdownLatch有什么区别?
这个线程问题主要用来检测你是否熟悉JDK5中的并发包。这两个的区别是CyclicBarrier能够重复使用已经经过的障碍,而CountdownLatch不能重复使用。
等待多线程完成的CountdownLatch
容许一个或多个线程等待其余线程完成操做。JDK1.5以后的并发包中提供的CountdownLatch能够实现join的功能,而且比join的功能更多。
好比定义一个CountDownLatch c = new CountDownLatch(n);
n能够表明n个线程,每一个线程执行的最后加上c.countDown(),n会减一。
另外一个线程须要等待这n个线程执行结束,就加上c.await(),则该线程阻塞,直到n变成0,即n个线程都执行完毕。若是不想让该线程阻塞太长时间,则能够经过await(long time,TimeUnit unit)方法指定时间,等待特定时间后,就再也不阻塞。
一个线程调用countDown方法happens-before另一个线程调用await方法
注:计数器必须大于等于0,只是等于0时,计数器就是零,调用await方法时不会阻塞当前线程。CountDownLatch不可能从新初始化或者修改内部计数器的值。
同步屏障CyclicBarrier
字面意思是可循环(Cyclic)使用的屏障(Barrier)。它要作的事情是,让一组线程到达一个屏障(也能够叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,全部被屏障拦截的线程才会继续进行。
CyclicBarrier默认的构造方法CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每一个线程调用await方法告诉CyclicBarrier我已经到达了屏障,而后当前线程阻塞。
CyclicBarrier还提供一个更高级的构造函数CyclicBarrier(int parties,Runnable barrierAction),用于在线程到达屏障时,优先执行barrierAction就方便处理更复杂的业务场景。好比分别处理每一个文件中的数据,最后经过barrierAction来对数据进行汇总。
区别:CountDownLatch的计数器只能使用一次,而CyclicBarrier得计数器可使用reset()方法重置,因此,CyclicBarrier能处理更为复杂的业务场景。好比,若是计算发生错误,能够重置计数器,并让线程从新执行一次。
CyclicBarrier还提供其余有用的方法,用来得到阻塞的线程数量以及了解阻塞的线程是否被中断等。
Ask、 什么是不可变对象,它对写并发应用有什么帮助?
另外一个多线程经典面试问题,并不直接跟线程有关,但间接帮助不少。这个java面试问题能够变的很是棘手,若是他要求你写一个不可变对象,或者问你为何String是不可变的。
不可变对象(immutable objects),后面文章我将使用immutable objects来代替不可变对象!
那么什么是immutable objects?什么又是mutable Objects呢?
immutable Objects就是那些一旦被建立,它们的状态就不能被改变的Objects,每次对他们的改变都是产生了新的immutable的对象,而mutable Objects就是那些建立后,状态能够被改变的Objects.
举个例子:String和StringBuilder,String是immutable的,每次对于String对象的修改都将产生一个新的String对象,而原来的对象保持不变,而StringBuilder是mutable,由于每次对于它的对象的修改都做用于该对象自己,并无产生新的对象。
但有的时候String的immutable特性也会引发安全问题,这就是密码应该存放在字符数组中而不是String中的缘由!
immutable objects 比传统的mutable对象在多线程应用中更具备优点,它不只可以保证对象的状态不被改变,并且还能够不使用锁机制就能被其余线程共享。
实际上JDK自己就自带了一些immutable类,好比String,Integer以及其余包装类。为何说String是immutable的呢?好比:java.lang.String 的trim,uppercase,substring等方法,它们返回的都是新的String对象,而并非直接修改原来的对象。
如何在Java中写出Immutable的类?
要写出这样的类,须要遵循如下几个原则:
1)immutable对象的状态在建立以后就不能发生改变,任何对它的改变都应该产生一个新的对象。
2)Immutable类的全部的属性都应该是final的。
3)对象必须被正确的建立,好比:对象引用在对象建立过程当中不能泄露(leak)。
4)对象应该是final的,以此来限制子类继承父类,以免子类改变了父类的immutable特性。
5)若是类中包含mutable类对象,那么返回给客户端的时候,返回该对象的一个拷贝,而不是该对象自己(该条能够归为第一条中的一个特例)
固然不彻底遵照上面的原则也可以建立immutable的类,好比String的hashcode就不是final的,但它能保证每次调用它的值都是一致的,不管你多少次计算这个值,它都是一致的,由于这些值的是经过计算final的属性得来的!
另外,若是你的Java类中存在不少可选的和强制性的字段,你也可使用建造者模式来建立一个immutable的类。
下面是一个例子:
咱们为类添加了final修饰,从而避免由于继承和多态引发的immutable风险。
上面是最简单的一种实现immutable类的方式,能够看到它的全部属性都是final的。
有时候你要实现的immutable类中可能包含mutable的类,好比java.util.Date,尽管你将其设置成了final的,可是它的值仍是能够被修改的,为了不这个问题,咱们建议返回给用户该对象的一个拷贝,这也是Java的最佳实践之一。下面是一个建立包含mutable类对象的immutable类的例子:
public final class ImmutableReminder{
private final Date remindingDate;
public ImmutableReminder (Date remindingDate) {
if(remindingDate.getTime() < System.currentTimeMillis()){
throw new IllegalArgumentException("Can not set reminder” + “ for past time: " + remindingDate);
}
this.remindingDate = new Date(remindingDate.getTime());
}
public Date getRemindingDate() {
return (Date) remindingDate.clone();
}
}
上面的getRemindingDate()方法能够看到,返回给用户的是类中的remindingDate属性的一个拷贝,这样的话若是别人经过getRemindingDate()方法得到了一个Date对象,而后修改了这个Date对象的值,那么这个值的修改将不会致使ImmutableReminder类对象中remindingDate值的修改。
使用Immutable类的好处:
1)Immutable对象是线程安全的,能够不用被synchronize就在并发环境中共享
2)Immutable对象简化了程序开发,由于它无需使用额外的锁机制就能够在线程间共享
3)Immutable对象提升了程序的性能,由于它减小了synchroinzed的使用
4)Immutable对象是能够被重复使用的,你能够将它们缓存起来重复使用,就像字符串字面量和整型数字同样。你可使用静态工厂方法来提供相似于valueOf()这样的方法,它能够从缓存中返回一个已经存在的Immutable对象,而不是从新建立一个。
immutable也有一个缺点就是会制造大量垃圾,因为他们不能被重用并且对于它们的使用就是”用“而后”扔“,字符串就是一个典型的例子,它会创造不少的垃圾,给垃圾收集带来很大的麻烦。固然这只是个极端的例子,合理的使用immutable对象会创造很大的价值。
看完以上的分析以后,屡次提到final
对于final域,编译器和处理器遵照两个重排序规则。
1)在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操做之间不能重排序。
2)初次读一个final域的对象的引用,与随后初次读这个final域,这两个操做之间不能重排序。
final域的重排序规则能够确保:在引用变量为任意线程可见以前,该引用变量指向的对象的final域已经在构造函数中被正确初始化了。其实,要获得这个效果,还须要一个保证:在构造函数内部,不能让这个被构造对象的引用为其余线程所见,也就是对象引用不能再构造函数中“溢出”。
由于在构造函数返回前,被构造对象的引用不能为其余线程所见,由于此时的final域可能尚未被初始化。在构造函数返回后,任意线程都将保证能看到final域正确初始化以后的值。
旧的内存模型中一个缺陷就是final域的值会改变,JDK5以后,加强了final的语义,增长了写和读重排序规则,能够为java程序员提供初始化安全保证:只要对象是正确构造的(被构造对象的引用在构造函数中没有溢出),那么不须要使用同步,就能够保证任意线程都能看到这个final域在构造函数中被初始化以后的值。
Ask、你在多线程环境中遇到的常见的问题是什么?你是怎么解决它的?
多线程和并发程序中常遇到的有Memory-interface、竞争条件、死锁、活锁和饥饿。问题是没有止境的,若是你弄错了,将很难发现和调试。这是大多数基于面试的,而不是基于实际应用的Java线程问题。
Ask、在java中绿色线程和本地线程区别?
1.什么是绿色线程?
绿色线程(Green Thread)是一个相对于操做系统线程(Native Thread)的概念。
操做系统线程(Native Thread)的意思就是,程序里面的线程会真正映射到操做系统的线程,线程的运行和调度都是由操做系统控制的
绿色线程(Green Thread)的意思是,程序里面的线程不会真正映射到操做系统的线程,而是由语言运行平台自身来调度。
当前版本的Python语言的线程就能够映射到操做系统线程。当前版本的Ruby语言的线程就属于绿色线程,没法映射到操做系统的线程,所以Ruby语言的线程的运行速度比较慢。
难道说,绿色线程要比操做系统线程要慢吗?固然不是这样。事实上,状况可能正好相反。Ruby是一个特殊的例子。线程调度器并非很成熟。
目前,线程的流行实现模型就是绿色线程。好比,stackless Python,就引入了更加轻量的绿色线程概念。在线程并发编程方面,不管是运行速度仍是并发负载上,都优于Python。
另外一个更著名的例子就是ErLang(爱立信公司开发的一种开源语言)。
ErLang的绿色线程概念很是完全。ErLang的线程不叫Thread,而是叫作Process。这很容易和进程混淆起来。这里要注意区分一下。
ErLang Process之间根本就不须要同步。由于ErLang语言的全部变量都是final的,不容许变量的值发生任何变化。所以根本就不须要同步。
final变量的另外一个好处就是,对象之间不可能出现交叉引用,不可能构成一种环状的关联,对象之间的关联都是单向的,树状的。所以,内存垃圾回收的算法效率也很是高。这就让ErLang可以达到Soft Real Time(软实时)的效果。这对于一门支持内存垃圾回收的语言来讲,可不是一件容易的事情
2.Java世界中的绿色线程
所谓绿色线程更多的是一个逻辑层面的概念,依赖于虚拟机来实现。操做系统对于虚拟机内部如何进行线程的切换并不清楚,从虚拟机外部来看,或者说站在操做系统的角度看,这些都是不可见的。能够把虚拟机看做一个应用程序,程序的代码自己来创建和维护针对不一样线程的堆栈,指令计数器和统计信息等等。这个时候的线程仅仅存在于用户级别的应用程序中,不须要进行系统级的调用,也不依赖于操做系统为线程提供的具体功能。绿色线程主要是为了移植方便,可是会增长虚拟机的复杂度。总的来讲,它把线程的实现对操做系统屏蔽,处在用户级别的实现这个层次上。绿色线程模型的一个特色就是多CPU也只能在某一时刻仅有一个线程运行。
本机线程简单地说就是和操做系统的线程对应,操做系统彻底了解虚拟机内部的线程。对于windows操做系统,一个java虚拟机的线程对应一个本地线程,java线程调度依赖于操做系统线程。对于solaris,复杂一些,由于后者自己提供了用户级和系统级两个层次的线程库。依赖于操做系统增长了对于平台的依赖性,可是虚拟机实现相对简单些,并且能够充分利用多CPU实现多线程同时处理。
Ask、线程与进程的区别?
Ask、 什么是多线程中的上下文切换?
即便是单核处理器也支持多线程执行代码,CPU经过给每一个线程分配CPU时间片来实现这个机制。时间片是CPU分配给各个线程的时间,由于时间片很是短,因此CPU经过不停地切换线程执行,让咱们感受多个线程同时执行,时间片通常为几十毫秒ms。
CPU经过时间片分配算法来循环执行任务,当前任务执行一个时间片以后会切换到下一个任务。可是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,能够再加载这个任务的状态。因此任务从保存到再加载的过程就是一次上下文切换。
Ask、死锁与活锁的区别,死锁与饥饿的区别?
避免死锁方法:一次封锁法和 顺序封锁法。
一次封锁法要求每一个事务必须一次将全部要使用的数据所有加锁,不然就不能继续执行。
一次封锁法虽然能够有效地防止死锁的发生,但也存在问题,一次就将之后要用到的所有数据加锁,势必扩大了封锁的范围,从而下降了系统的并发度。
顺序封锁法是预先对数据对象规定一个封锁顺序,全部事务都按这个顺序实行封锁。
顺序封锁法能够有效地防止死锁,但也一样存在问题。事务的封锁请求能够随着事务的执行而动态地决定,很难事先肯定每个事务要封锁哪些对象,所以也就很难按规定的顺序去施加封锁。
Ask、Java中用到的线程调度算法是什么?
JVM调度的模式有两种:分时调度和抢占式调度。
分时调度是全部线程轮流得到CPU使用权,并平均分配每一个线程占用CPU的时间;
抢占式调度是根据线程的优先级别来获取CPU的使用权。JVM的线程调度模式采用了抢占式模式。既然是抢占调度,那么咱们就能经过设置优先级来“有限”的控制线程的运行顺序,注意“有限”一次。
Ask、 在Java中什么是线程调度?
一、首先简单说下java内存模型:Java中全部变量都储存在主存中,对于全部线程都是共享的(由于在同一进程中),每一个线程都有本身的工做内存或本地内存(Working Memory),工做内存中保存的是主存中某些变量的拷贝,线程对全部变量的操做都是在工做内存中进行,而线程之间没法相互直接访问,变量传递均须要经过主存完成,可是在程序内部能够互相调用(经过对象方法),全部线程间的通讯相对简单,速度也很快。
java内存模型
二、进程间的内部数据和状态都是相互彻底独立的,所以进程间通讯大多数状况是必须经过网络实现。线程自己的数据,一般只有寄存器数据,以及一个程序执行时使用的堆栈,因此线程的切换比进程切换的负担要小。
三、CPU对于各个线程的调度是随机的(分时调度),在Java程序中,JVM负责线程的调度。 线程调度是指按照特定的机制为多个线程分配CPU的使用权,也就是实际执行的时候是线程,所以CPU调度的最小单位是线程,而资源分配的最小单位是进程。
Ask、在线程中你怎么处理不可捕捉异常?
在java多线程程序中,全部线程都不容许抛出未捕获的checked exception,也就是说各个线程须要本身把本身的checked exception处理掉。这一点是经过java.lang.Runnable.run()方法声明(由于此方法声明上没有throw exception部分)进行了约束。可是线程依然有可能抛出unchecked exception,当此类异常跑抛出时,线程就会终结,而对于主线程和其余线程彻底不受影响,且彻底感知不到某个线程抛出的异常(也是说彻底没法catch到这个异常)。JVM的这种设计源自于这样一种理念:“线程是独立执行的代码片段,线程的问题应该由线程本身来解决,而不要委托到外部。”基于这样的设计理念,在Java中,线程方法的异常(不管是checked仍是unchecked exception),都应该在线程代码边界以内(run方法内)进行try catch并处理掉.
但若是线程确实没有本身try catch某个unchecked exception,而咱们又想在线程代码边界以外(run方法以外)来捕获和处理这个异常的话,java为咱们提供了一种线程内发生异常时可以在线程代码边界以外处理异常的回调机制,即Thread对象提供的setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)方法。
经过该方法给某个thread设置一个UncaughtExceptionHandler,能够确保在该线程出现异常时能经过回调UncaughtExceptionHandler接口的public void uncaughtException(Thread t, Throwable e) 方法来处理异常,这样的好处或者说目的是能够在线程代码边界以外(Thread的run()方法以外),有一个地方能处理未捕获异常。可是要特别明确的是:虽然是在回调方法中处理异常,但这个回调方法在执行时依然还在抛出异常的这个线程中!另外还要特别说明一点:若是线程是经过线程池建立,线程异常发生时UncaughtExceptionHandler接口不必定会当即回调。
比之上述方法,还有一种编程上的处理方式能够借鉴,即,有时候主线程的调用方可能只是想知道子线程执行过程当中发生过哪些异常,而不必定会处理或是当即处理,那么发起子线程的方法能够把子线程抛出的异常实例收集起来做为一个Exception的List返回给调用方,由调用方来根据异常状况决定如何应对。不过要特别注意的是,此时子线程早以终结。
Ask、 什么是线程组,为何在Java中不推荐使用?
Ask、为何使用Executor框架比使用应用建立和管理线程好?
Ask、 在Java中Executor和Executors的区别?
Ask、 如何在Windows和Linux上查找哪一个线程使用的CPU时间最长?
windows上面用任务管理器看,linux下能够用top 这个工具看。固然若是你要查找具体的进程,能够用ps命令,好比查找java:ps -ef |grep java