java线程相关基础知识

多线程

多线程面试题TOP50
基本概念
  • 什么是线程
  • 多线程的优点
  • 多线程的几种实现方式
线程和进程的区别
引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位

进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。 但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
1) 简而言之,一个程序至少有一个进程,一个进程至少有一个线程.
2) 线程的划分尺度小于进程,使得多线程程序的并发性高。
3) 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
4)   线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。 但是线程不能够独立执行, 必须依存在应用程序中,由应用程序提供多个线程执行控制。
5)   从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。 这就是进程和线程的重要区别。
多线程的优点 :使用并发的方式,提升程序运行效率
实现方式有三种:
(1)继承Thread类,调用start()方法
(2)实现Runnable接口, new Thread(Runnable).start();
(3)实现Callable接口,可以返回结构Future也可以抛出异常

用 Runnable 还是 Thread
首选Runnable,因为这样这个类还可以继承。
  • 什么是线程安全
如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象是线程安全的。
  1. Vector, SimpleDateFormat 是线程安全类吗
  2. 哪些集合类是线程安全的
Vector:就比Arraylist多了个同步化机制(线程安全)。
Hashtable:就比Hashmap多了个线程安全。
ConcurrentHashMap:是一种高效但是线程安全的集合。
Stack:栈,也是线程安全的,继承于Vector。

Vector是线程安全, SimpleDateFormat不是线程安全的。
  • 多线程中的忙循环是什么
忙循环就是程序员用循环让一个线程等待,不像传统方法wait(), sleep() 或 yield() 它们都放弃了CPU控制,而忙循环不会放弃CPU,它就是在运行一个空循环。这么做的目的是为了保留CPU缓存,在多核系统中,一个等待线程醒来的时候可能会在另一个内核运行,这样会重建缓存。

  • 进程间如何通讯,线程间如何通讯(后面补)
(1) 管道(pipe):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有血缘关系的进程间使用。进程的血缘关系通常指父子进程关系。
(2)有名管道(named pipe):有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间通信。
(3)信号量(semophore):信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它通常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
(4)消息队列(message queue):消息队列是由消息组成的链表,存放在内核中 并由消息队列标识符标识。消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。
(5)信号(signal):信号是一种比较复杂的通信方式,用于通知接收进程某一事件已经发生。
(6)共享内存(shared memory):共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问,共享内存是最快的IPC方式,它是针对其他进程间的通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量配合使用,来实现进程间的同步和通信。
(7)套接字(socket):套接口也是一种进程间的通信机制,与其他通信机制不同的是它可以用于不同及其间的进程通信。
  • 什么是多线程环境下的伪共享(false sharing) 
伪共享的非标准定义为:缓存系统中是以缓存行(cache line)为单位存储的,当多线程修改互相独立的变量时,如果这些变量共享同一个缓存行,就会无意中影响彼此的性能,这就是伪共享。

  • 同步和异步有何异同,在什么情况下分别使用他们?举例说明
如果数据将在线程间共享。例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就是共享数据,必须进行 同步存取
当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用 异步编程 ,在很多情况下采用异步途径往往更有效率。
Java中交互方式分为同步和异步两种:
同步交互:指发送一个请求,需要等待返回,然后才能够发送下一个请求,有个等待过程;
异步交互:指发送一个请求,不需要等待返回,随时可以再发送下一个请求,即不需要等待。 
区别:一个需要等待,一个不需要等待,在部分情况下,我们的项目开发中都会优先选择不需要等待的异步交互方式。
哪些情况建议使用同步交互呢?比如银行的转账系统,对数据库的保存操作等等,都会使用同步交互操作,其余情况都优先使用异步交互


Current
  • ConcurrentHashMap 和 Hashtable的区别
Hashtable和ConcurrentHashMap有什么分别呢?它们都可以用于多线程的环境,但是当Hashtable的大小增加到一定的时候,性能会急剧下降,因为迭代时需要被锁定很长的时间。因为ConcurrentHashMap引入了分割(segmentation),不论它变得多么大,仅仅需要锁定map的某个部分,而其它的线程不需要等到迭代完成才能访问map。简而言之,在迭代的过程中,ConcurrentHashMap仅仅锁定map的某个部分,而Hashtable则会锁定整个map。

  • ArrayBlockingQueue的用法
