0. Java 中的 volatile 变量是什么html
Java 语言提供了一种稍弱的同步机制,即volatile
变量。可是volatile并不容易彻底被正确、完整的理解。通常来讲,volatile具有2条语义,或者说2个特性。第一是保证volatile修饰的变量对全部线程的可见性,这里的可见性是指当一条线程修改了该变量,新值对于其它线程来讲是当即能够得知的。而普通变量作不到这一点。java
第二条语义是禁止指令重排序优化,这条语义在JDK1.5才被修复。程序员
关于第一点:根据JMM,全部的变量存储在主内存,而每一个线程还有本身的工做内存,线程的工做内存保存该线程使用到的变量的主内存副本拷贝,线程对变量的操做在工做内存中进行,不能直接读写主内存的变量。在volatile可见性这一点上,普通变量作不到的缘由正因如此。好比,线程A修改了一个普通变量的值,而后向主内存进行回写,线程B在线程A回写完成后再从主内存读取,新变量才能对线程B可见。其实,按照虚拟机规范,volatile变量依然有工做内存的拷贝,要借助主内存来实现可见性。但因为volatile的特殊规则保证了新值能当即同步回主内存,以及每次使用从主内存刷新,以此保证了多线程操做volatile变量的可见性。web
关于第二点:先说指令重排序,指令重排序是指CPU采用了容许将多条指令不按规定顺序分开发送给相应的处理单元处理,但并非说任意重排,CPU须要正确处理指令依赖状况确保最终的正确结果,指令重排序是机器级的优化操做。那么为何volatile要禁止指令重排序呢,又是如何去作的。举例,DCL(双重检查加锁)的单例模式。volatile修饰后,代码中将会插入许多内存屏障指令保证处理器不发生乱序执行。同时因为Happens-before规则的保证,在刚才的例子中写操做会发生在后续的读操做以前。算法
除了以上2点,volatile还保证对于64位long和double的读取是原子性的。由于在JMM中容许虚拟机对未被volatile修饰的64位的long和double读写操做分为2次32位的操做来执行,这也就是所谓的long和double的非原子性协定。数据库
基于以上几点,咱们知道volatile虽然有这些语义和特性在并发的状况下仍然不能保证线程安全。大部分状况下仍然须要加锁。编程
除非是如下2种状况,1.运算结果不依赖变量的当前值,或者可以确保只有单一线程修改变量的值;2.变量不须要与其余的状态变量共同参与不变约束。设计模式
1. volatile简述数组
Java 语言提供了一种稍弱的同步机制,即volatile
变量.用来确保将变量的更新操做通知到其余线程,保证了新值能当即同步到主内存,以及每次使用前当即从主内存刷新。 当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的。volatile
修饰变量,每次被线程访问时强迫其从主内存重读该值,修改后再写回。保证读取的可见性,对其余线程当即可见。volatile
的另外一个语义是禁止指令重排序优化。可是volatile
并不保证原子性,也就不能保证线程安全。缓存
2. Java 中能建立 volatile 数组吗?
能,Java 中能够建立 volatile 类型数组,不过只是一个指向数组的引用,而不是整个数组。个人意思是,若是改变引用指向的数组,将会受到 volatile 的保护,可是若是多个线程同时改变数组的元素,volatile 就不能起到以前的保护做用了。
3. volatile 能使得一个非原子操做变成原子操做吗?
一个典型的例子是在类中有一个 long 类型的成员变量。若是你知道该成员变量会被多个线程访问,如计数器、价格等,你最好是将其设置为 volatile。为何?由于 Java 中读取 long 类型变量不是原子的,须要分红两步,若是一个线程正在修改该 long 变量的值,另外一个线程可能只能看到该值的一半(前 32 位)。可是对一个 volatile 型的 long 或 double 变量的读写是原子。
4. volatile 禁止指令重排序的底层原理指令重排序,是指CPU容许多条指令不按程序规定的顺序分开发送给相应电路单元处理。但并非说任意重排,CPU须要能正确处理指令依赖状况以正确的执行结果。volatile
禁止指令重排序是经过内存屏障实现的,指令重排序不能把后面的指令重排序到内存屏障以前。由内存屏障保证一致性。注:该条语义在JDK1.5才得以修复,这点也是JDK1.5以前没法经过双重检查加锁来实现单例模式的缘由。
5. volatile 类型变量提供什么保证?
volatile 变量提供有序性和可见性保证,例如,JVM 或者 JIT为了得到更好的性能会对语句重排序,可是 volatile 类型变量即便在没有同步块的状况下赋值也不会与其余语句重排序。 volatile 提供 happens-before 的保证,确保一个线程的修改能对其余线程是可见的。某些状况下,volatile 还能提供原子性,如读 64 位数据类型,像 long 和 double 都不是原子的,但 volatile 类型的 double 和 long 就是原子的。
volatile的使用场景:
6. volatile的性能
volatile变量的读操做性能消耗和普通变量差很少,可是写操做可能相对慢一些,由于它须要在本地代码中插入许多内存屏障指令以确保处理器不发生乱序执行。大多数状况下,volatile总开销比锁低,但咱们要注意volatile的语义可否知足使用场景。
7. 10 个线程和 2 个线程的同步代码,哪一个更容易写?
从写代码的角度来讲,二者的复杂度是相同的,由于同步代码与线程数量是相互独立的。可是同步策略的选择依赖于线程的数量,由于越多的线程意味着更大的竞争,因此你须要利用同步技术,如锁分离,这要求更复杂的代码和专业知识。
8. 你是如何调用 wait()方法的?使用 if 块仍是循环?为何?
wait() 方法应该在循环调用,由于当线程获取到 CPU 开始执行的时候,其余条件可能尚未知足,因此在处理前,循环检测条件是否知足会更好。下面是一段标准的使用 wait 和 notify 方法的代码:
// The standard idiom for using the wait method
synchronized (obj) {
while (condition does not hold)
obj.wait(); // (Releases lock, and reacquires on wakeup)
... // Perform action appropriate to condition
}
复制代码
参见 Effective Java 第 69 条,获取更多关于为何应该在循环中来调用 wait 方法的内容。
9. 什么是多线程环境下的伪共享(false sharing)?
伪共享是多线程系统(每一个处理器有本身的局部缓存)中一个众所周知的性能问题。伪共享发生在不一样处理器的上的线程对变量的修改依赖于相同的缓存行,以下图所示:
伪共享问题很难被发现,由于线程可能访问彻底不一样的全局变量,内存中却碰巧在很相近的位置上。如其余诸多的并发问题,避免伪共享的最基本方式是仔细审查代码,根据缓存行来调整你的数据结构。
10. 线程的run方法和start方法
run方法
只是thread类的一个普通方法,若直接调用程序中依然只有主线程这一个线程,还要顺序执行,依然要等待run方法体执行完毕才可执行下面的代码。
start方法
用start方法来启动线程,是真正实现了多线程。调用thread类的start方法来启动一个线程,此时线程处于就绪状态,一旦获得cpu时间片,就开始执行run方法。
11. ReadWriteLock(读写锁)
写写互斥 读写互斥 读读并发, 在读多写少的状况下能够提升效率。
12. resume(继续挂起的线程)和suspend(挂起线程)一块儿用
13. wait与notify、notifyall一块儿用
14. sleep与wait的异同点
15. 让一个线程中止执行
异常 - 中止执行休眠 - 中止执行阻塞 - 中止执行
16. ThreadLocal简介
16.1 ThreadLocal解决了变量并发访问的冲突问题
当使用ThreadLocal维护变量时,ThreadLocal为每一个使用该变量的线程提供独立的变量副本,每一个线程均可以独立地改变本身的副本,而不会影响其它线程所对应的副本,是线程隔离的。线程隔离的秘密在于ThreadLocalMap类(ThreadLocal的静态内部类)
16.2 与synchronized同步机制的比较
首先,它们都是为了解决多线程中相同变量访问冲突问题。不过,在同步机制中,要经过对象的锁机制保证同一时间只有一个线程访问该变量。该变量是线程共享的, 使用同步机制要求程序缜密地分析何时对该变量读写,何时须要锁定某个对象, 何时释放对象锁等复杂的问题,程序设计编写难度较大, 是一种“以时间换空间”的方式。而ThreadLocal采用了以“以空间换时间”的方式。
17. 线程局部变量原理
当使用ThreadLocal维护变量时,ThreadLocal为每一个使用该变量的线程提供独立的变量副本,每一个线程均可以独立地改变本身的副本,而不会影响其它线程所对应的副本,是线程隔离的。线程隔离的秘密在于ThreadLocalMap类(ThreadLocal的静态内部类)
线程局部变量是局限于线程内部的变量,属于线程自身全部,不在多个线程间共享。Java 提供 ThreadLocal 类来支持线程局部变量,是一种实现线程安全的方式。可是在管理环境下(如 web 服务器)使用线程局部变量的时候要特别当心,在这种状况下,工做线程的生命周期比任何应用变量的生命周期都要长。任何线程局部变量一旦在工做完成后没有释放,Java 应用就存在内存泄露的风险。
ThreadLocal的方法:void set(T value)、T get()以及T initialValue()。
ThreadLocal是如何为每一个线程建立变量的副本的:
首先,在每一个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。初始时,在Thread里面,threadLocals为空,当经过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,而且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。而后在当前线程里面,若是要使用副本变量,就能够经过get方法在threadLocals里面查找。
总结:
18. JDK提供的用于并发编程的同步器
Semaphore
Java并发库的Semaphore能够很轻松完成信号量控制,Semaphore能够控制某个资源可被同时访问的个数,经过 acquire() 获取一个许可,若是没有就等待,而 release() 释放一个许可。 CyclicBarrier
主要的方法就是一个:await()。await()方法每被调用一次,计数加一,并阻塞住当前线程。当计数等于定义的大小时,阻塞解除,全部在此CyclicBarrier上面阻塞的线程开始运行而且计数重置为0。 CountDownLatch
直译过来就是倒计数(CountDown)门闩(Latch)。倒计数不用说,门闩的意思顾名思义就是阻止前进。在这里就是指 CountDownLatch.await() 方法在倒计数为0以前会阻塞当前线程。 19. 什么是 Busy spin?咱们为何要使用它?
Busy spin 是一种在不释放 CPU 的基础上等待事件的技术。它常常用于避免丢失 CPU 缓存中的数据(若是线程先暂停,以后在其余CPU上运行就会丢失)。因此,若是你的工做要求低延迟,而且你的线程目前没有任何顺序,这样你就能够经过循环检测队列中的新消息来代替调用 sleep() 或 wait() 方法。它惟一的好处就是你只需等待很短的时间,如几微秒或几纳秒。LMAX 分布式框架是一个高性能线程间通讯的库,该库有一个 BusySpinWaitStrategy 类就是基于这个概念实现的,使用 busy spin 循环 EventProcessors 等待屏障。
20. Java 中怎么获取一份线程 dump 文件?
在 Linux 下,你能够经过命令 kill -3 PID (Java 进程的进程 ID)来获取 Java 应用的 dump 文件。在 Windows 下,你能够按下 Ctrl + Break 来获取。这样 JVM 就会将线程的 dump 文件打印到标准输出或错误文件中,它可能打印在控制台或者日志文件中,具体位置依赖应用的配置。
21. Swing 是线程安全的?
不是,Swing 不是线程安全的。你不能经过任何线程来更新 Swing 组件,如 JTable、JList 或 JPanel,事实上,它们只能经过 GUI 或 AWT 线程来更新。这就是为何 Swing 提供 invokeAndWait() 和 invokeLater() 方法来获取其余线程的 GUI 更新请求。这些方法将更新请求放入 AWT 的线程队列中,能够一直等待,也能够经过异步更新直接返回结果。
22. 用 wait-notify 写一段代码来解决生产者-消费者问题?
记住在同步块中调用 wait() 和 notify()方法,若是阻塞,经过循环来测试等待条件。
23. 用 Java 写一个线程安全的单例模式(Singleton)?
当咱们说线程安全时,意思是即便初始化是在多线程环境中,仍然能保证单个实例。Java 中,使用枚举做为单例类是最简单的方式来建立线程安全单例模式的方式。参见我整理的单例的文章6种单例模式的实现以及double check的剖析
24. Java 中,编写多线程程序的时候你会遵循哪些最佳实践?
这是我在写Java 并发程序的时候遵循的一些最佳实践:
a)给线程命名,这样能够帮助调试。
b)最小化同步的范围,而不是将整个方法同步,只对关键部分作同步。
c)若是能够,更偏向于使用 volatile 而不是 synchronized。
d)使用更高层次的并发工具,而不是使用 wait() 和 notify() 来实现线程间通讯,如 BlockingQueue,CountDownLatch 及 Semeaphore。
e)优先使用并发集合,而不是对集合进行同步。并发集合提供更好的可扩展性。
25. 说出至少 5 点在 Java 中使用线程的最佳实践。
这个问题与以前的问题相似,你可使用上面的答案。对线程来讲,你应该:
a)对线程命名
b)将线程和任务分离,使用线程池执行器来执行 Runnable 或 Callable。
c)使用线程池
26. 在多线程环境下,SimpleDateFormat 是线程安全的吗?
不是,很是不幸,DateFormat 的全部实现,包括 SimpleDateFormat 都不是线程安全的,所以你不该该在多线程序中使用,除非是在对外线程安全的环境中使用,如将 SimpleDateFormat 限制在 ThreadLocal 中。若是你不这么作,在解析或者格式化日期的时候,可能会获取到一个不正确的结果。所以,从日期、时间处理的全部实践来讲,我强力推荐 joda-time 库。
27. Happens-Before规则
按控制流顺序前后发生
一个unlock操做先行发生于后面对同一个锁的lock操做
对一个volatile变量的写操做先行发生于后面对这个变量的读操做
start方法先行发生于线程的每个动做
对线程的interrupt方法调用先行发生于被中断线程的代码检测到中断时间的发生
线程内的全部操做都先行发生于对此线程的终止检测
一个对象的初始化完成先行发生于它的finalize方法的开始
若是A先行发生于操做B,B先行发生于操做C,则A先行发生于操做C
28. 什么是线程
线程是操做系统可以进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运做单位。程序员能够经过它进行多处理器编程,可使用多线程对运算密集型任务提速。好比,若是一个线程完成一个任务要100 毫秒,那么用十个线程完成改任务只需 10 毫秒。Java在语言层面对多线程提供了很好的支持。
29. 线程和进程有什么区别
从概念上:
进程:一个程序对一个数据集的动态执行过程,是分配资源的基本单位。
线程:存在于进程内,是进程内的基本调度单位,共享进程的资源。
从执行过程当中来看:
进程:拥有独立的内存单元,而多个线程共享内存,从而提升了应用程序的运行效率。
线程:每个独立的线程,都有一个程序运行的入口、顺序执行序列、和程序的出口。可是线程不可以独立的执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
从逻辑角度来看:(重要区别)
多线程的意义在于一个应用程序中,有多个执行部分能够同时执行。可是,操做系统并无将多个线程看作多个独立的应用,来实现进程的调度和管理及资源分配。
简言之,一个程序至少有一个进程,一个进程至少有一个线程。进程是资源分配的基本单位,线程共享进程的资源。
30. 用 Runnable 仍是 Thread
Java 不支持类的多重继承,但容许你调用多个接口。因此若是你要继承其余类,固然是实现Runnable接口好了。
31. Java 中 Runnable 和 Callable 有什么不一样
Runnable和 Callable 都表明那些要在不一样的线程中执行的任务。Runnable 从 JDK1.0 开始就有了,Callable 是在 JDK1.5 增长的。它们的主要区别是 Callable 的 call () 方法能够返回值和抛出异常,而 Runnable 的 run ()方法没有这些功能。
32. Java 中 CyclicBarrier 和 CountDownLatch 有什么不一样
它们都是JUC下的类,CyclicBarrier 和 CountDownLatch 均可以用来让一组线程等待其它线程。区别在于CountdownLatch计数没法被重置。若是须要重置计数,请考虑使用 CyclicBarrier。
33. Java 内存模型是什么
Java 内存模型规定和指引 Java 程序在不一样的内存架构、CPU 和操做系统间有肯定性地行为。它在多线程的状况下尤为重要。Java内存模型对一个线程所作的变更能被其它线程可见提供了保证,它们之间是先行发生关系。这个关系定义了一些规则让程序员在并发编程时思路更清晰。
线程内的代码可以按前后顺序执行,这被称为程序次序规则。
对于同一个锁,一个解锁操做必定要发生在时间上后发生的另外一个锁定操做以前,也叫作管程锁定规则。
前一个对volatile的写操做在后一个volatile的读操做以前,也叫volatile变量规则。
一个线程内的任何操做必需在这个线程的 start ()调用以后,也叫做线程启动规则。
一个线程的全部操做都会在线程终止以前,线程终止规则。
一个对象的终结操做必需在这个对象构造完成以后,也叫对象终结规则。
a先行于b,b先行于c,传递性
34. 什么是线程安全?Vector 是一个线程安全类吗
若是你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。若是每次运行结果和单线程运行的结果是同样的,并且其余的变量的值也和预期的是同样的,就是线程安全的。一个线程安全的计数器类的同一个实例对象在被多个线程使用的状况下也不会出现计算失误。很显然你能够将集合类分红两组,线程安全和非线程安全的。Vector 是用同步方法来实现线程安全的,而和它类似的 ArrayList 不是线程安全的。
35. Java 中什么是竞态条件? 举个例子说明。
竞态条件会致使程序在并发状况下出现一些 bugs。多线程对一些资源的竞争的时候就会产生竞态条件,若是首先要执行的程序竞争失败排到后面执行了,那么整个程序就会出现一些不肯定的 bugs。这种 bugs 很难发现并且会重复出现,由于线程间的随机竞争。几类竞态条件check-and-act、读取-修改-写入、put-if-absent。
36. Java 中如何中止一个线程
当 run () 或者 call () 方法执行完的时候线程会自动结束,若是要手动结束一个线程,你能够用 volatile 布尔变量来退出 run ()方法的循环或者是取消任务来中断线程。其余情形:异常 - 中止执行 休眠 - 中止执行 阻塞 - 中止执行
37. 一个线程运行时发生异常会怎样
简单的说,若是异常没有被捕获该线程将会中止执行。Thread.UncaughtExceptionHandler 是用于处理未捕获异常形成线程忽然中断状况的一个内嵌接口。当一个未捕获异常将形成线程中断的时候 JVM 会使用 Thread.getUncaughtExceptionHandler ()来查询线程的 UncaughtExceptionHandler 并将线程和异常做为参数传递给 handler 的 uncaughtException ()方法进行处理。
38. 如何在两个线程间共享数据?
经过共享对象来实现这个目的,或者是使用像阻塞队列这样并发的数据结构
39. Java 中 notify 和 notifyAll 有什么区别
notify ()方法不能唤醒某个具体的线程,因此只有一个线程在等待的时候它才有用武之地。而 notifyAll ()唤醒全部线程并容许他们争夺锁确保了至少有一个线程能继续运行。
40. 为何 wait, notify 和 notifyAll 这些方法不在 thread 类里面
一个很明显的缘由是 JAVA 提供的锁是对象级的而不是线程级的。若是线程须要等待某些锁那么调用对象中的 wait ()方法就有意义了。若是 wait ()方法定义在 Thread 类中,线程正在等待的是哪一个锁就不明显了。简单的说,因为 wait,notify 和 notifyAll 都是锁级别的操做,因此把他们定义在 Object 类中由于锁属于对象。
41. 什么是 FutureTask?
在 Java 并发程序中 FutureTask 表示一个能够取消的异步运算。它有启动和取消运算、查询运算是否完成和取回运算结果等方法。只有当运算完成的时候结果才能取回,若是运算还没有完成 get 方法将会阻塞。一个 FutureTask 对象能够对调用了 Callable 和 Runnable 的对象进行包装,因为 FutureTask 也是调用了 Runnable 接口因此它能够提交给 Executor 来执行。
42. Java 中 interrupted 和 isInterruptedd 方法的区别
interrupted是静态方法,isInterruptedd是一个普通方法
若是当前线程被中断(没有抛出中断异常,不然中断状态就会被清除),你调用interrupted方法,第一次会返回true。而后,当前线程的中断状态被方法内部清除了。第二次调用时就会返回false。若是你刚开始一直调用isInterrupted,则会一直返回true,除非中间线程的中断状态被其余操做清除了。也就是说isInterrupted 只是简单的查询中断状态,不会对状态进行修改。
43. 为何 wait 和 notify 方法要在同步块中调用
若是不这么作,代码会抛出 IllegalMonitorStateException异常。还有一个缘由是为了不 wait 和 notify 之间产生竞态条件。
44. 为何你应该在循环中检查等待条件?
处于等待状态的线程可能会收到错误警报和伪唤醒,若是不在循环中检查等待条件,程序就会在没有知足结束条件的状况下退出。所以,当一个等待线程醒来时,不能认为它原来的等待状态仍然是有效的,在 notify 方法调用以后和等待线程醒来以前这段时间它可能会改变。这就是在循环中使用 wait 方法效果更好的缘由。
45. Java 中的同步集合与并发集合有什么区别
同步集合与并发集合都为多线程和并发提供了合适的线程安全的集合,不过并发集合的可扩展性更高。在 Java1.5 以前程序员们只有同步集合来用且在多线程并发的时候会致使争用,阻碍了系统的扩展性。Java1.5加入了并发集合像 ConcurrentHashMap,不只提供线程安全还用锁分离和内部分区等现代技术提升了可扩展性。它们大部分位于JUC包下。
46. 什么是线程池? 为何要使用它?
建立线程要花费昂贵的资源和时间,若是任务来了才建立线程那么响应时间会变长,并且一个进程能建立的线程数有限。为了不这些问题,在程序启动的时候就建立若干线程来响应处理,它们被称为线程池,里面的线程叫工做线程。从 JDK1.5 开始,Java API 提供了 Executor 框架让你能够建立不一样的线程池。好比单线程池,每次处理一个任务;数目固定的线程池或者是缓存线程池(一个适合不少生存期短的任务的程序的可扩展线程池)。
47. 如何写代码来解决生产者消费者问题?
在现实中你解决的许多线程问题都属于生产者消费者模型,就是一个线程生产任务供其它线程进行消费,你必须知道怎么进行线程间通讯来解决这个问题。比较低级的办法是用 wait 和 notify 来解决这个问题,比较赞的办法是用 Semaphore 或者 BlockingQueue 来实现生产者消费者模型。
48.如何避免死锁?
死锁是指两个或两个以上的进程在执行过程当中,因争夺资源而形成的一种互相等待的现象,若无外力做用,它们都将没法推动下去。这是一个严重的问题,由于死锁会让你的程序挂起没法完成任务,死锁的发生必须知足如下四个条件:
互斥条件:一个资源每次只能被一个进程使用。
请求与保持条件:一个进程因请求资源而阻塞时,对已得到的资源保持不放。
不剥夺条件:进程已得到的资源,在末使用完以前,不能强行剥夺。
循环等待条件:若干进程之间造成一种头尾相接的循环等待资源关系。
避免死锁最简单的方法就是阻止循环等待条件,将系统中全部的资源设置标志位、排序,规定全部的进程申请资源必须以必定的顺序(升序或降序)作操做来避免死锁。
49. Java 中活锁和死锁有什么区别?
活锁和死锁相似,不一样之处在于处于活锁的线程或进程的状态是不断改变的,活锁能够认为是一种特殊的饥饿。一个现实的活锁例子是两我的在狭小的走廊碰到,两我的都试着避让对方好让彼此经过,可是由于避让的方向都同样致使最后谁都不能经过走廊。简单的说就是,活锁和死锁的主要区别是前者进程的状态能够改变可是却不能继续执行。
50. 怎么检测一个线程是否拥有锁
在 java.lang.Thread 中有一个方法叫 holdsLock,当且仅当当前线程拥有某个具体对象的锁时它返回true。
51. 你如何在 Java 中获取线程堆栈
在 Linux 下,你能够经过命令 kill -3 PID (Java 进程的进程 ID)来获取 Java 应用的 dump 文件。在 Windows 下,你能够按下 Ctrl + Break 来获取。这样 JVM 就会将线程的 dump 文件打印到标准输出或错误文件中,它可能打印在控制台或者日志文件中,具体位置依赖应用的配置。
52.Java 中 synchronized 和 ReentrantLock 有什么不一样
Java 在过去很长一段时间只能经过 synchronized 关键字来实现互斥,它有一些缺点。好比你不能扩展锁以外的方法或者块边界,尝试获取锁时不能中途取消等。Java 5 经过 Lock 接口提供了更复杂的控制来解决这些问题。 ReentrantLock 类实现了 Lock,它拥有与 synchronized 相同的并发性和内存语义且它还具备可扩展性。
53.有三个线程 T1,T2,T3,怎么确保它们按顺序执行
能够用线程类的 join ()方法。具体操做是在T3的run方法中调用t2.join(),让t2执行完再执行t3;T2的run方法中调用t1.join(),让t1执行完再执行t2。这样就按T1,T2,T3的顺序执行了
54.Thread 类中的 yield 方法有什么做用
Yield 方法能够暂停当前正在执行的线程对象,让其它有相同优先级的线程执行。它是一个静态方法并且只保证当前线程放弃 CPU 占用而不能保证使其它线程必定能占用 CPU,执行 yield的线程有可能在进入到暂停状态后立刻又被执行。
55.Java 中 ConcurrentHashMap 的并发度是什么
ConcurrentHashMap 把实际 map 划分红若干部分来实现它的可扩展性和线程安全。这种划分是使用并发度得到的,它是 ConcurrentHashMap 类构造函数的一个可选参数,默认值为 16,这样在多线程状况下就能避免争用。
56.Java 中 Semaphore是什么
JUC下的一种新的同步类,它是一个计数信号。从概念上讲,Semaphore信号量维护了一个许可集合。acquire获取许可,release释放一个许可,从而可能释放一个正在阻塞的获取者。可是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采起相应的行动。信号量经常用于多线程的代码中,好比数据库链接池。
57.若是你提交任务时,线程池队列已满。会发会生什么?
这个问题问得很狡猾,许多程序员会认为该任务会阻塞直到线程池队列有空位。事实上若是一个任务不能被调度执行那么 ThreadPoolExecutor’s submit ()方法将会抛出一个 RejectedExecutionException 异常。
58.Java 线程池中 submit () 和 execute ()方法有什么区别
两个方法均可以向线程池提交任务,execute ()方法的返回类型是 void,它定义在 Executor 接口中, 而 submit ()方法能够返回持有计算结果的 Future 对象,它定义在 ExecutorService 接口中,它扩展了 Executor 接口,其它线程池类像 ThreadPoolExecutor 和 ScheduledThreadPoolExecutor 都有这些方法。
59.什么是阻塞式方法?
阻塞式方法是指程序会一直等待该方法完成期间不作其余事情,ServerSocket 的 accept ()方法就是一直等待客户端链接。这里的阻塞是指调用结果返回以前,当前线程会被挂起,直到获得结果以后才会返回。此外,还有异步和非阻塞式方法在任务完成前就返回。
60.Swing 是线程安全的吗?
你能够很确定的给出回答,Swing 不是线程安全的。你不能经过任何线程来更新 Swing 组件,如 JTable、JList 或 JPanel,事实上,它们只能经过 GUI 或 AWT 线程来更新。这就是为何 Swing 提供 invokeAndWait() 和 invokeLater() 方法来获取其余线程的 GUI 更新请求。这些方法将更新请求放入 AWT 的线程队列中,能够一直等待,也能够经过异步更新直接返回结果。
61.Java 中 invokeAndWait 和 invokeLater 有什么区别
这两个方法是 Swing API 提供给 Java 开发者用来从当前线程而不是事件派发线程更新 GUI 组件用的。InvokeAndWait ()同步更新 GUI 组件,好比一个进度条,一旦进度更新了,进度条也要作出相应改变。若是进度被多个线程跟踪,那么就调用 invokeAndWait ()方法请求事件派发线程对组件进行相应更新。而 invokeLater ()方法是异步调用更新组件的。
62.Swing API 中那些方法是线程安全的?
虽然Swing不是线程安全的可是有一些方法是能够被多线程安全调用的。如repaint (), revalidate ()。 JTextComponent 的 setText ()方法和 JTextArea 的 insert () 和 append () 方法也是线程安全的。
63.如何在 Java 中建立 Immutable 对象
Immutable 对象能够在没有同步的状况下共享,下降了对该对象进行并发访问时的同步化开销。但是 Java 没有@Immutable 这个注解符,要建立不可变类,要实现下面几个步骤:经过构造方法初始化全部成员、对变量不要提供 setter 方法、将全部的成员声明为私有的,这样就不容许直接访问这些成员、在 getter 方法中,不要直接返回对象自己,而是克隆对象,并返回对象的拷贝。
64.Java 中的 ReadWriteLock 是什么?
通常而言,读写锁是用来提高并发程序性能的锁分离技术的成果。Java 中的 ReadWriteLock 是 Java 5 中新增的一个接口,一个 ReadWriteLock 维护一对关联的锁,一个用于只读操做一个用于写。在没有写线程的状况下一个读锁可能会同时被多个读线程持有。写锁是独占的,你可使用 JDK 中的 ReentrantReadWriteLock 来实现这个规则,它最多支持 65535 个写锁和 65535 个读锁。
65.多线程中的忙循环是什么?
忙循环就是程序员用循环让一个线程等待,不像传统方法 wait (), sleep () 或 yield () 它们都放弃了 CPU 控制,而忙循环不会放弃 CPU,它就是在运行一个空循环。这么作的目的是为了保留 CPU 缓存,在多核系统中,一个等待线程醒来的时候可能会在另外一个内核运行,这样会重建缓存。为了不重建缓存和减小等待重建的时间就可使用它了。
66.volatile 变量和 atomic 变量有什么不一样
volatile 变量和 atomic 变量看起来很像,但功能却不同。volatile 变量能够确保先行关系,即写操做会发生在后续的读操做以前, 但它并不能保证原子性。例如用 volatile 修饰 count 变量那么 count++ 操做并非原子性的。而 AtomicInteger 类提供的 atomic 方法可让这种操做具备原子性如 getAndIncrement ()方法会原子性的进行增量操做把当前值加一,其它数据类型和引用变量也能够进行类似操做。
67.若是同步块内的线程抛出异常会发生什么?
不管你的同步块是正常仍是异常退出的,里面的线程都会释放锁,因此对比锁接口我更喜欢同步块,由于它不用我花费精力去释放锁,该功能能够在 finally block 里释放锁实现。
68.如何在 Java 中建立线程安全的 Singleton
5种,急加载,同步方法,双检锁,静态内部类,枚举
69.如何强制启动一个线程?
这个问题就像是如何强制进行 Java 垃圾回收,目前尚未以为方法,虽然你可使用 System.gc ()来进行垃圾回收,可是不保证能成功。在 Java 里面没有办法强制启动一个线程,它是被线程调度器控制着且 Java 没有公布相关的 API。
70.Java 中的 fork join 框架是什么?
fork join 框架是 JDK7 中出现的一款高效的工具,Java 开发人员能够经过它充分利用现代服务器上的多处理器。它是专门为了那些能够递归划分红许多子模块设计的,目的是将全部可用的处理能力用来提高程序的性能。fork join 框架一个巨大的优点是它使用了工做窃取算法,能够完成更多任务的工做线程能够从其它线程中窃取任务来执行。
71.Java 多线程中调用 wait () 和 sleep ()方法有什么不一样?
Java 程序中 wait 和 sleep 都会形成某种形式的暂停,它们能够知足不一样的须要。wait ()方法意味着条件等待,若是等待条件为真且其它线程被唤醒时它会释放锁,而 sleep ()方法仅仅释放 CPU 资源或者让当前线程短暂停顿,但不会释放锁。
72.可重入锁
可重入锁:若是当前线程已经得到了某个监视器对象所持有的锁,那么该线程在该方法中调用另一个同步方法也一样持有该锁。
public synchrnozied void test() {
xxxxxx;
test2();
}
public synchronized void test2() {
yyyyy;
}复制代码
在上面代码段中,执行 test 方法须要得到当前对象做为监视器的对象锁,但方法中又调用了 test2 的同步方法。
若是锁是具备可重入性的话,那么该线程在调用 test2 时并不须要再次得到当前对象的锁,能够直接进入 test2 方法进行操做。
若是锁是不具备可重入性的话,那么该线程在调用test2前会等待当前对象锁的释放,实际上该对象锁已被当前线程所持有,不可能再次得到。
若是锁是不具备可重入性特色的话,那么线程在调用同步方法、含有锁的方法时就会产生死锁。
73. 同步方法和同步代码块
同步方法默认用this或者当前类class对象做为锁;同步代码块能够选择以什么来加锁,比同步方法要更细颗粒度,咱们能够选择只同步会发生同步问题的部分代码而不是整个方法。
【感谢您能看完,若是可以帮到您,麻烦点个赞~】
更多经验技术欢迎前来共同窗习交流:一点课堂-为梦想而奋斗的在线学习平台 http://www.yidiankt.com/
![关注公众号,回复“1”免费领取-【java核心知识点】]
QQ讨论群:616683098
QQ:3184402434
想要深刻学习的同窗们能够加我QQ一块儿学习讨论~还有全套资源分享,经验探讨,等你哦!