线程上下文切换
有四个原因
- 线程的时间片用完
- 垃圾回收
- 有更高优先级的线程要执行
- 线程自己调用sleep、join、yield、park、synchronized、lock等方法
线程频繁上下文切换会影响性能
sleep、yield、join
-
sleep不释放锁,调用sleep会让当前线程从running进入Timed Waiting状态
-
yield方法让当前线程让出cpu的控制权,当前线程进入就绪状态,可以继续抢占cpu资源
-
join等待线程,等待调用这个方法的线程执行完毕在执行下面的语句,有参数时表示如果参数时间内线程还没执行完,就不等了
interrupt
打断线程,打断当前正在执行的线程,但线程不会立刻被终结,只是告诉线程你已经被打断了,自行处理后事吧,当线程在wait、join、sleep中被打断时,打断标记interrupted会被恢复为false;
interrupt() 打断线程
static interrupted() 判断当前线程是否被打断,会清除打断标记
isInterrupted()判断当前线程是否被打断,不会清除打断标记
public class monitor {
public static void main(String[] args) throws InterruptedException {
TwoPhaseTermination test = new TwoPhaseTermination();
test.start();
Thread.sleep(5000);
test.stop();
}
}
class TwoPhaseTermination{
private Thread monitor;
public void start(){
monitor = new Thread(() ->{
while (true){
Thread current = Thread.currentThread();
if(current.isInterrupted()){
System.out.println("料理后事");
System.out.println(current.isInterrupted()); //true
System.out.println(Thread.interrupted()); //true 但清除了标记
System.out.println(current.isInterrupted()); //false
break;
}
try{
Thread.sleep(2000);
System.out.println("监控线程正在监控中,未被打断");
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("---------在睡眠中被打断--------");
current.interrupt();
}
}
});
monitor.start();
}
public void stop(){
monitor.interrupt();
}
}
park 当打断标记为true时,park失效
两阶段终止模式
线程的状态
在操作系统层面,线程有五种状态
- 初始状态、可运行状态、运行状态、阻塞状态、终止状态
在java中,Thread.state中有六种状态
- NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED
synchronized
加在普通方法上,锁住的是对象(this)、加在静态方法上,锁住的是类
轻量级锁
轻量级锁不是用来替代传统的重量级锁的,而是在没有多线程竞争的情况下,使用轻量级锁能够减少性能消耗,但是当多个线程同时竞争锁时,轻量级锁会膨胀为重量级锁。
synchronized加锁时,如果没有竞争关系,默认加的是轻量级锁,如果有竞争关系,升级为重量级锁
锁重入
当一个线程准备给一个对象加锁时发现这个对象已经被锁住了并且是自己给锁住了,会再创建一条lock record记录,且设置该条记录为null,这就是锁重入。
当退出synchronized代码块时,锁记录不为null,这时使用cas将MarkWord的值恢复给对象头
- 若成功,代表解锁成功
- 失败,说明轻量级锁已经升级为重量级锁,进入重量级锁的解锁过程
锁膨胀
锁膨胀是指一个线程企图对一个对象加锁时,加的是轻量级锁,发现加锁失败,说明有其他线程之前已经对这个对象加过锁,因此这个线程进入锁膨胀流程
- 为Obeject对象申请Monitor锁,让Object指向重量级锁地址
- 然后自己进入Monitor锁EntryList中,并进入Blocked状态
这时当另一个线程退出同步代码块时,使用cas将MarkWord的值恢复给对象头,发现失败。说明锁已经膨胀为重量级锁,进入重量级锁的解锁过程
- 根据Monitor地址找到Monitor对象
- 将Monitor对象中的owner指针置为null
- 唤醒EntryList中阻塞的线程,这些线程根据优先级优先重新抢占cpu时间片
偏向锁
1.线程A第一次访问同步块时,先检测对象头Mark Word中的标志位是否为01,依此判断此时对象锁是否处于无所状态或者偏向锁状态(匿名偏向锁);
2.然后判断偏向锁标志位是否为1,如果不是,则进入轻量级锁逻辑(使用CAS竞争锁),如果是,则进入下一步流程;
3.判断是偏向锁时,检查对象头Mark Word中记录的Thread Id是否是当前线程ID,如果是,则表明当前线程已经获得对象锁,以后该线程进入同步块时,不需要CAS进行加锁,只会往当前线程的栈中添加一条Displaced Mark Word为空的Lock Record中,用来统计重入的次数(如图为当对象所处于偏向锁时,当前线程重入3次,线程栈帧中Lock Record记录)。
对象创建后,没有线程的竞争关系时默认是偏向锁,当有其他线程使用这个对象时,会将偏向锁升级为轻量级锁
调用对象的hashcode会禁用掉偏向锁
- 偏向锁撤销
Dog dog = new Dog();
new Thread(() -> {
System.out.println(ClassLayout.parseInstance(dog).toPrintable());
synchronized (dog){
System.out.println(ClassLayout.parseInstance(dog).toPrintable());
}
System.out.println(ClassLayout.parseInstance(dog).toPrintable());
synchronized (revocation.class){
revocation.class.notify();
}
},"t1").start();
new Thread(() -> {
synchronized (revocation.class){
try {
revocation.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(ClassLayout.parseInstance(dog).toPrintable());
synchronized (dog){
System.out.println(ClassLayout.parseInstance(dog).toPrintable());
}
System.out.println(ClassLayout.parseInstance(dog).toPrintable());
},"t2").start();
//结果如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vzvSzaxX-1675264249259)(C:\Users\16665\AppData\Roaming\Typora\typora-user-images\image-20220419170811519.png)]
批量重偏向与批量撤销
从偏向锁的加锁解锁过程可以看出,当只有一个线程反复进入同步代码块时,偏向锁带来的性能开销基本可以忽略不记,但当有其他线程尝试获得锁时,就需要将偏向锁撤销为无锁状态或轻量级锁,所有在多线程竞争的情况下,偏向锁有时也会比较消耗性能。
批量重偏向
场景:一个线程创建很多对象,并对这些对象进行初始化的同步操作,这时另一个线程也使用这些对象作为锁对象,这样会导致大量的偏向锁撤销操作。当jvm判断撤销偏向锁阈值达到20时,会觉得自己是不是偏向错了呢,然后会在给这些对象重新加锁时偏向至加锁线程。
批量撤销
当撤销偏向锁阈值达到40时,jvm会觉得,我确实是偏向错了,压根就不应该偏向,于是整个类的所有对象都变成不可偏向的,即使新创建的对象也无法偏向。
自旋优化
重量级锁在竞争时,如果一个线程发现对象已被其他贤臣给锁住,并不会直接进入阻塞状态,进入阻塞状态会导致线程上下文切换,对性能有一定影响,这时线程会进行自旋,可以认为线程不断的尝试获取锁,如果自旋期间持有锁的线程释放锁,那么当前线程拿到锁后就可以直接开始执行同步代码块。
当然自旋也不是一直运行的,自旋会占用cpu时间,有多核cpu时自旋才会发挥性能。
java7以后已经不能控制是否开启自旋。
MarkWord
lock record地址 + 最后两位 01 无锁状态 、 00 轻量级锁、10 重量级锁
锁消除
利用jit即时编译器 jit发现频繁对一个对象进行加锁解锁,但检测到并没有发生竞争时,虚拟机就自动把锁进行消除。可以通过修改参数禁止jit锁消除
wait和sleep
都会阻塞线程,调用sleep和有参wait方法时,当前线程会进入timed-waiting状态
执行wait方法时会释放锁,而执行sleep方法不会释放锁。
执行wait方法进入waiting状态,可通过notify或者notifyAll来激活线程。
park & unpark
park方法由LockSupport类来调用,调用后线程进入waiting状态,可调用unpark方法来唤醒线程。
调用park方法后线程不一定进入waiting状态,要看Parker对象中的_counter属性,每个线程都有自己的Parker对象,里面包含 counter, _cond, _mutex;
可以把线程比作旅行者,Parker就像他随身携带的背包,_counter就好比背包里的干粮。
调用park方法时,会检查背包里的干粮是否足够(_counter是否为1)
- 若为1,代表干粮足够,可以继续前进(线程可以不用停止,继续执行,_counter-1)
- 若为0,干粮不够,需要休息,直到有干粮(线程进入waiting状态,等待唤醒)
调用unpark方法,唤醒当前park中的线程,若线程没在waiting状态,将线程的Parker对象中的 _counter + 1,最高为,这就表示,unpark可以在park之前调用,多次调用只会让 _counter值为1
同步模式之保护性暂停
- 有一个结果需要从一个线程传递到另一个线程,让他们关联同一个 GuardedObject
- 如果有结果不断从一个线程到另一个线程那么可以使用消息队列(见生产者/消费者)
- JDK 中,join 的实现、Future 的实现,采用的就是此模式
- 因为要等待另一方的结果,因此归类到同步模式
//单任务版GuardedOject
public class GuardedObjectTest {
public static void main(String[] args) throws InterruptedException {
GuardedObject go = new GuardedObject();
new Thread(() -> {
System.out.println("取结果");
go.getResponse(2000);
System.out.println("结果为" + go.response);
}).start();
new Thread(() -> {
System.out.println("产生结果");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
go.complieResponse(new Date());
}).start();
}
}
class GuardedObject{
public Object response;
public Object getResponse(long timeout){
synchronized (this){
long begin = System.currentTimeMillis();
long passedTime = 0;
while (response == null){
long waiting = timeout - passedTime;
if(waiting <= 0){
break;
}
try {
this.wait(waiting);
} catch (InterruptedException e) {
e.printStackTrace();
}
passedTime = System.currentTimeMillis() - begin;
}
return response;
}
}
public void complieResponse(Object response){
synchronized (this) {
this.response = response;
this.notifyAll();
}
}
}
//多任务版GuardedObject
package thread.guardedObject;
import lombok.extern.slf4j.Slf4j;
import java.lang.annotation.Target;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
public class GuardedTask {
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
new Person().start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for(Integer id : MailBoxes.getKeySet()) {
new PostMan(id, "内容 " + id).start();
}
}
}
@Slf4j
class Person extends Thread{
@Override
public void run() {
GuardedObject guardedObject = MailBoxes.createGuardedObject(MailBoxes.generatedId());
System.out.println("收信的id:" + guardedObject.getId());
Object o = guardedObject.getResponse(5000);
System.out.println("收信的id:" + guardedObject.getId() + ",内容:" + o);
}
}
class PostMan extends Thread{
private int id;
private String mail;
public PostMan(int id, String mail) {
this.id = id;
this.mail = mail;
}
@Override
public void run() {
GuardedObject go = MailBoxes.getGuardedObject(id);
System.out.println("送信的id:" + id + ",内容:" + mail);
go.complieResponse(mail);
}
}
class MailBoxes{
//信箱,可以投信、发信
private static int id = 1;
public synchronized static int generatedId(){
return id++;
}
private static Map<Integer,GuardedObject> table = new Hashtable<>();
//用户投信
public static GuardedObject createGuardedObject(int id){
GuardedObject go = new GuardedObject(id);
table.put(go.getId(),go);
return go;
}
//邮递员发信
public static GuardedObject getGuardedObject(int id){
return table.remove(id);
}
public static Set<Integer> getKeySet(){
return table.keySet();
}
}
class GuardedObject{
private int id;
private Object response;
public GuardedObject(int id){
this.id = id;
}
public int getId(){
return id;
}
public Object getResponse(long timeout) {
synchronized (this){
long begin = System.currentTimeMillis();
long passedTime = 0;
while (response == null){
long waitingTime = timeout - passedTime;
if(waitingTime <= 0){
break;
}
try {
this.wait(waitingTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
passedTime = System.currentTimeMillis() - begin;
}
return response;
}
}
public void complieResponse(Object response){
synchronized (this){
this.response = response;
this.notify();
}
}
}
CAS的特点
CompareAndSwap,比较并交换
cas结合volatile可以实现无锁并发,适用于线程少,核心数低的情况
- cas是基于乐观锁的思想,最乐观的估计,不怕别的线程来改变我的值,大不了我再试一次
- synchronized是基于悲观锁的思想,认为我不加锁的话一定会有人来改变我的值,所以我必须加着锁,等我改完你们再改
- CAS体现的是无锁并发,无阻塞并发
- 因为没使用synchronized,所以不会陷入阻塞状态
- 当线程很多时,cas要不断重试,也会影响效率
原子对象
AtomicInteger
AtomicReference //可以存放引用数据类型的对象
AtomicStampedReference //额外增加时间戳作为版本号
AtomicMarkableReference //增加布尔型作为版本号
原子数组
普通类型的数组在多线程高并发场景下,会发生错误,需要使用原子数组,AtomicInegerArray
自定义连接池
public class Pool {
private final int poolSize;
private Connection[] connections;
//连接状态数组,0 表示空闲 1 表示繁忙
private AtomicIntegerArray states;
public Pool(int poolSize) {
this.poolSize = poolSize;
connections = new Connection[poolSize];
states = new AtomicIntegerArray(new int[poolSize]);
for(int i = 0; i < poolSize; i++){
connections[i] = new MorkConnection("" + (i + 1));
}
}
//借连接
public Connection borrow(){
while (true){
for (int i = 0; i < poolSize; i++) {
if(states.get(i) == 0){
if(states.compareAndSet(i,0,1)){
System.out.println("获取连接" + connections[i]);
return connections[i];
}
}
}
synchronized (this){
try {
System.out.println("线程池没了,等待中....");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//归还连接
public void free(Connection conn){
for (int i = 0; i < poolSize; i++) {
if(connections[i] == conn){
states.set(i,0);
}
synchronized (this){
System.out.println("线程池有货啦,都起床吧....");
this.notifyAll();
return;
}
}
}
}
class MorkConnection implements Connection{
private String name;
public MorkConnection(String name) {
this.name = name;
}
@Override
public String toString() {
return "MorkConnection{" +
"name='" + name + '\'' +
'}';
}
}
public class PoolTest {
public static void main(String[] args) {
Pool pool = new Pool(2);
for (int i = 0; i < 5; i++) {
new Thread(() -> {
Connection conn = pool.borrow();
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
pool.free(conn);
}).start();
}
}
}
自定义线程池
package thread.myThreadPool;
import com.sun.corba.se.spi.orbutil.threadpool.Work;
import javafx.concurrent.Worker;
import javax.management.QueryEval;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class MyThreadPoolTest {
public static void main(String[] args) {
ThreadPool threadPool = new ThreadPool(2,1000,TimeUnit.MILLISECONDS,1,(queue,task) -> {
/**
* 1.死等
* queue.put(task)
* 2.带超时等待
queue.tryPut(task,500,TimeUnit.MILLSECONDE)
* 3.让调用者放弃执行
* System.out.println("放弃执行");
* 4.让调用者自己执行
* task.run():
* 5.让调用者抛出异常
* throw new RuntimeException("任务失败“);
*/
queue.offer(task,500,TimeUnit.MILLISECONDS);
});
for (int i = 0; i < 4; i++) {
int j = i;
threadPool.executor(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(j);
});
}
}
}
@FunctionalInterface
interface RejectPolicy<T>{
void reject(BlockingQueue<T> queue,T task);
}
class ThreadPool{
//线程集合
private Set<Worker> works = new HashSet<>();
//任务队列
private BlockingQueue<Runnable> taskQueue;
//线程池的核心线程数
private int coreSize;
//获取任务的超时时间
private long timeout;
private TimeUnit timeUnit;
//使用的策略模式
private RejectPolicy<Runnable> rejectPolicy;
public ThreadPool( int coreSize, long timeout, TimeUnit timeUnit,
int queueCapacity,RejectPolicy<Runnable> rejectPolicy) {
this.coreSize = coreSize;
this.timeout = timeout;
this.timeUnit = timeUnit;
taskQueue = new BlockingQueue<>(queueCapacity);
this.rejectPolicy = rejectPolicy;
}
//执行任务
public void executor(Runnable task){
synchronized (works){
if(works.size() < coreSize){
Worker worker = new Worker(task);
System.out.println("新增worker" + worker + ",任务" + task);
works.add(worker);
worker.start();
}else{
//线程池没有核心线程数目可分配
/**
* 1.死等
* 2.带超时等待
* 3.让调用者放弃执行
* 4.让调用者自己执行
* 5.让调用者抛出异常
*/
taskQueue.tryPut(rejectPolicy,task);
}
}
}
private class Worker extends Thread{
private Runnable task;
public Worker(Runnable task) {
this.task = task;
}
@Override
public void run() {
while (task != null || (task = taskQueue.poll(timeout,timeUnit)) != null){
try{
System.out.println("任务" + task + "正在执行");
task.run();
}catch (Exception e){
e.printStackTrace();
}finally {
task = null;
}
}
synchronized (works){
System.out.println("worker被移除" + this);
works.remove(this);
}
}
}
}
class BlockingQueue<T>{
//任务队列的容量
private int capacity;
//双端列表
private Deque<T> queue;
//获取锁
private ReentrantLock lock;
//当任务队列满时的休息室
private Condition fullWaitSet;
//当任务队列为空时的休息室
private Condition emptyWaitSet;
public BlockingQueue(int capacity) {
this.capacity = capacity;
queue = new ArrayDeque<>(capacity);
lock = new ReentrantLock();
fullWaitSet = lock.newCondition();
emptyWaitSet = lock.newCondition();
}
//获取任务
public T take(){
lock.lock();
try{
while (queue.isEmpty()){
try{
emptyWaitSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T t = queue.removeFirst();
return t;
}finally {
lock.unlock();
}
}
//添加任务
public void put(T task){
lock.lock();
try {
while (queue.size() == capacity){
try {
System.out.println("等待任务加入任务队列");
fullWaitSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("任务加入任务队列");
queue.addLast(task);
emptyWaitSet.signal();
}finally {
lock.unlock();
}
}
//带超时时间的获取
public T poll(long timeout,TimeUnit timeUnit){
lock.lock();
try{
long nanos = timeUnit.toNanos(timeout);
while (queue.isEmpty()){
try{
if(nanos <= 0){
return null;
}
//防止虚假唤醒,返回的是休息了多长时间
nanos = emptyWaitSet.awaitNanos(timeout);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T t = queue.removeFirst();
fullWaitSet.signal();
return t;
}finally {
lock.unlock();
}
}
//带超时时间的添加
public boolean offer(T task,long timeout,TimeUnit timeUnit){
lock.lock();
try{
long nanos = timeUnit.toNanos(timeout);
while(queue.size() == capacity){
try{
if(nanos <= 0){
return false;
}
System.out.println("等待任务加入队列" + task);
nanos = fullWaitSet.awaitNanos(timeout);
}catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("任务加入队列" + task);
queue.addLast(task);
emptyWaitSet.signal();
return true;
} finally{
lock.unlock();
}
}
public void tryPut(RejectPolicy<T> rejectPolicy,T task){
lock.lock();
try {
// 判断判断是否满
if(queue.size() == capacity) {
rejectPolicy.reject(this, task);
} else { // 有空闲
System.out.println( "加入任务队列" + task);
queue.addLast(task);
emptyWaitSet.signal();
}
}finally {
lock.unlock();
}
}
public int getSize(){
lock.lock();
try{
return queue.size();
}finally {
lock.unlock();
}
}
}
线程池的五种状态
RUNNING
SHUTDOWN 不会接收新任务,但会把阻塞队列中的任务执行完
STOP 会中断正在执行的任务,并把阻塞队列中的任务全部抛弃
TIDYING 任务执行完毕,活动线程为0 ,即将进入终结状态
TERMINATED 终结状态
线程池构造方法
public ThreadPoolExecutor(int corePoolSize,
int maxinumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockintQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandle handler
)
-
corePoolSize 核心线程数目
-
keepAliveTime 最大存活时间,相对于救急线程而言,救急线程 = 最大线程数目 - 核心线程数目
-
unit 最大存活时间的单位
-
workQueue 阻塞队列
-
threadFactory 线程工厂
-
handler 拒绝策略
固定大小线程池
固定大小线程池 核心线程数 == 最大线程数,因此也不需要超时时间
阻塞队列是无界的,可以存放任意数量的任务
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bLF6YWEP-1675264257612)(null)]
带缓冲线程池
没有核心线程数,全是救急线程,阻塞队列采用的是SynchronousQueue,特点是放入一个任务后,没有线程来取,另一个任务是放不进来的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UJXyYtB2-1675264257528)(null)]
单线程线程池
希望多个线程排队工作,线程固定数为1,即使任务执行完,也不会释放线程,任务数多于1时,会放入无界队列
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3CGLByVn-1675264257436)(null)]
线程池的方法
shutdown()方法,不会打断当前正在执行的任务,同时会把阻塞队列里的任务执行完,执行shutdown时也不会阻塞当前线程。
shutdownNow()方法,将当前正在执行的任务打断,并把阻塞队列中任务返回
自定义锁 继承AQS
//自定义锁(不可重入锁)
public class MyLock implements Lock {
//独占锁
private class MySync extends AbstractQueuedSynchronizer {
@Override
//尝试加锁
protected boolean tryAcquire(int arg) {
if(compareAndSetState(0,1)){
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
//尝试解锁
@Override
protected boolean tryRelease(int arg) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
//是否持有独占锁
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
}
public Condition newCondition(){
return new ConditionObject();
}
}
private MySync sync = new MySync();
@Override
//加锁(不成功会进入阻塞队列)
public void lock() {
sync.acquire(1);
}
//加锁 可打断
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
//尝试加锁 (只加锁一次,成功返回true,失败返回false)
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
//尝试加锁 (带超时时间)
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1,unit.toNanos(time));
}
//解锁
@Override
public void unlock() {
sync.release(0);
}
//声明条件变量
@Override
public Condition newCondition() {
return sync.newCondition();
}
}
加锁成功 加锁失败
解锁竞争成功 解锁竞争失败
锁重入原理
在加锁时,会设置一个nextc变量,如果发现当前线程等于要要加的锁的owner,nextc加一
解锁时,直到nextc变为0,才返回true,否则返回false;
不可打断模式
在这个模式下,即使当前线程被其他线程打断,也不会停止运行,仍会驻留在AQS队列中,直到获得锁以后,才会直到,奥,原来有人打断过我这个线程。
可打断模式
当获取到打断标记且为true时,不可打断模式采取的措施是不管,继续执行for循环,
可打断模式采取的是抛出异常,这就是可打断模式
非公平锁与公平锁
-
非公平锁是指不管AQS队列中是否有正在阻塞的线程,只要发现状态为0,没人占用这个锁,就直接上去竞争锁,也不去检查AQS队列
-
公平锁当状态为0没人占用锁时,会先去先查AQS队列中是否有前驱节点,没有才去竞争
await与singal
- await方法 conditionObject中有一个队列,当调用条件变量的await时,线程会进入这个队列,并且将线程park,把锁中owner置为null,state置为0
- singal解除条件变量的锁,首先将线程从conditionObject队列中断开,然后转移到AQS队列中去竞争锁
读写锁 reentrantreadwritelock
- 重入时升级不支持,意思是如果持有读锁,无法升级为写锁
- 重入时降级支持,意思是如果持有写锁,可以降级为读锁
stampedlock
读写锁,支持乐观锁,会返回一个stamp (戳) 解锁时判断戳是否被改变了,被改变以后必须重新读
乐观锁(tryOptimisticRead方法)
//加读锁
StampedLock lock = new StampedLock();
long stamp = lock.readLock();
lock.unlockRead(stamp);
if(lock.validate(stamp)){
System.out.println("戳没变");
}else{
System.out.println("戳变了");
}
//加写锁
long stampWrite = lock.writeLock();
lock.unlockWrite(stampWrite);
semaphore
Semaphore lock = new Semaphore(3);
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
lock.acquire();
System.out.println("running....");
} catch (InterruptedException e) {
}finally {
lock.release();
System.out.println("end.....");
}
}).start();
}
CountDownLatch
用来进行线程同步协作,等待所有线程完成倒计时
//初始化
CountDownLatch latch = new CountDownLatch(10);
//表示倒计时为10,当调用latch.await方法后,当前线程开始阻塞,等倒计时减到0时,线程开始运行
//游戏开始,等所有玩家的进度达到百分之百时,可以进入游戏
//在这个例子中,每有一个线程的进度达到100%,count就会减一,减到0后,主线程结束阻塞状态
public static void main(String[] args) throws InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(10);
CountDownLatch latch = new CountDownLatch(10);
Random random = new Random();
String[] arr = new String[10];
for (int j = 0; j < 10; j++) {
int id = j;
pool.submit(() -> {
for(int i = 0; i <= 100; i ++){
arr[id] = i + "%";
try {
Thread.sleep(random.nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.print("\r" + Arrays.toString(arr));
}
latch.countDown();
});
}
latch.await();
System.out.println("\n" + "游戏开始...");
}