CyclicBarrier 和 CountDownLatch有什么不同?各自的内部原理和用法是什么
Semaphore的用法
Java中的并发工具类:
(1)等待多线程完成的CountDownLatch
允许一个或多个线程等待其他线程完成操作。当调用countDownLatch的countDown方法,N就减1,await方法会阻塞当前线程,知道N变为零。
也可以把countDown引用传入 ,用于执行1个线程中的N个执行步骤
用法介绍:
  1. 创建一个计数器,设置初始值,CountdownLatch countDownLatch = new CountDownLatch(2);
  2. 在 等待线程 里调用 countDownLatch.await() 方法,进入等待状态,直到计数值变成 0;
  3. 在 其他线程 里,调用 countDownLatch.countDown() 方法,该方法会将计数值减小 1;
  4. 当 其他线程 的 countDown() 方法把计数值变成 0 时,等待线程 里的 countDownLatch.await() 立即退出,继续执行下面的代码。
实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private static void runDAfterABC() {
     int worker = 3 ;
     CountDownLatch countDownLatch = new CountDownLatch(worker);
     new Thread( new Runnable() {
         @Override
         public void run() {
             System.out.println( "D is waiting for other three threads" );
             try {
                 countDownLatch.await();
                 System.out.println( "All done, D starts working" );
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }
     }).start();
     for ( char threadName= 'A' ; threadName <= 'C' ; threadName++) {
         final String tN = String.valueOf(threadName);
         new Thread( new Runnable() {
             @Override
             public void run() {
                 System.out.println(tN + "is working" );
                 try {
                     Thread.sleep( 100 );
                 } catch (Exception e) {
                     e.printStackTrace();
                 }
                 System.out.println(tN + "finished" );
                 countDownLatch.countDown();
             }
         }).start();
     }
}

下面是运行结果:

1
2
3
4
5
6
7
8
9
D is waiting for other three threads
A is working
B is working
C is working
 
A finished
C finished
B finished
All done, D starts working

其实简单点来说,CountDownLatch 就是一个倒计数器,我们把初始计数值设置为3,当 D 运行时,先调用 countDownLatch.await() 检查计数器值是否为 0,若不为 0 则保持等待状态;当A B C 各自运行完后都会利用countDownLatch.countDown(),将倒计数器减 1,当三个都运行完后,计数器被减至 0;此时立即触发 D 的 await() 运行结束,继续向下执行。
因此,CountDownLatch 适用于一个线程去等待多个线程的情况。

(2)CyclicBarrier
让一组线程到达一个屏障(同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续执行。当到达屏障时,当前线程调用CyclicBarrier.await()方法,告诉CyclicBarrier已经到达屏障。
默认构造方法CyclicBarrier(int parties)
高级构造方法CyclicBarrier(int partie, Runnable barrierAction)在线程到达屏障时,优先执行barrierAction
两者区别:
CountDownLatch的计数器只能使用一次,CyclicBarrier的计数器可以使用reset方法重置
CyclicBarrier还有其他方法,
getNumberWaiting(获得CyclicBarrier阻塞的线程数量)
isBroken(阻塞的线程是否被中断)
使用方法:
  1. 先创建一个公共 CyclicBarrier 对象,设置 同时等待 的线程数,CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
  2. 这些线程同时开始自己做准备,自身准备完毕后,需要等待别人准备完毕,这时调用 cyclicBarrier.await(); 即可开始等待别人;
  3. 当指定的 同时等待 的线程数都调用了 cyclicBarrier.await();时,意味着这些线程都准备完毕好,然后这些线程才 同时继续执行。
  4. 可以实现线程间相互等待
实现代码如下,设想有三个跑步运动员,各自准备好后等待其他人,全部准备好后才开始跑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private static void runABCWhenAllReady() {
     int runner = 3 ;
     CyclicBarrier cyclicBarrier = new CyclicBarrier(runner);
     final Random random = new Random();
     for ( char runnerName= 'A' ; runnerName <= 'C' ; runnerName++) {
         final String rN = String.valueOf(runnerName);
         new Thread( new Runnable() {
             @Override
             public void run() {
                 long prepareTime = random.nextInt( 10000 ) + 100 ;
                 System.out.println(rN + "is preparing for time:" + prepareTime);
                 try {
                     Thread.sleep(prepareTime);
                 } catch (Exception e) {
                     e.printStackTrace();
                 }
                 try {
                     System.out.println(rN + "is prepared, waiting for others" );
                     cyclicBarrier.await(); // 当前运动员准备完毕,等待别人准备好
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 } catch (BrokenBarrierException e) {
                     e.printStackTrace();
                 }
                 System.out.println(rN + "starts running" ); // 所有运动员都准备好了,一起开始跑
             }
         }).start();
     }
}

打印的结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
A is preparing for time: 4131
B is preparing for time: 6349
C is preparing for time: 8206
 
A is prepared, waiting for others
 
B is prepared, waiting for others
 
C is prepared, waiting for others
 
C starts running
A starts running
B starts running

(3)Semaphore控制并发线程数数的信号量
可以用作流量控制,特别是公用资源有限的应用场景,比如数据库连接。
Semaphore的acquire()方法获取一个许可证  relese()方法归还许可证,构造函数Semaphore(10)
其他方法
int availablePermits()返回此信号量当前可用的许可证数
int getQueueLength()返回正在等待获取许可证的线程数数
boolean hasQueuedThreads()是否有线程正在等待获取许可证
void reducePermits(int reduction)减少reduction个许可证,是protected方法
Collection getQueuedThreads()返回所有等待获取许可证的线程集合,是protected方法
(4)线程交换数据的Exchanger
用于进行线程间的数据交换。可以用于遗传算法,或者用于校对工作。
如果第一个线程先执行了exchange()方法,它会一直等待第二个线程也执行exchange()方法。当两个线程都达到同步点时,这两个线程就可以交换数据。


Thread
  • 启动一个线程是调用 run() 还是 start() 方法?start() 和 run() 方法有什么区别
  • 调用start()方法时会执行run()方法,为什么不能直接调用run()方法
  • sleep() 方法和对象的 wait() 方法都可以让线程暂停执行,它们有什么区别
  • yield方法有什么作用?sleep() 方法和 yield() 方法有什么区别
  • Java 中如何停止一个线程
  • stop() 和 suspend() 方法为何不推荐使用
  • 如何在两个线程间共享数据
  • 如何强制启动一个线程
  • 如何让正在运行的线程暂停一段时间
  • 什么是线程组,为什么在Java中不推荐使用
  • 你是如何调用 wait(方法的)?使用 if 块还是循环?为什么
生命周期
  • 有哪些不同的线程生命周期
  • 线程状态,BLOCKED 和 WAITING 有什么区别
  • 画一个线程的生命周期状态图
ThreadLocal 用途是什么,原理是什么,用的时候要注意什么

ThreadPool
  • 线程池是什么?为什么要使用它
线程池:基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。
线程池的好处:1. 降低资源消耗。2. 提高响应速度。3. 提高线程的可管理性

  • 如何创建一个Java线程池
  • ThreadPool用法与优势
  • 提交任务时,线程池队列已满时会发会生什么
  • newCache 和 newFixed 有什么区别?简述原理。构造函数的各个参数的含义是什么,比如 coreSize, maxsize 等
  • 线程池的实现策略
  • 线程池的关闭方式有几种,各自的区别是什么
  • 线程池中submit() 和 execute()方法有什么区别?
  • 线程池的原理是什么
ThreadPool的原理 :当向一个线程池提交任务时,处理流程如下
(1)核心线程数 :  线程池判断核心线程池里的线程是否都在执行任务,如果不是,则创建一个新的工作线程,如果核心线程池里的线程都在执行任务,则进入下个流程。
(2)队列:线程池判断工作队列是否已经满,如果工作队列没有满,则将提交的任务存储在这个工作队列中
(3)线程池最大线程数:如果工作队列慢了,则线程池判断线程池中的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。
(4)饱和策略:

线程池的创建:参数
(1)corePoolSize:提交任务到线程池,会创建新的任务,直到达到corePoolSize大小
(2)maximumPoolSize:线程池最大数量,
(3)keepAliveTime:线程活动保持时间
(4)TimeUit线程活动保持时间的单位
(5)runnableTaskQueue任务队列 
ArrayBlockingQueue基于数据的有界队列   
LinkedBlockingQueue基于链表的无界队列
synchronousQueue 不存储元素的阻塞队列 
PriorityBlockingQueue 优先级无限
(6)RejectedExecutionHandler饱和策略
AbortPolicy直接抛出异常
CallerRunsPolicy 只用调用者所在线程来运行任务
DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
DiscardPolicy不处理,丢弃掉。

提交任务submit和execute方法
execute用于提交不需要返回值的任务
submit用于提交需要返回值的任务,线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功。
future.get()获取返回值。

关闭线程池
原理:遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程
shutdown关闭所有,不关闭正在执行的任务
shutdownNow关闭所有包括正在执行的任务


Executor接口
ThreadPoolExecutor        corePool        maximumPool    keepAliveTime          BlockingPool       RejectedExecutionHandler    
FixedThreadPool              nThreads           nThreads                   0                 LinkedBlockingQueue           无
SingleThreadExecutor      1                        1                                0                   LinkedBlockingQueue          无
CachedThreadPool            0                Integer.MAX_VALUE       60秒              SynchronousQueue              

CachedThreadPool
(1)执行SynchronousQueue.offer(Runnable task)方法,如果有空闲线程正在执行Synchronous.poll方法,执行execute
(2)如果maximumPool中没有线程执行poll,则创建一个线程执行execute方法
(3)步骤2中新创建的线程执行完后,会执行poll方法,这个poll让空闲线程最多等待60秒,如果60秒内有新任务则执行execute否则空闲线程终止。    
ScheduledThreadPoolExecutor详解执行步骤
(1)获取任务,获取已经到期的任务 (DelayQueue.take())  
(2)执行ScheduledFutureTask
(3)修改ScheduledFutureTask的time变量为下次将要被执行的时间。
FutureTask
三种状态
未启动
已经启动
已完成
 方法执行示意图

线程调度
  • Java中用到的线程调度算法是什么
一般线程调度模式分为两种——抢占式调度和协同式调度。抢占式调度指的是每条线程执行的时间、线程的切换都由系统控制,系统控制指的是在系统某种运行机制下,可能每条线程都分同样的执行时间片,也可能是某些线程执行的时间片较长,甚至某些线程得不到执行的时间片。在这种机制下,一个线程的堵塞不会导致整个进程堵塞。协同式调度指某一线程执行完后主动通知系统切换到另一线程上执行,这种模式就像接力赛一样,一个人跑完自己的路程就把接力棒交接给下一个人,下个人继续往下跑。线程的执行时间由线程本身控制,线程切换可以预知,不存在多线程同步问题,但它有一个致命弱点:如果一个线程编写有问题,运行到一半就一直堵塞,那么可能导致整个系统崩溃。
JVM的线程调度是抢占式的。
  • 什么是多线程中的上下文切换
即使是单核CPU也支持多线程执行代码,CPU通过给每个线程分配CPU时间片来实现这个机制。时间片是CPU分配给各个线程的时间,因为时间片非常短,所以CPU通过不停地切换线程执行,让我们感觉多个线程时同时执行的,时间片一般是几十毫秒(ms)。
CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。但是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再次加载这个任务的状态, 从任务保存到再加载的过程就是一次上下文切换
  • 你对线程优先级的理解是什么
线程会被分配到若干时间片,当线程的时间片用完了就会发生线程调度,并等待着下次分配。线程分配到的时间片多少也就决定了线程使用处理器资源的多少,而线程优先级就是决定线程需要多或者少分配一些处理器资源的线程属性。
什么是线程调度器 (Thread Scheduler) 和时间分片 (Time Slicing)
线程调度器是一个操作系统服务,它负责为 Runnable 状态的线程分配CPU时间。一旦我们创建一个线程并启动它,它的执行便依赖于线程调度器的实现。时间分片是指将可用的CPU时间分配给可用的 Runnable 线程的过程。分配CPU时间可以基于线程优先级或者线程等待的时间。线程调度并不受到Java虚拟机控制,所以由应用程序来控制它是更好的选择(也就是说不要让你的程序依赖于线程的优先级)。
线程同步
  • 请说出你所知的线程同步的方法
  • synchronized 的原理是什么
  • synchronized 和 ReentrantLock 有什么不同
  • 什么场景下可以使用 volatile 替换 synchronized  (currentHashMap的底层实现就用volatile代替了synchronized)
  • 有T1,T2,T3三个线程,怎么确保它们按顺序执行?怎样保证T2在T1执行完后执行,T3在T2执行完后执行
  • 同步块内的线程抛出异常会发生什么
  • 当一个线程进入一个对象的 synchronized 方法A 之后,其它线程是否可进入此对象的 synchronized 方法B
  • 使用 synchronized 修饰静态方法和非静态方法有什么区别
  • 如何从给定集合那里创建一个 synchronized 的集合
我们可以使用Collections.synchronizedCollection(Collection c)根据指定集合来获取一个synchronized(线程安全的)集合。
线程同步的方法:
(1)synchronized关键字和volatile
volatile关键字用来修饰成员变量,保证内存的可见性。
对象,监视器,同步队列,执行线程之间的关系
任意线程对Object(Object由synchronized保护)的访问,首先要获得Object的监视器。如果获取失败,线程进入同步队列,线程状态编程BLOCKED。当访问Object的前驱(获得了锁的线程)释放了锁,则该释放操作唤醒阻塞在同步队列中的线程,使其重新尝试对监视器的获取。
(2)等待通知机制
调用wait方法,同时释放了lock的锁。
等待通知范式如下:
synchronized(对象){
    while(条件不满足){
        对象.wait()
    }
}
调用notify方法,通知时不释放lock的锁,直到当前线程释放了lock后,waitThread才能从wait中返回。
synchronized(对象){
      改变条件
     对象.notifyAll()
}
等待队列和同步队列
waitThread调用wait()进入等待队列,notifyThread调用notifyAll()将等待队列中的线程移动到同步队列,同步队列中的线程争夺Monitor,得到Monitor的线程继续执行,如果是notify()方法,这个对象的同队列中只有一个线程,那就直接执行。
(3)管道
面向字节
PipedOutputStream
PipedIntputStream
面向字符
PipedReader
PipedWriter
(4)Thread.join()
线程A中执行了join:当前线程A等待thread线程终止之后才能从thread.join()中返回。
synchronized join() {
        while(isAlive()){
            wait(0);
        }
}
当线程终止时,会调用线程自身的notifyAll方法,会通知所有等待在该线程对象上的线程
(5)ThreadLocal的使用
线程局部变量
每个线程都有一个LocalMap的数据结构, ThreadLocal对象为建,任意对象为值的存储结构。
一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值。
可以通过set(T)来设置这个值。通过get(T)获取到原来设置的值。


  • Java Concurrency API 中 的 Lock 接口是什么?对比同步它有什么优势
  • Lock 与 Synchronized 的区别?Lock 接口比 synchronized 块的优势是什么
  • ReadWriteLock是什么?
  • 锁机制有什么用
  • 什么是乐观锁(Optimistic Locking)?如何实现乐观锁?如何避免ABA问题
  • 解释以下名词:重排序,自旋锁,偏向锁,轻量级锁,可重入锁,公平锁,非公平锁,乐观锁,悲观锁
  • 什么时候应该使用可重入锁
  • 简述锁的等级方法锁、对象锁、类锁
  • Java中活锁和死锁有什么区别?
  • 什么是死锁(Deadlock)?导致线程死锁的原因?如何确保 N 个线程可以访问 N 个资源同时又不导致死锁
  • 死锁与活锁的区别,死锁与饥饿的区别
  • 怎么检测一个线程是否拥有锁
  • 如何实现分布式锁
  • 有哪些无锁数据结构,他们实现的原理是什么
  • 读写锁可以用于什么应用场景
使用版本号避免ABA问题
AtomicStampedReference 来解决ABA问题,这个类的compareAndSet方法的作用是首先检查当前引用是否等于预期引用,并且检查当前标识是否等于预期标志。如果全部相等,则以院子方式将该引用和该标志的值设置为给定的更新值。 
Lock和Synchronized的区别是什么?
(1)Lock可以非阻塞的获取锁
(2)能被中断地获取锁
(3)可以超时获取锁

AbstractQueuedSynchronizer队列同步器(AQS)
是用来构建锁或者其他同步组件的基础框架,它使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。
可以用三种方法来修改同步状态
getState()获取当前同步状态
setState(int newState)设置当前同步状态
compareAndSetState(int expect, int update)使用CAS设置当前状态,该方法能够保证状态设置的原子性。
通过将操作代理在自定义的队列同步器上,可以方便的实现一个自定义同步组件。

ReentrantLock可重入锁(也是排他锁),允许锁上加锁,默认是以 非公平性锁来获取锁
在nonfairTryAcquire方法获得锁中,通过判断当前线程是否为获取锁的线程来决定获取操作是否成功,如果是,将同步状态值进行增加并返回true。
在tryRelease方法找那个释放所,将c减去传入参数releases,当c==0时才释放锁。
如果是公平性锁,会在tryAcquire中CAS的判断处增加hasQueuedPredecessors方法判断是否有前驱节点。

公平获得锁:在绝对时间上,先对锁进行获取的请求一定先被满足。所得获取顺序符合请求的绝对时间顺序,FIFO
非公平获得锁
公平锁保证了锁的获取按照FIFO原则,而代价是进行大量的线程切换。
非公平性锁虽然可能造成线程“饥饿”,但极少的线程切换,保证了其更大的吞吐量,提高了执行效率。

读写锁ReentrantReadWriteLock
排他锁的概念:在同一时刻只允许一个线程进行访问。
而读写锁在同一时刻可以允许多个读线程访问,但是在写线程访问时,所有的读线程和其他的写线程军被阻塞。
读写锁支持:
(1)公平获取锁和非公平获取锁(默认)
(2)重进入
(3)锁降级:指写锁降级称为读锁,具体是指把持住(当前拥有的)写锁,再获取到读锁,随后释放(先前拥有的)写锁的过程。
读写锁的实现: 
维护一个整型变量,高16位表示读,低16位表示写。

写锁的获取与释放
如果存在读锁,则写锁不能被获取,因为读写锁要确保写锁的操作对读锁可见,如果允许读锁在已被获取的情况下对写锁的获取,那么正在运行的其他读线程就无法感知到当前写线程的操作。因此,只有等待其他线程都释放了读锁,写锁才能被当前线程获取,而写锁一旦被获取,则其他读写线程的后序访问均被阻塞。

读锁的获取与释放
读状态时所有线程获取读次数的总和。
读锁的次数保存在ThreadLocal中,由线程自身维护。
读锁的获取,如果其他线程获得了写锁,则当前线程获取读锁失败,进入等待状态。如果当前线程获取了写锁或者写锁未被获取,则当前线程增加读状态,成功获取读锁。
读锁的释放(线程安全,可能有多个读线程同时释放读锁),减少值是1<<16

锁降级:其中的读锁获取是必须要的,保证数据可见性。因为当前线程在释放写锁之前获取读锁,可以阻塞其他写线程获取写锁进行数据更新,保证数据时正确的。
不支持锁升级,也是因为要保证数据可见性,如果读锁已经被多个线程获取,如果写线程更新数据对于其他读线程不可见,读出的数据时有问题的。(而且在读锁被持有的时候,是不能获取到写锁的)

LockSupport工具
park()撤销线程的许可
unpark()给线程许可(permit)

Condition接口


Executors类是什么? Executor和Executors的区别
什么是Java线程转储(Thread Dump),如何得到它
如何在Java中获取线程堆栈
说出 3 条在 Java 中使用线程的最佳实践
在线程中你怎么处理不可捕捉异常
实际项目中使用多线程举例。你在多线程环境中遇到的常见的问题是什么?你是怎么解决它的
请说出与线程同步以及线程调度相关的方法
程序中有3个 socket,需要多少个线程来处理
假如有一个第三方接口,有很多个线程去调用获取数据,现在规定每秒钟最多有 10 个线程同时调用它,如何做到
如何在 Windows 和 Linux 上查找哪个线程使用的 CPU 时间最长
如何确保 main() 方法所在的线程是 Java 程序最后结束的线程
非常多个线程(可能是不同机器),相互之间需要等待协调才能完成某种工作,问怎么设计这种协调方案
你需要实现一个高效的缓存,它允许多个用户读,但只允许一个用户写,以此来保持它的完整性,你会怎样去实现它


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值