文章目录
一、公平锁与非公平锁
1、公平锁
定义:是指多个线程按照申请锁的顺序来获取锁,即按先来后到
2、非公平锁
定义:是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发的情况下,有可能会造成优先级反转或饥饿现象。简单来说就是后申请的线程的优先级较高,可以抢占先申请的线程
。其中ReentrantLock默认就是非公平锁
。即在创建锁的时候将值设false。
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
3、二者区别
公平锁:在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当线程是等待队列的第一个,就站有锁,否则就会加入等待队列中,以后会按照FIFO的规则从队列中取到自己。
非公平锁:比较粗鲁,上来就直接尝试站有锁,如果尝试失败,就再采用类似公平锁那种方式。
非公平锁的优点在于吞吐量比公平锁大,对于Synchronized而言,也是一种非公平锁
。
二、可重入锁(递归锁)
1、定义
可重入锁:指的是统一线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码,在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。也就是说,线程可以进入任何一个它已经拥有的锁所同步着的代码块。可重入锁最大的作用是避免死锁。
2、验证可重入锁——ReentrantLock/Synchronized
2.1、Synchronized
public class Phone{
public synchronized void sendSMS(){
System.out.println(Thread.currentThread().getName() + "\t invoked sendSMS()");
sendEmail();
}
public synchronized void sendEmail(){
System.out.println(Thread.currentThread().getName() + "\t invoked sendEmail()");
}
}
public class ReentrantLockDemo {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
try{
phone.sendSMS();
}catch (Exception e){
e.printStackTrace();
}
},"t1").start();
new Thread(()->{
try{
phone.sendSMS();
}catch (Exception e){
e.printStackTrace();
}
},"t2").start();
}
}
运行结果
在此个例子中,我们在Phone中定义了两个方法,分别加了synchronized锁,可重入锁就是在一个线程持有锁的情况,可以在此请求该锁而不会被阻塞
,我们看到t1线程先拿到了锁执行了sengSMS方法,在sendSMS方法中有调用了sendEmail方法,由输出结果可得,t1在执行完sendSMS后再次请求锁是可以直接获取到的。
2.2、ReentrantLock
public class LockDemo implements Runnable {
Lock lock = new ReentrantLock();
@Override
public void run() {
get();
}
public void get() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\t invoked get()");
set();
}finally {
lock.unlock();
}
}
private void set() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\t invoked set()");
}finally {
lock.unlock();
}
}
}
public class ReentrantLockDemo {
public static void main(String[] args) {
LockDemo demo = new LockDemo();
Thread t3 = new Thread(demo);
Thread t4 = new Thread(demo);
t3.start();
t4.start();
}
}
运行结果
但需要注意:Lock的方法需要手动释放锁,所以当我们最好加锁与解锁两两匹配,否则会导致死锁的现象。
三、自旋锁
1、定义
自旋锁:是指尝试获取锁的线程不会立即阻塞,而是采用自旋的方式去尝试获取锁
,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU
详细介绍CAS
自旋锁的好处:循环比较获取直到成功为止,没有类似wait的阻塞
2、例子验证
public class SpinLockDemo {
AtomicReference<Thread> atomicReference = new AtomicReference<>(); //原子引用线程
public void myLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "\t come in myLock");
do {
}while (!atomicReference.compareAndSet(null,thread));
System.out.println(Thread.currentThread().getName() + "\t invoked myLock");
}
public void myUnlock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "\t come in myUnlock");
atomicReference.compareAndSet(thread,null);
System.out.println(Thread.currentThread().getName() + "\t invoked myUnlock ");
}
public static void main(String[] args) {
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(()->{
spinLockDemo.myLock();
try {
Thread.sleep(5000);
System.out.println("休眠5秒");
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLockDemo.myUnlock();
},"AA").start();
new Thread(()->{
spinLockDemo.myLock();
try {
Thread.sleep(1000);
System.out.println("休眠1秒");
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLockDemo.myUnlock();
},"BB").start();
}
}
运行结果
结果证明,线程AA执行myLock方法进入睡眠状态,但是线程AA没有执行完,线程BB进入自旋状态,直到线程AA执行完之后线程BB才开始执行。
四、独占锁(写锁)/共享锁(读锁)/互斥锁
1、定义
独占锁:指该锁一次只能被一个线程所持有。ReentrantLock和Synchronized都是独占锁
共享锁:指该锁可被多个线程所持有
对于ReentrantReadWriteLock:其读锁是共享锁,其写锁是独占锁
读锁的共享锁可保证并发读是非常高效的,读写,写读,写写的过程都是互斥的。
2、举例验证
public class MyCache {
private volatile Map<String,Object> map = new HashMap<>();
private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
public void put(String key,Object value){
rwLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " \t 正在写入" + key);
try {
TimeUnit.MICROSECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "\t 写入完成");
}catch (Exception e){
}finally {
rwLock.writeLock().unlock();
}
}
public void get(String key){
rwLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " \t 正在读取" + key);
try {
TimeUnit.MICROSECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
Object result = map.get(key);
System.out.println(Thread.currentThread().getName() + "\t 读取完成" + result);
}catch (Exception e){
}finally {
rwLock.readLock().unlock();
}
}
}
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
for(int i = 1; i <= 5; i++){
final int tempInt = i;
new Thread(()->{
myCache.put(tempInt+"",tempInt+"");
},String.valueOf(i)).start();
}
for(int i = 1; i <= 5; i++){
final int tempInt = i;
new Thread(()->{
myCache.get(tempInt+"");
},String.valueOf(i)).start();
}
}
}
结果为
3、总结
读-读:可以共存
读-写:不能共存
写-写:不能共存
写操作:原子+独占,整个过程必须是一个完整的统一体,中间不允许被分割,被打断
五、CountDownLatch
1、定义
CountDownLatch:让一些线程阻塞直到另一些线程完成一系列操作后才被唤醒。
2、举例
public enum CountryEnum {
ONE(1,"唐"),TWO(2,"宋"),THREE(3,"元"),FOUR(4,"明"),FIVE(5,"清");
@Getter
private Integer retCode;
@Getter
private String retMessage;
CountryEnum(Integer retCode,String retMessage){
this.retCode = retCode;
this.retMessage = retMessage;
}
public static CountryEnum forEach_CountryEnum(int index){
CountryEnum[] myArray = CountryEnum.values();
for(CountryEnum element : myArray){
if(index == element.getRetCode()){
return element;
}
}
return null;
}
}
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(5);
for(int i = 1; i <= 5; i++){
new Thread(()->{
countDownLatch.countDown();
System.out.println(Thread.currentThread().getName() + "朝灭亡");
},CountryEnum.forEach_CountryEnum(i).getRetMessage()).start();
}
countDownLatch.await(); //当减为0的时候,被阻止的主线程才能被使用
System.out.println(Thread.currentThread().getName() + "朝");
}
}
六、Semaphore
1、定义
信号量主要用于两个目的:一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制
2、举例
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);//模拟3个停车位
for(int i = 0; i <= 6; i++){ //模拟6辆汽车
new Thread(()->{
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "抢到停车位");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "停车3秒后离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
},String.valueOf(i)).start();
}
}
}
七、阻塞队列
1、阻塞队列种类
类型 | 解释 |
---|---|
ArrayBlockingQueue | 由数组结构组成的有界阻塞队列 |
LinkedBlockingQueue | 由链表结构组成的有界(但大小默认值为Integer.MAX_VALUE)阻塞队列 |
PriorityBlockingQueue | 支持优先级排序的无界阻塞队列 |
DelayQueue | 使用优先级队列实现的延迟无界阻塞队列 |
SynchronousQueue | 不存储元素的阻塞队列,也即单个元素的队列 |
LinkedTransferQueue | 由链表结构组成的无界阻塞队列 |
LinkedBolckingDegue | 由链表结构组成的双向阻塞队列 |
1、ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列。此队列按FIFO(先进先出)原则对元素进行排序。
2、LinkedBlockingQueue:一个基于链表结构的阻塞队列。此队列按FIFO(先进先出)排序元素,吞吐量通常要高于ArrayBlockingQueue。
3、SynchronizedQueue:一个不存储元素的阻塞队列,每个输入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高。
当阻塞队列为空时,从队列中获取元素的操作将会被阻塞;
当阻塞队列是满时,往队列中添加元素的操作将会被阻塞。
2、ArrayBlockingQueue的使用
public class BlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.add("a");
blockingQueue.add("b");
blockingQueue.add("c");
// blockingQueue.add("x"); //此时阻塞队列已满,再往队里添加元素会抛异常IllegalStateException: Queue full
blockingQueue.remove();
blockingQueue.remove();
blockingQueue.remove();
// blockingQueue.element(); //当阻塞队列为空时,获取不到元素或报NoSuchElementException
blockingQueue.offer("a");//成功为true,失败false
blockingQueue.poll(); //阻塞队列为空时,返回null
blockingQueue.peek(); //首元素
blockingQueue.put("a"); //当阻塞队列满时,生产者线程继续往队里put元素,队列会一直阻塞生产线程直到put数据or响应中断退出
blockingQueue.take(); //当阻塞队列空时,消费者线程试图从队里take元素,队列会一直阻塞消费者线程直到队列可用
blockingQueue.offer("a",2L, TimeUnit.SECONDS); //当阻塞队列满时,队列会则色生产者线程一定时间,超过限时后生产者线程退出
blockingQueue.poll(2L,TimeUnit.SECONDS);
}
}
3、SynchronousQueue的使用
public class SynchronousQueueDemo {
public static void main(String[] args) {
BlockingQueue<String> blockingQueue = new SynchronousQueue<>();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName() + "\t put 1");
blockingQueue.put("1");
System.out.println(Thread.currentThread().getName() + "\t put 2");
blockingQueue.put("2");
System.out.println(Thread.currentThread().getName() + "\t put 3");
blockingQueue.put("3");
}catch (Exception e){
e.printStackTrace();
}
},"AAA").start();
new Thread(()->{
try {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take());
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take());
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take());
}catch (Exception e){
e.printStackTrace();
}
},"BBB").start();
}
}
结果为每停止5秒打印一次数据,能得到:
SynchronousQueue是不存储元素的阻塞队列,也即单个元素的队列
没有容量;每个put操作必须要等待一个take操作,否则不能继续添加元素,反之亦然。
八、Synchronized和Lock的区别
1、Synchronized和Lock的区别
Synchronized | ReentrantLock | |
---|---|---|
原始构成 | 关键字,属于JVM层面 | 具体类,是api层面的锁 |
使用方法 | 不需要手动释放锁,执行完后系统会让线程释放对锁的占用 | 需要用户手动释放锁,如若没有释放,可能会导致死锁现象(释放需要lock()和unlock()方法配合try/finally语句块来完成) |
等待是否可中断 | 不可中断,除非抛出异常或者正常运行完成 | 可中断 1、设置超时方法try lock (long timeout,TimeUnit unit)、2、lock Interruptibly()放代码块中,调用interrupt()方法可中断 |
加锁是否公平 | 非公平锁 | 两者都可以,默认为非公平锁,构造方法可以传入boolean值,true为公平锁,false为非公平锁 |
锁绑定多个条件 | 没有 | 用来实现分组唤醒需要唤醒的线程们,可以精确唤醒,而不是想synchronized要么随机唤醒一个线程,要么唤醒全部线程 |
2、精确唤醒实例
public class ShareResource {
private int number = 1;
private Lock lock = new ReentrantLock();
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();
public void print5(){ //print10 print15
lock.lock();
try{
//1.判断
while(number != 1){ // 2 3
c1.await();
}
//2.干活
for(int i = 1; i <= 5; i++ ){
System.out.println(Thread.currentThread().getName() +"\t" + i);
}
//3.通知
number = 2; // 3 1
c2.signal(); //c3.signal(); c1.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public class SyncAndReentrantLockDemo {
public static void main(String[] args) {
ShareResource shareResource = new ShareResource();
new Thread(()->{
for(int i = 1; i <= 10; i++){
shareResource.print5();
}
},"A").start();
}
}
九、线程通信之生产者消费者阻塞队列的实现
public class MyResource {
private volatile boolean FLAG = true; //默认开启 生产+消费 生产一个消费一个,保持线程的可见性
//使用原子的方式进行加操作,不要用i++,++i
private AtomicInteger atomicInteger = new AtomicInteger(); //默认值为0
BlockingQueue<String> blockingQueue = null; //通顺,适配,通用
public MyResource(BlockingQueue<String> blockingQueue){
this.blockingQueue = blockingQueue;
System.out.println(blockingQueue.getClass().getName());
}
public void myProd() throws InterruptedException {
String data = null;
boolean retValue;
while (FLAG){
data = atomicInteger.incrementAndGet() + "";
retValue = blockingQueue.offer(data,2L, TimeUnit.SECONDS);
if(retValue){
System.out.println(Thread.currentThread().getName() + "\t插入队列" + data+ "成功");
}else {
System.out.println(Thread.currentThread().getName() + "\t插入队列" + data+ "失败");
}
TimeUnit.SECONDS.sleep(1);
}
System.out.println(Thread.currentThread().getName() + "\t FLAG==false 生产动作结束");
}
public void myConsumer() throws InterruptedException {
String result = null;
while (FLAG){
result = blockingQueue.poll(2L,TimeUnit.SECONDS);
if(null == result || result.equalsIgnoreCase(" ")){
FLAG = false;
System.out.println(Thread.currentThread().getName() + "\t 超过2秒没有取到,消费退出");
return;
}
System.out.println(Thread.currentThread().getName() + "\t 消费队列" + result + "成功");
}
}
public void stop(){
this.FLAG=false;
}
}
public class ProdConsumer_BlockQueueDemo {
public static void main(String[] args) {
MyResource myResource = new MyResource(new ArrayBlockingQueue<>(10));
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "\t生产线程启动");
try {
myResource.myProd();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"Prod").start();
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "\t消费线程启动");
try {
myResource.myConsumer();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"Consumer").start();
System.out.println("活动结束");
myResource.stop();
}
}