本文主要是介绍【并发编程】AQSSemaphore(信号量) CountDownLatch (倒计时器) CyclicBarrier(循环栅栏) ThreadLocal,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
目录
AQS相关
Semaphore(信号量)
CountDownLatch (倒计时器)
CyclicBarrier(循环栅栏)
ThreadLocal
JMM的理解
Thread 的start() 和 run() 方法区别?
Thread类中的yield方法?
如何停止一个线程?
synchronized和ReentrantLock异同?
SynchronizedMap和ConcurrentHashMap·?
interrupted 和 isInterrupted区别?
为什么wait和notify要在同步块中调用?
为什么wait, notify 和 notifyAll这些方法不在thread类里面?
AQS相关
中文就是抽象队列同步器。
AQS 是一个用来构建锁和同步器的框架,能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的 ReentrantLock
,Semaphore
等等皆是基于 AQS 的。
AQS 核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS 是基于 CLH 锁 实现的。
CLH 锁是对自旋锁的一种改进,是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系),暂时获取不到锁的线程将被加入到这个队列中。AQS 将每条请求共享资源的线程封装成一个 CLH 队列锁的一个结点(Node)来实现锁的分配。在 CLH 队列锁中,一个节点表示一个线程,它保存着线程的引用(thread)、 当前节点在队列中的状态(waitStatus)、前驱节点(prev)、后继节点(next)。
同步工具类:
Semaphore(信号量)
semaphore就是一个信号量,作用是限制某段代码块的并发数。Semaphore有一个构造函数,可以传入一个int型整数n,表示某段代码最多只有n个线程可以访问,如果超出了n,那么请等待,等到某个线程执行完毕这段代码块,下一个线程再进入。
由此可以看出如果Semaphore构造函数中传入的int型整数n=1,相当于变成了一个synchronized了。
CountDownLatch (倒计时器)
CountDownLatch是一种同步工具类,用于实现线程之间的等待和协调。它的原理是通过一个计数器来实现的,计数器的初始值可以设置为任意正整数,每当一个线程完成了一定的任务,就将计数器的值减1,当计数器的值达到零时,表示所有线程都已经完成任务,等待在CountDownLatch上的线程将被唤醒。
CountDownLatch的主要方法包括:
-
CountDownLatch(int count)
:构造一个CountDownLatch对象,指定初始计数器的值。 -
void countDown()
:将计数器的值减1。如果计数器的值变为零,则唤醒所有等待的线程。 -
void await()
:阻塞当前线程,直到计数器的值变为零。
CyclicBarrier(循环栅栏)
是一种同步工具类,用于控制多个线程在某个屏障点上进行等待,然后同时继续执行。它的原理是通过一个计数器和一个屏障点来实现的,计数器的初始值可以设置为任意正整数,当线程到达屏障点时,将计数器的值减1,当计数器的值达到零时,所有等待在CyclicBarrier上的线程将被唤醒。
CyclicBarrier的主要方法包括:
-
CyclicBarrier(int parties)
:构造一个CyclicBarrier对象,指定参与线程的数量。 -
int await()
:线程调用该方法进入等待状态,直到计数器的值达到零。返回一个整数,表示该线程是第几个到达屏障点的线程。 -
int await(long timeout, TimeUnit unit)
:线程调用该方法进入等待状态,直到计数器的值达到零或超时。返回一个整数,表示该线程是第几个到达屏障点的线程。
ThreadLocal
threadLocal可以理解为线程本地变量,他会在每个线程都创建一个副本,那么在线程之间访问内部副本变量就行了,做到了线程之间互相隔离。
原理:ThreadLocal有一个静态内部类ThreadLocalMap,ThreadLocalMap又包含了一个Entry数组,Entry本身是一个弱引用,他的key是指向ThreadLocal的弱引用,Entry具备了保存key value键值对的能力。
内存泄露问题:
ThreadLocalMap
中使用的 key 为 ThreadLocal
的弱引用,而 value 是强引用。所以,如果 ThreadLocal
没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而 value 不会被清理掉。
这样一来,ThreadLocalMap
中就会出现 key 为 null 的 Entry。假如不做任何措施的话,value 永远无法被 GC 回收,这个时候就可能会产生内存泄露。ThreadLocalMap
实现中已经考虑了这种情况,在调用 set()
、get()
、remove()
方法的时候,会清理掉 key 为 null 的记录。使用完 ThreadLocal
方法后最好手动调用remove()
方法
JMM的理解
JMM是Java定义的一种抽象的计算机内存模型,它规定了多线程程序在访问共享内存时的行为规则。JMM定义了一组规则和屏障,用于确保多线程程序的可见性、有序性和正确性。
需要JMM的原因:
原因是多线程编程中存在着复杂的并发问题,如数据竞争、内存重排序等。如果没有明确的内存模型来规范多线程程序的行为,开发者很难编写正确的、可靠的多线程程序。JMM提供了一套规则和语义,使得开发者能够更好地理解多线程编程中的问题,并通过合适的同步机制和内存访问约束来确保程序的正确性和可靠性。JMM的存在使得多线程编程更加可控和可靠,提高了多线程程序的开发效率和可移植性。
happen-before关系: JMM定义了happen-before关系,它是一种偏序关系,用于确定不同操作之间的执行顺序。happen-before关系的存在可以帮助开发者推断多线程程序中的执行结果。
Thread 的start() 和 run() 方法区别?
start()方法被用来启动新创建的线程,而且start()内部调用了run()方法,这和直接调用run()方法的效果不一样。直接执行 run()
方法的话不会以多线程的方式执行。当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。
Thread类中的yield方法?
Yield方法可以暂停当前正在执行的线程对象,让其它有相同优先级的线程执行。它是个静态方法,只保证当前线程放弃CPU占用而不能保证使其它线程一定能占用CPU,执行yield()的线程有可能在进入到暂停状态后马上又被执行。
如何停止一个线程?
1、使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
2、使用stop方法强行终止,但是不推荐这个方法,因为stop和suspend及resume一样都是过期作废的方法。
3、使用interrupt方法中断线程。
synchronized和ReentrantLock异同?
相似点:
都是可重入锁,都是阻塞式的加锁方式同步,也就是说当如果一个线程获得了对象锁,进入了同步块,其他访问该同步块的线程都必须阻塞在同步块外面等待,而进行线程阻塞和唤醒的代价是比较高的.
区别:
这两种方式最大区别就是对于Synchronized来说,它是java语言的关键字,是原生语法层面的互斥,需要jvm实现。而ReentrantLock它是JDK 1.5之后提供的API层面的互斥锁,需要lock()和unlock()方法配合try/finally语句块来完成。
相比synchronized
,ReentrantLock
增加了一些高级功能。主要来说主要有三点:
-
等待可中断 :
ReentrantLock
提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()
来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。 -
可实现公平锁 :
ReentrantLock
可以指定是公平锁还是非公平锁。而synchronized
只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。ReentrantLock
默认情况是非公平的,可以通过ReentrantLock
类的ReentrantLock(boolean fair)
构造方法来制定是否是公平的。 -
可实现选择性通知(锁可以绑定多个条件):
synchronized
关键字与wait()
和notify()
/notifyAll()
方法相结合可以实现等待/通知机制。ReentrantLock
类当然也可以实现,但是需要借助于Condition
接口与newCondition()
方法。
SynchronizedMap和ConcurrentHashMap·?
-
SynchronizedMap()和Hashtable一样,在调用map所有方法时,都对整个map进行同步,只要有一个线程访问map,其他线程就无法进入map,而ConcurrentHashMap的实现却更加精细,它对map中的所有桶加了锁。所以,而如果一个线程在访问ConcurrentHashMap某个桶时,其他线程,仍然可以对map执行某些操作。
-
所以,ConcurrentHashMap在性能以及安全性方面,明显比synchronizedMap()更加有优势。同时,同步操作精确控制到桶,这样,即使在遍历map时,如果其他线程试图对map进行数据修改,也不会抛出ConcurrentModificationException。
interrupted 和 isInterrupted区别?
interrupted() 和 isInterrupted()的主要区别是前者会将中断状态清除而后者不会。
Java多线程的中断机制是用内部标识来实现的,调用Thread.interrupt()来中断一个线程就会设置中断标识为true。当中断线程调用静态方法Thread.interrupted()来检查中断状态时,中断状态会被清零。而非静态方法isInterrupted()用来查询其它线程的中断状态且不会改变中断状态标识。简单的说就是任何抛出InterruptedException异常的方法都会将中断状态清零。无论如何,一个线程的中断状态有有可能被其它线程调用中断来改变。
为什么wait和notify要在同步块中调用?
-
只有在调用线程拥有某个对象的独占锁时,才能够调用该对象的wait(),notify()和notifyAll()方法。
-
如果不这么做,代码会抛出IllegalMonitorStateException异常。
-
为了避免wait和notify之间产生竞态条件
-
wait()方法强制当前线程释放对象锁。这意味着在调用某对象的wait()方法之前,当前线程必须已经获得该对象的锁。因此,线程必须在某个对象的同步方法或同步代码块中才能调用该对象的wait()方法。
-
在调用对象的notify()和notifyAll()方法之前,调用线程必须已经得到该对象的锁。因此,必须在某个对象的同步方法或同步代码块中才能调用该对象的notify()或notifyAll()方法。调用wait()方法的原因通常是,调用线程希望某个特殊的状态(或变量)被设置之后再继续执行。调用
-
notify()或notifyAll()方法的原因通常是,调用线程希望告诉其他等待中的线程:"特殊状态已经被设置"。这个状态作为线程间通信的通道,它必须是一个可变的共享状态(或变量)
为什么wait, notify 和 notifyAll这些方法不在thread类里面?
JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在Thread类中,线程正在等待的是哪个锁就不明显了。简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象。
这篇关于【并发编程】AQSSemaphore(信号量) CountDownLatch (倒计时器) CyclicBarrier(循环栅栏) ThreadLocal的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!