- 0 内存模型
- 1.Lock,tryLock,lockInterruptibly区别
- 2.Condition
- 3.Unsafe类
- 5..Fork/Join框架与窃取
- 6 ThreadLocal
- 7.线程的状态
- 8 thread类
- 9 停止一个线程
- 10 乐观锁和悲观锁
- 11 实现线程的方式
- 12 怎么唤醒一个阻塞的线程?
- 13 volatile关键字的作用(必考)
- 14 释放锁和不释放锁
- 15 synchronized 的实现原理以及锁优化(必考)
- 16 指令重排序(必考)
- 17 happens-before(必考)
- 18 CompletableFuture
- 19 什么是多线程上下文切换
- 20 Java当中有哪几种锁
- 21 提交任务时,线程池队列已满
- 22 CyclicBarrier和CountDownLatch区别
- 23 atomic包
- 24 locks包
- 25 Lock接口.
- 26 LockSupport
- 27 AbstractOwnableSynchronizer
- 28 AbstractQueuedLongSynchronizer
- 29 AbstractQueuedSynchronizer(AQS必考)
- 30 ReentrantLock(必考)
- 31 ReadWriteLock
- 32 StampedLock
- 33 CountDownLatch
- 34 CyclicBarrier
- 35 Semaphore
- 36 Exchanger
- 37 Phaser
- 40 线程池体系
- 41 自旋锁、排队自旋锁、MCS锁、CLH锁
- 42 ForkJoinPool
- 43 并发良好的实践?
- 44 如何合理配置线程池大小
- 45 线程池8大参数 ?拒绝策略?
- 46 使用ScheduledExecutorService代替Timer
- 47 线程优先级
- 48 Executors弊端
- 49 ReentrantLock 保证可见性?
- 50 CyclicBarrier与CountDownLatch的区别
- 51 Thread.sleep()和Object.wait()的区别
- 52 Thread.sleep()和Condition.await()的区别
- 53 Thread.sleep()和LockSupport.park()的区别
- 54 Object.wait()和LockSupport.park()的区别
- 55 CAS同时具有Volatile读写的语义?
- 56 concurrent包的实现?
- 57 final重排序规则
- 58 安全的发布对象
- 58 ThreadLocal泄漏?线程池里的线程用ThreadLocal
- 59 sleep、wait、yield、join区别
- Java对象的内存分配过程是如何保证线程安全的?">60 Java对象的内存分配过程是如何保证线程安全的?
- 61 栈上分配对象
- 62 线程与进程的区别
- 63 线程池以及调优
- 64 给定一个进程,有多个线程,其中一个线程出现OOM异常,判断所有线程的状态
- 65、原子类实现
- 66 死锁的四个条件?
- 67、
- 68、死锁与活锁的区别,死锁与饥饿的区别
- 69 JMM模型
- 70、Java中用到的线程调度算法是什么
- 71、什么是线程组,为什么在Java中不推荐使用
- 72 什么状态下的线程可以中断
- 74 Thread.sleep实现原理
- 75 线程池参数考虑
- 76 内存泄漏
0 内存模型
1.Lock,tryLock,lockInterruptibly区别
https://blog.csdn.net/u013851082/article/details/70140223
2.Condition
3.Unsafe类
5..Fork/Join框架与窃取
6 ThreadLocal
7.线程的状态
1 new :创建尚未启动的线程
2 运行:包括了runing和ready状态
3 无期限等待:需要其他线程唤醒。不会被分配时间片。无参数的object.await(),Thread.join()。LockSupport.part()方法。一个线程进入了锁,但是需要等待其他线程执行某些操作。时间不确定 当wait,join,park方法调用时,进入waiting状态。前提是这个线程已经拥有锁了
4.期限等待:不会分配cpu执行时间,不需要其他线程显示唤醒。Thread.sleep()。设置了Timeout的object.await(),Thread.join()方法,LockSupport.parkNaos()、LockSupport.parkUntil()。一个线程进入了锁,但是需要等待其他线程执行某些操作。时间确定 通过sleep或wait timeout方法进入的限期等待的状态
5.阻塞:争用排他锁synchornized。与等待的区别是等其他线程唤醒。
6.结束。
8 thread类
- 成员
name是表示Thread的名字,可以通过Thread类的构造器中的参数来指定线程名字,
priority表示线程的优先级(最大值为10,最小值为1,默认值为5),
daemon表示线程是否是守护线程
target表示要执行的任务。
- 方法(4)yield()方法:和sleep()方法有点相似,使线程进入就绪状态,让出时间片,线程优先更高或等于。当前线程放弃CPU,但不会释放锁。不同操作系统行为不一样,线程优先级也不确定public final void join(long millis,int nanos) throws InterruptedException最多等待 millis 毫秒加上 nanos 纳秒该线程死亡。除了等到终止之外,调用 join()方法还具有同步效果。join()创建一个before-before关系:这意味着当线程t1调用t2.join()时,t2完成的所有更改在返回时在t1中可见
(1)start方法:start方法后,系统才会开启一个新的线程来执行用户定义的子任务,在这个过程中,会为相应的线程分配需要的资源
(2)run方法:
(3)sleep方法:进入阻塞状态,不会分cpu时间片,不会释放锁
(5) join方法:与 wait()*和_notify()方法一样,join()是另一种线程间同步机制。*如果引用的线程被中断,join()方法也可能返回。在这种情况下,该方法抛出 _InterruptedException
(6)interrupt方法:单独调用interrupt方法可以使得处于阻塞状态的线程抛出一个异常,也就说,它可以用来中断一个正处于阻塞状态的线程;另外,通过interrupt方法和isInterrupted()方法来停止正在运行的线程。不能中断没有阻塞的线程,除非主动使用通过调用isInterrupted()判断中断标志是否被置位来,是否主动结束进程。
- 本线程中断自身是被允许的,且”中断标记”设置为true
- 其它线程调用本线程的interrupt()方法时,会通过checkAccess()检查权限。这有可能抛出SecurityException异常。(7)interrupted方法:判断的是当前线程是否处于中断状态。是类的静态方法,同时会清除线程的中断状态。
- 若线程在阻塞状态时,调用了它的interrupt()方法,那么它的“中断状态”会被清除并且会收到一个InterruptedException异常。
- 例如,线程通过wait()进入阻塞状态,此时通过interrupt()中断该线程;调用interrupt()会立即将线程的中断标记设为“true”,但是由于线程处于阻塞状态,所以该“中断标记”会立即被清除为“false”,同时,会产生一个InterruptedException的异常。
- 如果线程被阻塞在一个Selector选择器中,那么通过interrupt()中断它时;线程的中断标记会被设置为true,并且它会立即从选择操作中返回。
- 如果不属于前面所说的情况,那么通过interrupt()中断线程时,它的中断标记会被设置为“true”。
- 若线程在阻塞状态时,调用了它的interrupt()方法,那么它的“中断状态”会被清除并且会收到一个InterruptedException异常。
(8) isInterrupted()方法,判断调用线程是否处于中断状态,不会清除中断状态。
(9)stop方法:stop方法已经是一个废弃的方法,它是一个不安全的方法。因为调用stop方法会直接终止run方法的调用,并且会抛出一个ThreadDeath错误,如果线程持有某个对象锁的话,会完全释放锁,导致对象状态不一致
(10) destroy方法也是废弃的方法。基本不会被使用到。
(11)suspend()和resume()suspend()方法在线程挂起时,并不释放对象锁,因此可能会导致死锁。resume该方法很功能很简单,就是恢复 因suspend()方法挂起的线程,使之重新能够获得CPU执行
SecurityException if the current thread is not allowed taccess this threa
(12)public static native boolean holdsLock(Object obj);
9 停止一个线程
1.不安全的做法 stop 、suspend
3 使用共享变量的方式
4 中断
5 使用Feature取消。
6 正常运行结束退出
10 乐观锁和悲观锁
(1)乐观锁:乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。自旋锁。
(2)悲观锁:对于并发间操作产生的线程安全问题持悲观状态,悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。
11 实现线程的方式
第一种:继承Thread第二种:实现Runnable接口,这种方式使用较多,面向接口编程一直是被推崇的开发原则。第三种:实现Callable接口用来实现返回结果的线程
第四种; lambda表达式
12 怎么唤醒一个阻塞的线程?
1.如果线程是因为调用了wait()、sleep()或者join()方法而导致的阻塞,可以中断线程,并且通过抛出InterruptedException来唤醒它;
2.如果线程遇到了IO阻塞,无能为力,因为IO是操作系统实现的,Java代码并没有办法直接接触到操作系统。
13 volatile关键字的作用(必考)
14 释放锁和不释放锁
1.释放锁:
(1)执行完同步代码块,就会释放锁。(synchronized)
(2)在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放。(exception)
(3)在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程会释放锁,进 入对象的等待池。(wait)
(4)Thread.stop
(5)LOCK.lockInterruptibly可以使用interrupt
2.不会释放锁
(1)Thread.sleep()
(2)Thread.yield()
(3)Thread.suspend()
15 synchronized 的实现原理以及锁优化(必考)
https://www.jianshu.com/p/e62fa839aa41
16 指令重排序(必考)
17 happens-before(必考)
在Java内存模型中,如果不存在happens-beforejvm可以随意排序。JSR-133S使用happens-before概念阐述了两个操作之间的内存可见性。在JMM中,如果一个操作的结果需要对另一个操作可见,那么这两个操作则存在happens-before关系。
那什么是happens-before呢?在JSR-133中,happens-before关系定义如下:
- 如果一个操作happens-before另一个操作,那么意味着第一个操作的结果对第二个操作可见,而且第一个操作的执行顺序将排在第二个操作的前面。
- 两个操作之间存在happens-before关系,并不意味着Java平台的具体实现必须按照happens-before关系指定的顺序来执行。如果重排序之后的结果,与按照happens-before关系来执行的结果一致,那么这种重排序并不非法(也就是说,JMM允许这种重排序)
java天然满足happens-before规则的情况:
- 程序顺序规则:一个线程中的每一个操作,happens-before于该线程中的任意后续操作。
- 监视器规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。
- volatile规则:对一个volatile变量的写,happens-before于任意后续对一个volatile变量的读。
- 传递性:若果A happens-before B,B happens-before C,那么A happens-before C。
- 线程启动规则:Thread对象的start()方法,happens-before于这个线程的任意后续操作。
- 线程终止规则:线程中的任意操作,happens-before于该线程的终止监测。我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值等手段检测到线程已经终止执行。
- 线程中断操作:对线程interrupt()方法的调用,happens-before于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测到线程是否有中断发生。
- 对象终结规则:一个对象的初始化完成,happens-before于这个对象的finalize()方法的开始。
扩展:
任何并发集合的写操作与该集合的后续访问或者删除操作存在happens-before关系;线程中提交Runnable到Executor之前的代码与Runnable中的代码存在happens-before关系。对提交Callable到ExecutorService也一样;一个Future代表的异步计算和另一个线程中Future.get()的后续操作存在happens-before关系;释放同步方法(比如Lock.unlock,Semaphore.release和 CountDownLatch.countDown)之前的代码和随后的在相同对象上的获得方法(比如Lock.lock, Semaphore.acquire, Condition.await和 CountDownLatch.await)存在happens-before关系;通过Exchanger成功交换对象的线程对,exchange()之前的代码和exchange()之后的代码存在happens-before关系;调用CyclicBarrier.await和Phaser.awaitAdvance之前的代码和执行barrier操作存在happens-before关系;执行barrier操作和其他线程中对应await返回后的代码存在happens-before关系。
18 CompletableFuture
https://mp.weixin.qq.com/s/d_TzKlyxD0RoWb8-UPimSQ
19 什么是多线程上下文切换
多线程的上下文切换是指CPU控制权由一个已经正在运行的线程切换到另外一个就绪并等待获取CPU执行权的线程的过程。CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。但是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再次加载这个任务的状态。
1 引起线程上下文切换的原因?
对于我们经常使用的抢占式操作系统而言,引起线程上下文切换的原因大概有以下几种:
- 当前执行任务的时间片用完之后,系统CPU正常调度下一个任务
- 当前执行任务碰到IO阻塞,调度器将此任务挂起,继续下一任务
- 多个任务抢占锁资源,当前任务没有抢到锁资源,被调度器挂起,继续下一任务
- 用户代码挂起当前任务,让出CPU时间
- 硬件中断
2.上下文切换次数查看
在Linux系统下可以使用vmstat命令来查看上下文切换的次数,下面是利用vmstat查看上下文切换次数的示例:
CS(Context Switch)表示上下文切换的次数,从图中可以看到,上下文每秒钟切换500~600次左右。
如果要查看上下文切换的时长,可以利用Lmbench3,这是一个性能分析工具。
3 如何减少上下文切换
既然上下文切换会导致额外的开销,因此减少上下文切换次数便可以提高多线程程序的运行效率。减少上下文切换的方法有无锁并发编程、CAS算法、使用最少线程和使用协程。
- 无锁并发编程。多线程竞争时,会引起上下文切换,所以多线程处理数据时,可以用一些办法来避免使用锁,如将数据的ID按照Hash取模分段,不同的线程处理不同段的数据
- CAS算法。Java的Atomic包使用CAS算法来更新数据,而不需要加锁
- 使用最少线程。避免创建不需要的线程,比如任务很少,但是创建了很多线程来处理,这样会造成大量线程都处于等待状态
协程。在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换
20 Java当中有哪几种锁
-
21 提交任务时,线程池队列已满
如果你使用的LinkedBlockingQueue,也就是×××队列的话,没关系,继续添加任务到阻塞队列中等待执行,因为LinkedBlockingQueue可以近乎认为是一个无穷大的队列,可以无限存放任务;如果你使用的是有界队列比方说ArrayBlockingQueue的话,任务首先会被添加到ArrayBlockingQueue中,ArrayBlockingQueue满了,则会使用拒绝策略RejectedExecutionHandler处理满了的任务,默认是AbortPolicy。
22 CyclicBarrier和CountDownLatch区别
23 atomic包
atomic包里面一共提供了13个类,分为4种类型,分别是:原子更新基本类型,原子更新数组,原子更新引用,原子更新属性,这13个类都是使用Unsafe实现的包装类。
标量类:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
- 数组类:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
- 更新器类:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
- 复合变量类:AtomicMarkableReference,AtomicStampedReference
1.原子更新基本类型
addAndGet(int delta) :以原子方式将输入的数值与实例中原本的值相加,并返回最后的结果;
incrementAndGet() :以原子的方式将实例中的原值进行加1操作,并返回最终相加后的结果;
getAndSet(int newValue):将实例中的值更新为新值,并返回旧值;
getAndIncrement():以原子的方式将实例中的原值加1,返回的是自增前的旧值;
2.原子更新数组类型
int base = unsafe.arrayBaseOffset(int[].class);Unsafe类的arraBaseOffset方法:返回指定类型数组的第一个元素地址相对于数组起始地址的偏移值。
int scale = unsafe.arrayIndexScale(int[].class);Unsafe类的arrayIndexScale方法:返回指定类型数组的元素所占用的字节数。比如int[]数组中的每个int元素占用4个字节,就返回4。
那么,通过base + i sacle 其实就可以知道 索引i的元素在数组中的内存起始地址。但是,观察AtomicIntegerArray的byteOffset方法,是通过i << shift + base 的公式计算元素的起始地址的:
地址的:
这里,
其实就等于scale。
shift = 31 - Integer.numberOfLeadingZeros(scale),Integer.numberOfLeadingZeros(scale)是将scale转换为2进制,然后从左往右数连续0的个数。
读者可以自己计算下:shift = 31 - Integer.numberOfLeadingZeros(4) = 31 - 29 =2
之所以要这么绕一圈,其实是处于性能的考虑,通过移位计算乘法的效率往往更高。
3.AtomicReference AtomicStampedReference AtomicMarkableReference
4.LongAdder,LongAccumulator DoubleAdder和DoubleAccumulator
https://segmentfault.com/a/1190000015865714?utm_source=tag-newest
AtomicLong是利用了底层的CAS操作来提供并发性的,比如addAndGet方法。在并发量较低的环境下,线程冲突的概率比较小,自旋的次数不会很多。但是,高并发环境下,N个线程同时进行自旋操作,会出现大量失败并不断自旋的情况,此时AtomicLong的自旋会成为瓶颈。
AtomicLong中有个内部变量value保存着实际的long值,所有的操作都是针对该变量进行。也就是说,高并发环境下,value变量其实是一个热点,也就是N个线程竞争一个热点。
LongAdder的基本思路就是**分散热点*,将value值分散到一个数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行CAS操作,这样热点就被分散了,冲突的概率就小很多。如果要获取真正的long值,只要将各个槽中的变量值累加返回。
但是AtomicLong提供的功能其实更丰富,尤其是addAndGet、decrementAndGet、compareAndSet这些方法。
addAndGet、decrementAndGet除了单纯的做自增自减外,还可以立即获取增减后的值,而LongAdder则需要做同步控制才能精确获取增减后的值。如果业务需求需要精确的控制计数,做计数比较,AtomicLong也更合适。
比如有三个ThreadA、ThreadB、ThreadC,每个线程对value增加10。
对于AtomicLong,最终结果的计算始终是下面这个形式:
但是对于LongAdder**来说,内部有一个base变量,一个Cell[]数组。base变量:非竞态条件下,直接累加到该变量上Cell[]数组:竞态条件下,累加个各个线程自己的槽Cell[i]中最终结果的计算是下面这个形式:
24 locks包
25 Lock接口.
实现类主要有reentrantLock. WriteLock,WriteLockView
26 LockSupport
https://www.iteye.com/blog/agapple-970055
LockSupport定义了一组以park开头的方法用来阻塞当前线程,以及unpark(Thread)方法来唤醒一个被阻塞的线程,这些方法描述如下:
// 返回提供给最近一次尚未解除阻塞的 park 方法调用的 blocker 对象,如果该调用不受阻塞,则返回 null。
static Object getBlocker(Thread t)
// 为了线程调度,禁用当前线程,除非许可可用。
static void park()
// 为了线程调度,在许可可用之前禁用当前线程。
static void park(Object blocker)
// 为了线程调度禁用当前线程,最多等待指定的等待时间,除非许可可用。
static void parkNanos(long nanos)
// 为了线程调度,在许可可用前禁用当前线程,并最多等待指定的等待时间。
static void parkNanos(Object blocker, long nanos)
// 为了线程调度,在指定的时限前禁用当前线程,除非许可可用。
static void parkUntil(long deadline)
// 为了线程调度,在指定的时限前禁用当前线程,除非许可可用。
static void parkUntil(Object blocker, long deadline)
// 如果给定线程的许可尚不可用,则使其可用。
static void unpark(Thread thread)
27 AbstractOwnableSynchronizer
该类为创建可能涉及所有权概念的锁和相关同步器提供了基础。AbstractOwnableSynchronizer类本身不管理或使用此信息。但是,子类和工具可以使用适当维护的值来帮助控制和监视访问并提供诊断。
private transient Thread exclusiveOwnerThread;
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
protected final Thread getExclusiveOwnerThread() {
return exclusiveOwnerThread;
}
28 AbstractQueuedLongSynchronizer
this class may be useful when creating synchronizers such as multilevel locks and barriers that require 64 bits of state.
29 AbstractQueuedSynchronizer(AQS必考)
30 ReentrantLock(必考)
https://blog.csdn.net/fuyuwei2015/article/details/83719444
见博客
31 ReadWriteLock
https://www.jianshu.com/p/4a624281235e
见博客
32 StampedLock
33 CountDownLatch
34 CyclicBarrier
35 Semaphore
36 Exchanger
可用于两个线程之间交换信息。可简单地将Exchanger对象理解为一个包含两个格子的容器,通过exchanger方法可以向两个格子中填充信息。当两个格子中的均被填充时,该对象会自动将两个格子的信息交换,然后返回给线程,从而实现两个线程的信息交换,Exchanger类仅可用作两个线程的信息交换,当超过两个线程调用同一个exchanger对象时,得到的结果是随机的,exchanger对象仅关心其包含的两个“格子”是否已被填充数据,当两个格子都填充数据完成时(调用exchange方法),该对象就认为线程之间已经配对成功,然后开始执行数据交换操作
37 Phaser
https://www.jianshu.com/p/a9a713cba61a
1)两个计数器:分别表示parties个数和当前phase。register和deregister会触发parties变更(CAS),全部parties到达(arrive)会触发phase变更。
2)一个主要的阻塞队列:非AQS实现,对于arriveAndWait的线程,会被添加到队列中并被park阻塞,知道当前phase中最后一个party到达后触发唤醒。
40 线程池体系
见博客
41 自旋锁、排队自旋锁、MCS锁、CLH锁
https://blog.csdn.net/fei33423/article/details/30316377
42 ForkJoinPool
43 并发良好的实践?
- 给线程命名
- 最小化同步范围
- 优先使用volatile
- 尽可能使用更高层次的并发工具而非wait和notify()来实现线程通信,如BlockingQueue,Semeaphore
- 优先使用并发容器而非同步容器.
- 考虑使用线程池
- 不使用Executors
- 不使用Timer
- ThreadLocal 清空
44 如何合理配置线程池大小
首先,需要考虑到线程池所进行的工作的性质:IO密集型?CPU密集型?简单的分析来看,如果是CPU密集型的任务,我们应该设置数目较小的线程数,比如CPU数目加1。如果是IO密集型的任务,则应该设置可能多的线程数,由于IO操作不占用CPU,所以,不能让CPU闲下来。当然,如果线程数目太多,那么线程切换所带来的开销又会对系统的响应时间带来影响。在《linux多线程服务器端编程》中有一个思路,CPU计算和IO的阻抗匹配原则。如果线程池中的线程在执行任务时,密集计算所占的时间比重为P(0 下面验证一下边界条件的正确性:
假设C = 8,P = 1.0,线程池的任务完全是密集计算,那么T = 8。只要8个活动线程就能让8个CPU饱和,再多也没用了,因为CPU资源已经耗光了。假设C = 8,P = 0.5,线程池的任务有一半是计算,有一半在等IO上,那么T = 16.考虑操作系统能灵活,合理调度sleeping/writing/running线程,那么大概16个“50%繁忙的线程”能让8个CPU忙个不停。启动更多的线程并不能提高吞吐量,反而因为增加上下文切换的开销而降低性能。
如果P < 0.2,这个公式就不适用了,T可以取一个固定值,比如 5C。另外公式里的C不一定是CPU总数,可以是“分配给这项任务的CPU数目”,比如在8核机器上分出4个核来做一项任务,那么C=4文章如何合理设置线程池大小里面提到了一个公式:最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 ) CPU数目
比如平均每个线程CPU运行时间为0.5s,而线程等待时间(非CPU运行时间,比如IO)为1.5s,CPU核心数为8,那么根据上面这个公式估算得到:((0.5+1.5)/0.5)8=32。这个公式进一步转化为:
最佳线程数目 = (线程等待时间与线程CPU时间之比 + 1) CPU数目可以得出一个结论:线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。以上公式与之前的CPU和IO密集型任务设置线程数基本吻合。
45 线程池8大参数 ?拒绝策略?
46 使用ScheduledExecutorService代替Timer
1、管理并发任务的缺陷
timer有且仅有一个线程去执行定时任务,如果存在多个任务,且任务时间过长,会导致执行效果与预期不符。
2、当任务抛出异常时的缺陷
如果TimerTask抛出RuntimeException,Timer会停止所有任务的运行
3、不能满足对时效性要求较高的多任务并发作业,Timer背后只有一个线程串行的执行任务调度
4、不能满足对复杂任务的调度
5 Timer 是基于绝对时间的,对系统时间比较敏感,而 ScheduledThreadPoolExecutor 则是基于相对时间;
Timer 是内部是单一线程,而 ScheduledThreadPoolExecutor 内部是个线程池,所以可以支持多个任务并发执行。
6 。Timer 运行多个 TimeTask 时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行,使用 ScheduledExecutorService 则没有这个问题。
使用 ScheduledExecutorService 更容易明确任务实际执行策略,更方便自行控制。
7。 默认 Timer 执行线程不是 daemon 线程, 任务执行完,主线程(或其他启动定时器的线程)结束时,task 线程并没有结束。需要注意潜在内存泄漏问题。
47 线程优先级
48 Executors弊端
Executors返回的线程池对象的弊端如下:
1)FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
2)CachedThreadPool 和 ScheduledThreadPool :
允许创建的线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
49 ReentrantLock 保证可见性?
http://ifeve.com/java%E9%94%81%E6%98%AF%E5%A6%82%E4%BD%95%E4%BF%9D%E8%AF%81%E6%95%B0%E6%8D%AE%E5%8F%AF%E8%A7%81%E6%80%A7%E7%9A%84/
50 CyclicBarrier与CountDownLatch的区别
这两个类都可以实现一组线程在到达某个条件之前进行等待,它们内部都有一个计数器,当计数器的值不断的减为0的时候所有阻塞的线程将会被唤醒。
有区别的是
CyclicBarrier的计数器由自己控制,而CountDownLatch的计数器则由使用者来控制,在CyclicBarrier中线程调用await方法不仅会将自己阻塞还会将计数器减1,而在CountDownLatch中线程调用await方法只是将自己阻塞而不会减少计数器的值。
另外,CountDownLatch只能拦截一轮,而CyclicBarrier可以实现循环拦截。一般来说用CyclicBarrier可以实现CountDownLatch的功能,而反之则不能,例如上面的赛马程序就只能使用CyclicBarrier来实现。总之,这两个类的异同点大致如此,至于何时使用CyclicBarrier,何时使用CountDownLatch,还需要读者自己去拿捏。
除此之外,CyclicBarrier还提供了:resert()、getNumberWaiting()、isBroken()等比较有用的方法。
51 Thread.sleep()和Object.wait()的区别
(1)Thread.sleep()不会释放占有的锁,Object.wait()会释放占有的锁;
(2)Thread.sleep()必须传入时间,Object.wait()可传可不传,不传表示一直阻塞下去;
(3)Thread.sleep()到时间了会自动唤醒,然后继续执行;
(4)Object.wait()不带时间的,需要另一个线程使用Object.notify()唤醒;
(5)Object.wait()带时间的,假如没有被notify,到时间了会自动唤醒,这时又分好两种情况,一是立即获取到了锁,线程自然会继续执行;二是没有立即获取锁,线程进入同步队列等待获取锁;
其实,他们俩最大的区别就是Thread.sleep()不会释放锁资源,Object.wait()会释放锁资源。
52 Thread.sleep()和Condition.await()的区别
这个题目的回答思路跟Object.wait()是基本一致的,不同的是Condition.await()底层是调用LockSupport.park()来实现阻塞当前线程的。
实际上,它在阻塞当前线程之前还干了两件事,一是把当前线程添加到条件队列中,二是“完全”释放锁,也就是让state状态变量变为0,然后才是调用LockSupport.park()阻塞当前线程,可以参考之前彤哥写的《死磕 java同步系列之ReentrantLock源码解析(二)——条件锁》这篇文章。
看到这里,今天开篇提的那个问题是不是就有答案了呢【本文由公从号“彤哥读源码”原创】?
53 Thread.sleep()和LockSupport.park()的区别
LockSupport.park()还有几个兄弟方法——parkNanos()、parkUtil()等,我们这里说的park()方法统称这一类方法。
(1)从功能上来说,Thread.sleep()和LockSupport.park()方法类似,都是阻塞当前线程的执行,且都不会释放当前线程占有的锁资源;
(2)Thread.sleep()没法从外部唤醒,只能自己醒过来;
(3)LockSupport.park()方法可以被另一个线程调用LockSupport.unpark()方法唤醒;
(4)Thread.sleep()方法声明上抛出了InterruptedException中断异常,所以调用者需要捕获这个异常或者再抛出;
(5)LockSupport.park()方法不需要捕获中断异常;
(6)Thread.sleep()本身就是一个native方法;
(7)LockSupport.park()底层是调用的Unsafe的native方法;
54 Object.wait()和LockSupport.park()的区别
二者都会阻塞当前线程的运行,他们有什么区别呢?经过上面的分析相信你一定很清楚了,真的吗?往下看!
(1)Object.wait()方法需要在synchronized块中执行;
(2)LockSupport.park()可以在任意地方执行;
(3)Object.wait()方法声明抛出了中断异常,调用者需要捕获或者再抛出;
(4)LockSupport.park()不需要捕获中断异常【本文由公从号“彤哥读源码”原创】;
(5)Object.wait()不带超时的,需要另一个线程执行notify()来唤醒,但不一定继续执行后续内容;
(6)LockSupport.park()不带超时的,需要另一个线程执行unpark()来唤醒,一定会继续执行后续内容;
(7)如果在wait()之前执行了notify()会怎样?抛出IllegalMonitorStateException异常;
(8)如果在park()之前执行了unpark()会怎样?线程不会被阻塞,直接跳过park(),继续执行后续内容;
55 CAS同时具有Volatile读写的语义?
JAVA实现CAS的原理:
compareAndSwapInt是借助C来调用CPU底层指令实现的。下面从分析比较常用的CPU(intel x86)来解释CAS的实现原理。下面是sun.misc.Unsafe类的compareAndSwapInt()方法的源代码:
public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x); 再看下在JDK中依次调用的C++代码为: #define LOCK_IF_MP(mp) asm cmp mp, 0 \ asm je L0 \ asm _emit 0xF0 \ asm L0: inline jint Atomic::cmpxchg (jint exchange_value, volatile jint dest, jint compare_value) { // alternative for InterlockedCompareExchange int mp = os::is_MP(); __asm { mov edx, dest mov ecx, exchange_value mov eax, compare_value LOCK_IF_MP(mp) cmpxchg dword ptr [edx], ecx } }
如上面源代码所示,*程序会根据当前处理器的类型来决定是否为cmpxchg指令添加lock前缀。如果程序是在多处理器上运行,就为cmpxchg指令加上lock前缀(lock cmpxchg)。反之,如果程序是在单处理器上运行,就省略lock前缀(单处理器自身会维护单处理器内的顺序一致性,不需要lock前缀提供的内存屏障效果)。
56 concurrent包的实现?
由于java的CAS同时具有 volatile 读和volatile写的内存语义,因此Java线程之间的通信现在有了下面四种方式:
- A线程写volatile变量,随后B线程读这个volatile变量。
- A线程写volatile变量,随后B线程用CAS更新这个volatile变量。
- A线程用CAS更新一个volatile变量,随后B线程用CAS更新这个volatile变量。
- A线程用CAS更新一个volatile变量,随后B线程读这个volatile变量。
Java的CAS会使用现代处理器上提供的高效机器级别原子指令,这些原子指令以原子方式对内存执行读-改-写操作,这是在多处理器中实现同步的关键(从本质上来说,能够支持原子性读-改-写指令的计算机器,是顺序计算图灵机的异步等价机器,因此任何现代的多处理器都会去支持某种能对内存执行原子性读-改-写操作的原子指令)。同时,volatile变量的读/写和CAS可以实现线程之间的通信。把这些特性整合在一起,就形成了整个concurrent包得以实现的基石。如果我们仔细分析concurrent包的源代码实现,会发现一个通用化的实现模式:
- 首先,声明共享变量为volatile;
- 然后,使用CAS的原子条件更新来实现线程之间的同步;
同时,配合以volatile的读/写和CAS所具有的volatile读和写的内存语义来实现线程之间的通信
57 final重排序规则
为何引入final重排序?为了是final域成为同步一种手段。final修饰的字段与不能幸免于对象溢出。
在构造方法内对一个final字段的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。
初次读一个包含final字段对象的引用,与随后初次读这个final字段,这两个操作不能重排序。
58 安全的发布对象
使用volatile或原子引用
- 静态初始化初始化一个对象引用
- 正确构造的final域中
- 将对象引用保存在一个锁保护的域中。
58 ThreadLocal泄漏?线程池里的线程用ThreadLocal
59 sleep、wait、yield、join区别
1.sleep 方法是属于 Thread 类中的,sleep 过程中线程不会释放锁,只会阻塞线程,让出cpu给其他线程,但是他的监控状态依然保持着,当指定的时间到了又会自动恢复运行状态,可中断,sleep 给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机
2 wait 方法是属于 Object 类中的,wait 过程中线程会释放对象锁,只有当其他线程调用 notify 才能唤醒此线程。wait 使用时必须先获取对象锁,即必须在 synchronized 修饰的代码块中使用,那么相应的 notify 方法同样必须在 synchronized 修饰的代码块中使用,如果没有在synchronized 修饰的代码块中使用时运行时会抛出IllegalMonitorStateException的异常
3 yield暂停当前正在执行的线程对象,不会释放资源锁,和 sleep 不同的是 yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。还有一点和 sleep 不同的是 yield 方法只能使同优先级或更高优先级的线程有执行的机会
4.join 等待调用join方法的线程结束之后,程序再继续执行,一般用于等待异步线程执行完结果之后才能继续运行的场景。例如:主线程创建并启动了子线程,如果子线程中药进行大量耗时运算计算某个数据值,而主线程要取得这个数据值才能运行,这时就要用到 join 方法了
60 Java对象的内存分配过程是如何保证线程安全的?
一般有两种解决方案:
- 1、对分配内存空间的动作做同步处理,采用CAS机制,配合失败重试的方式保证更新操作的线程安全性。
- 2、每个线程在Java堆中预先分配一小块内存,然后再给对象分配内存的时候,直接在自己这块”私有”内存中分配,当这部分区域用完之后,再分配新的”私有”内存。
方案1在每次分配时都需要进行同步控制,这种是比较低效的。
方案2是HotSpot虚拟机中采用的,这种方案被称之为TLAB分配,即Thread Local Allocation Buffer。这部分Buffer是从堆中划分出来的,但是是本地线程独享的。
这里值得注意的是,我们说TLAB时线程独享的,但是只是在“分配”这个动作上是线程独占的,至于在读取、垃圾回收等动作上都是线程共享的。而且在使用上也没有什么区别。
另外,TLAB仅作用于新生代的Eden Space,对象被创建的时候首先放到这个区域,但是新生代分配不了内存的大对象会直接进入老年代。因此在编写Java程序时,通常多个小的对象比大的对象分配起来更加高效。
所以,虽然对象刚开始可能通过TLAB分配内存,存放在Eden区,但是还是会被垃圾回收或者被移到Survivor Space、Old Gen等。
不知道大家有没有想过,我们使用了TLAB之后,在TLAB上给对象分配内存时线程独享的了,这就没有冲突了,但是,TLAB这块内存自身从堆中划分出来的过程也可能存在内存安全问题啊。
所以,在对于TLAB的分配过程,还是需要进行同步控制的。但是这种开销相比于每次为单个对象划分内存时候对进行同步控制的要低的多。
虚拟机是否使用TLAB是可以选择的,可以通过设置-XX:+/-UseTLAB参数来指定。
可以通过设置-XX:+/-UseTLAB参数来指定是否开启TLAB分配
61 栈上分配对象
栈上分配
在JVM中,堆是线程共享的,因此堆上的对象对于各个线程都是共享和可见的,只要持有对象的引用,就可以访问堆中存储的对象数据。虚拟机的垃圾收集系统可以回收堆中不再使用的对象,但对于垃圾收集器来说,无论筛选可回收对象,还是回收和整理内存都需要耗费时间。
如果确定一个对象的作用域不会逃逸出方法之外,那可以将这个对象分配在栈上,这样,对象所占用的内存空间就可以随栈帧出栈而销毁。在一般应用中,不会逃逸的局部对象所占的比例很大,如果能使用栈上分配,那大量的对象就会随着方法的结束而自动销毁了,无须通过垃圾收集器回收,可以减小垃圾收集器的负载。
JVM允许将线程私有的对象打散分配在栈上,而不是分配在堆上。分配在栈上的好处是可以在函数调用结束后自行销毁,而不需要垃圾回收器的介入,从而提高系统性能。 栈上分配的技术基础:一是逃逸分析:逃逸分析的目的是判断对象的作用域是否有可能逃逸出函数体。关于逃逸分析的问题可以看我另一篇文章:
二是标量替换:允许将对象打散分配在栈上,比如若一个对象拥有两个字段,会将这两个字段视作局部变量进行分配。
只能在server模式下才能启用逃逸分析,参数-XX:DoEscapeAnalysis启用逃逸分析,参数-XX:+EliminateAllocations开启标量替换(默认打开)。Java SE 6u23版本之后,HotSpot中默认就开启了逃逸分析,可以通过选项-XX:+PrintEscapeAnalysis查看逃逸分析的筛选结果。
TLAB
TLAB的全称是Thread Local Allocation Buffer,即线程本地分配缓存区,这是一个线程专用的内存分配区域。 由于对象一般会分配在堆上,而堆是全局共享的。因此在同一时间,可能会有多个线程在堆上申请空间。因此,每次对象分配都必须要进行同步(虚拟机采用CAS配上失败重试的方式保证更新操作的原子性),而在竞争激烈的场合分配的效率又会进一步下降。
1 JVM使用TLAB来避免多线程冲突,在给对象分配内存时,每个线程使用自己的TLAB,这样可以避免线程同步,提高了对象分配的效率。 2 TLAB本身占用eEden区空间,在开启TLAB的情况下,虚拟机会为每个Java线程分配一块TLAB空间。参数-XX:+UseTLAB开启TLAB,默认是开启的。
3 TLAB空间的内存非常小,缺省情况下仅占有整个Eden空间的1%,当然可以通过选项-XX:TLABWasteTargetPercent设置TLAB空间所占用Eden空间的百分比大小。
4 由于TLAB空间一般不会很大,因此大对象无法在TLAB上进行分配,总是会直接分配在堆上。TLAB空间由于比较小,因此很容易装满。比如,一个100K的空间,已经使用了80KB,当需要再分配一个30KB的对象时,肯定就无能为力了。这时虚拟机会有两种选择,第一,废弃当前TLAB,这样就会浪费20KB空间;第二,将这30KB的对象直接分配在堆上,保留当前的TLAB,这样可以希望将来有小于20KB的对象分配请求可以直接使用这块空间。实际上虚拟机内部会维护一个叫作refill_waste的值,当请求对象大于refill_waste时,会选择在堆中分配,若小于该值,则会废弃当前TLAB,新建TLAB来分配对象。这个阈值可以使用TLABRefillWasteFraction来调整,它表示TLAB中允许产生这种浪费的比例。默认值为64,即表示使用约为1/64的TLAB空间作为refill_waste。默认情况下,TLAB和refill_waste都会在运行时不断调整的,使系统的运行状态达到最优。如果想要禁用自动调整TLAB的大小,可以使用-XX:-ResizeTLAB禁用ResizeTLAB,并使用-XX:TLABSize手工指定一个TLAB的大小。 -XX:+PrintTLAB可以跟踪TLAB的使用情况。一般不建议手工修改TLAB相关参数,推荐使用虚拟机默认行为。
对象内存分配的两种方法
为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来。
指针碰撞(Serial、ParNew等带Compact过程的收集器) 假设Java堆中内存是绝对规整的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离,这种分配方式称为“指针碰撞”(Bump the Pointer)。 空闲列表(CMS这种基于Mark-Sweep算法的收集器) 如果Java堆中的内存并不是规整的,已使用的内存和空闲的内存相互交错,那就没有办法简单地进行指针碰撞了,虚拟机就必须维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式称为“空闲列表”(Free List)。
总结
总体流程
对象分配流程如果开启栈上分配,JVM会先进行栈上分配,如果没有开启栈上分配或则不符合条件的则会进行TLAB分配,如果TLAB分配不成功,再尝试在eden区分配,如果对象满足了直接进入老年代的条件,那就直接分配在老年代。
对象在内存的引用方式
对象在内存中的结构
62 线程与进程的区别
1调度
2 并发
3 拥有资源
4系统开销
- 进程是资源的分配和调度的一个独立单元,而线程是CPU调度的基本单元
- 同一个进程中可以包括多个线程,并且线程共享整个进程的资源(寄存器、堆栈、上下文),一个进行至少包括一个线程。
- 进程的创建调用fork或者vfork,而线程的创建调用pthread_create,进程结束后它拥有的所有线程都将销毁,而线程的结束不会影响同个进程中的其他线程的结束
- 线程是轻量级的进程,它的创建和销毁所需要的时间比进程小很多,所有操作系统中的执行功能都是创建线程去完成的
- 线程中执行时一般都要进行同步和互斥,因为他们共享同一进程的所有资源
- 线程有自己的私有属性TCB,线程id,寄存器、硬件上下文,而进程也有自己的私有属性进程控制块PCB,这些私有属性是不被共享的,用来标示一个进程或一个线程的标志
- 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动
- 线程是进程的一个实体, 是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源
- 一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行
进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序 健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
63 线程池以及调优
64 给定一个进程,有多个线程,其中一个线程出现OOM异常,判断所有线程的状态
65、原子类实现
66 死锁的四个条件?
1互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
2 不可强行抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占用者,主动释放。
3 请求和保持,即当资源的请求者在请求其他的资源的同时保持对原有资源的占有
4 循环等待,即存在一个等待队列: P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。
67、
68、死锁与活锁的区别,死锁与饥饿的区别
死锁:是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。死锁发生的四个条件1、互斥条件:线程对资源的访问是排他性的,如果一个线程对占用了某资源,那么其他线程必须处于等待状态,直到资源被释放。2、请求和保持条件:线程T1至少已经保持了一个资源R1占用,但又提出对另一个资源R2请求,而此时,资源R2被其他线程T2占用,于是该线程T1也必须等待,但又对自己保持的资源R1不释放。3、不剥夺条件:线程已获得的资源,在未使用完之前,不能被其他线程剥夺,只能在使用完以后由自己释放。4、环路等待条件:在死锁发生时,必然存在一个“进程-资源环形链”,即:{p0,p1,p2,…pn},进程p0(或线程)等待p1占用的资源,p1等待p2占用的资源,pn等待p0占用的资源。(最直观的理解是,p0等待p1占用的资源,而p1而在等待p0占用的资源,于是两个进程就相互等待)
活锁:是指线程1可以使用资源,但它很礼貌,让其他线程先使用资源,线程2也可以使用资源,但它很绅士,也让其他线程先使用资源。这样你让我,我让你,最后两个线程都无法使用资源。
关于“死锁与活锁”的比喻:死锁:迎面开来的汽车A和汽车B过马路,汽车A得到了半条路的资源(满足死锁发生条件1:资源访问是排他性的,我占了路你就不能上来,除非你爬我头上去),汽车B占了汽车A的另外半条路的资源,A想过去必须请求另一半被B占用的道路(死锁发生条件2:必须整条车身的空间才能开过去,我已经占了一半,尼玛另一半的路被B占用了),B若想过去也必须等待A让路,A是辆兰博基尼,B是开奇瑞QQ的屌丝,A素质比较低开窗对B狂骂:快给老子让开,B很生气,你妈逼的,老子就不让(死锁发生条件3:在未使用完资源前,不能被其他线程剥夺),于是两者相互僵持一个都走不了(死锁发生条件4:环路等待条件),而且导致整条道上的后续车辆也走不了。例如:马路中间有条小桥,只能容纳一辆车经过,桥两头开来两辆车A和B,A比较礼貌,示意B先过,B也比较礼貌,示意A先过,结果两人一直谦让谁也过不去。
饥饿:是指如果线程T1占用了资源R,线程T2又请求封锁R,于是T2等待。T3也请求资源R,当T1释放了R上的封锁后,系统首先批准了T3的请求,T2仍然等待。然后T4又请求封锁R,当T3释放了R上的封锁之后,系统又批准了T4的请求……,T2可能永远等待。
关于”饥饿“的比喻:在“首堵”北京的某一天,天气阴沉,空气中充斥着雾霾和地沟油的味道,某个苦逼的临时工交警正在处理塞车,有两条道A和B上都堵满了车辆,其中A道堵的时间最长,B相对相对堵的时间较短,这时,前面道路已疏通,交警按照最佳分配原则,示意B道上车辆先过,B道路上过了一辆又一辆,A道上排队时间最长的确没法通过,只能等B道上没有车辆通过的时候再等交警发指令让A道依次通过,这也就是ReentrantLock显示锁里提供的不公平锁机制(当然了,ReentrantLock也提供了公平锁的机制,由用户根据具体的使用场景而决定到底使用哪种锁策略),不公平锁能够提高吞吐量但不可避免的会造成某些线程的饥饿。
69 JMM模型
https://zhuanlan.zhihu.com/p/258393139
70、Java中用到的线程调度算法是什么
抢占式:
协同式:调度指某一线程执行完后主动通知系统切换到另一线程上执行,这种模式就像接力赛一样,一个人跑完自己的路程就把接力棒交接给下一个人,下个人继续往下跑。线程的执行时间由线程本身控制,线程切换可以预知,不存在多线程同步问题,但它有一个致命弱点:如果一个线程编写有问题,运行到一半就一直堵塞,那么可能导致整个系统崩溃。
JVM 的线程调度实现(抢占式调度)java 使用的线程调使用抢占式调度,Java 中线程会按优先级分配 CPU 时间片运行,且优先级越高越优先执行,但优先级高并不代表能独自占用执行时间片,可能是优先级高得到越多的执行时间片,反之,优先级低的分到的执行时间少但不会分配不到执行时间。
线程让出 cpu 的情况:\1. 当前运行线程主动放弃 CPU,JVM 暂时放弃 CPU 操作(基于时间片轮转调度的 JVM 操作系统不会让线程永久放弃 CPU,或者说放弃本次时间片的执行权),例如调用 yield()方法。\2. 当前运行线程因为某些原因进入阻塞状态,例如阻塞在 I/O 上。\3. 当前运行线程结束,即运行完 run()方法里面的任务。
71、什么是线程组,为什么在Java中不推荐使用
1 线程组ThreadGroup对象中的stop,resume,suspend会导致安全问题,主要是死锁问题,已经被官方废弃,多以价值已经大不如以前。
2 线程组ThreadGroup不是线程安全的,在使用过程中不能及时获取安全的信息。
来自
72 什么状态下的线程可以中断
1. 线程中断的方法
方法名 | 描述 |
---|---|
public boolean isInterrupted() | 返回是否中断,不做其他事情 |
public void interrupt() | 设置中断标志位 |
public static boolean interrupted() | 返回中断标志位,并清除中断标志位 |
2. Java线程不同状态下中断机制的效果
状态 | 中断效果 | 描述 |
---|---|---|
NEW | 无 | |
RUNNABLE | 设置中断标志位 | 用户自己判断是否中断,以及如何处理,调用的方法能否相应异常 |
BLOCKED | 设置中断标志位 | 用户自己判断是否中断,以及如何处理 调用的方法能否相应异常 |
WAITING | 抛InterruptedException异常,并清空中断标志位 | |
TIMED_WAITING | 抛InterruptedException异常,并清空中断标志位 | |
TERMINATED | 无 |
Object.wait, Thread.join和Thread.sleep 会抛出异常。
一些的方法的拥塞是不能响应中断请求的,这类操作以I/O操作居多,但是可以让其抛出类似的异常,来停止任务:
Socket I/O: 关闭底层socket,所有因执行读写操作而拥塞的线程会抛出SocketException;
同步 I/O:大部分Channel都实现了InterruptiableChannel接口,可以响应中断请求,抛出异常ClosedByInterruptException;
Selector的异步 I/O:Selector执行select方法之后,再执行close和wakeUp方法就会抛出异常ClosedSelectorException
3 可中断阻塞与不可中断阻塞
https://blog.csdn.net/johnf_nash/article/details/81104863
1 对于处于sleep,join等操作的线程,如果被调用interrupt()后,会抛出InterruptedException,然后线程的中断标志位会由true重置为false,因为线程为了处理异常已经重新处于就绪状态。
2 不可中断的操作,包括获取锁(进入synchronized段)以及Lock.lock(),Java.io 包中的同步 I/O,Java.io 包中的同步 Socket IO (对套接字进行读取和写入的操作:InputStream 和 OutputStream 中的read和write等),Java.nio包中的Selector的异步I/O 等。调用interrupt()对于这几个问题无效,因为它们都不抛出中断异常。如果拿不到资源,它们会无限期阻塞下去。
3 对于Lock.lock(),可以改用Lock.lockInterruptibly(),可被中断的加锁操作,它可以抛出中断异常。等同于等待时间无限长的Lock.tryLock(long time, TimeUnit unit)。
4 对于inputStream等资源,有些(实现了interruptibleChannel接口)可以通过close()方法将资源关闭,对应的阻塞也会被放开。
5使用Java1.0之前就存在的传统的I/O,Thread.interrupt()将不起作用,因为线程将不会退出被阻塞状态。
6 很幸运,对于 Socket 同步 I/O,Java平台为这种情形提供了一项解决方案,即调用阻塞该线程的套接字的close()方法。在这种情形下,如果线程被I/O操作阻塞,当调用该套接字的close方法时,该线程在调用accept地方法将接收到一个SocketException(SocketException为IOException的子异常)异常,这与使用interrupt()方法引起一个InterruptedException异常被抛出非常相似。
7 java.nio类库提供了更加人性化的I/O中断,被阻塞的nio通道会自动地响应中断,不需要关闭底层资源;
对于非标准的取消操作,我们可以一些方法来对它进行封装(如通过改写interrupt方法来将它封装在 Thread 中,通过 newTaskFor 等)。
74 Thread.sleep实现原理
sleep():进程、线程或任务(Linux中不区分进程与线程,都称为task)可以sleep,这会导致它们暂停执行一段时间,直到等待的时间结束才恢复执行或在这段时间内被中断。sleep()在OS中的实现的大概流程:
- 挂起进程(或线程)并修改其运行状态
- 用sleep()提供的参数来设置一个定时器。
- 当时间结束,定时器会触发,内核收到中断后修改进程(或线程)的运行状态。例如线程会被标志为就绪而进入就绪队列等待调度。
可变定时器(variable timer)一般在硬件层面是通过一个固定的时钟和计数器来实现的,每经过一个时钟周期将计数器递减,当计数器的值为0时产生中断。内核注册一个定时器后可以在一段时间后收到中断
75 线程池参数考虑
1 io型或cpu型
2 经验值
IO密集型=2Ncpu(可以测试后自己控制大小,2Ncpu一般没问题)(常出现于线程中:数据库数据交互、文件上传下载、网络数据传输等等)
计算密集型=Ncpu(常出现于线程中:复杂算法)
3 后台工作的线程数
4 内存堆栈是否会爆
5 压测 观察cpu负载 50-60%
6 动态线程池
76 内存泄漏
1 connect资源类的
2 thread local
3 静态集合
4 WeakHashMap代表缓存,此种Map的特点是,当除了自身有对key的引用外,此key没有其他引用那么此map会自动丢弃此值
5 改变哈希值,当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,造成内存泄露
6 内部类持有外部类,如果一个外部类的实例对象的方法返回了一个内部类的实例对象,这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持有外部类的实例对象,这个外部类对象将不会被垃圾回收,这也会造成内存泄露
7 hander类型
一段苹果的广告语,致疯狂的人:
他们特立独行。他们桀骜不驯。他们惹是生非。他们格格不入。他们用与众不同的眼光看待事物。他们不喜欢墨守成规。他们也不愿安于现状。你可以认同他们,反对他们,颂扬或是诋毁他们。但唯独不能漠视他们。因为他们改变了寻常事物。他们推动人类向前迈进。或许他们是别人眼里的疯子,但他们却是我们眼中的天才。因为只有那些疯狂到以为自己能够改变世界的人,才能真正改变世界。