【并发编程】AQSSemaphore(信号量) CountDownLatch (倒计时器) CyclicBarrier(循环栅栏) ThreadLocal

本文主要是介绍【并发编程】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 是一个用来构建锁和同步器的框架,能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的 ReentrantLockSemaphore等等皆是基于 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语句块来完成。

相比synchronizedReentrantLock增加了一些高级功能。主要来说主要有三点:

  • 等待可中断 : 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要在同步块中调用?

  1. 只有在调用线程拥有某个对象的独占锁时,才能够调用该对象的wait(),notify()和notifyAll()方法。

  2. 如果不这么做,代码会抛出IllegalMonitorStateException异常。

  3. 为了避免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的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/842630

相关文章

好题——hdu2522(小数问题:求1/n的第一个循环节)

好喜欢这题,第一次做小数问题,一开始真心没思路,然后参考了网上的一些资料。 知识点***********************************无限不循环小数即无理数,不能写作两整数之比*****************************(一开始没想到,小学没学好) 此题1/n肯定是一个有限循环小数,了解这些后就能做此题了。 按照除法的机制,用一个函数表示出来就可以了,代码如下

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

高并发环境中保持幂等性

在高并发环境中保持幂等性是一项重要的挑战。幂等性指的是无论操作执行多少次,其效果都是相同的。确保操作的幂等性可以避免重复执行带来的副作用。以下是一些保持幂等性的常用方法: 唯一标识符: 请求唯一标识:在每次请求中引入唯一标识符(如 UUID 或者生成的唯一 ID),在处理请求时,系统可以检查这个标识符是否已经处理过,如果是,则忽略重复请求。幂等键(Idempotency Key):客户端在每次

【编程底层思考】垃圾收集机制,GC算法,垃圾收集器类型概述

Java的垃圾收集(Garbage Collection,GC)机制是Java语言的一大特色,它负责自动管理内存的回收,释放不再使用的对象所占用的内存。以下是对Java垃圾收集机制的详细介绍: 一、垃圾收集机制概述: 对象存活判断:垃圾收集器定期检查堆内存中的对象,判断哪些对象是“垃圾”,即不再被任何引用链直接或间接引用的对象。内存回收:将判断为垃圾的对象占用的内存进行回收,以便重新使用。

Go Playground 在线编程环境

For all examples in this and the next chapter, we will use Go Playground. Go Playground represents a web service that can run programs written in Go. It can be opened in a web browser using the follow

深入理解RxJava:响应式编程的现代方式

在当今的软件开发世界中,异步编程和事件驱动的架构变得越来越重要。RxJava,作为响应式编程(Reactive Programming)的一个流行库,为Java和Android开发者提供了一种强大的方式来处理异步任务和事件流。本文将深入探讨RxJava的核心概念、优势以及如何在实际项目中应用它。 文章目录 💯 什么是RxJava?💯 响应式编程的优势💯 RxJava的核心概念

poj3750约瑟夫环,循环队列

Description 有N个小孩围成一圈,给他们从1开始依次编号,现指定从第W个开始报数,报到第S个时,该小孩出列,然后从下一个小孩开始报数,仍是报到S个出列,如此重复下去,直到所有的小孩都出列(总人数不足S个时将循环报数),求小孩出列的顺序。 Input 第一行输入小孩的人数N(N<=64) 接下来每行输入一个小孩的名字(人名不超过15个字符) 最后一行输入W,S (W < N),用

函数式编程思想

我们经常会用到各种各样的编程思想,例如面向过程、面向对象。不过笔者在该博客简单介绍一下函数式编程思想. 如果对函数式编程思想进行概括,就是f(x) = na(x) , y=uf(x)…至于其他的编程思想,可能是y=a(x)+b(x)+c(x)…,也有可能是y=f(x)=f(x)/a + f(x)/b+f(x)/c… 面向过程的指令式编程 面向过程,简单理解就是y=a(x)+b(x)+c(x)

Java并发编程之——BlockingQueue(队列)

一、什么是BlockingQueue BlockingQueue即阻塞队列,从阻塞这个词可以看出,在某些情况下对阻塞队列的访问可能会造成阻塞。被阻塞的情况主要有如下两种: 1. 当队列满了的时候进行入队列操作2. 当队列空了的时候进行出队列操作123 因此,当一个线程试图对一个已经满了的队列进行入队列操作时,它将会被阻塞,除非有另一个线程做了出队列操作;同样,当一个线程试图对一个空