AQS,CAS和线程池

本文详细介绍了Java中的线程模型,包括AQS(Abstract Queued Synchronizer)、CAS操作及其应用。讨论了线程池的实现,如定长线程池、周期定长线程池、单线程池和线程池的饱和策略。此外,还探讨了线程通信、信号量、障碍器和线程调度相关的方法,如yield、join和CountDownLatch。最后,文章提醒开发者避免使用Executors直接创建线程池,并提供了线程池大小的合理配置建议。
摘要由CSDN通过智能技术生成

线程相关的模型

volatile类型修饰符

保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。(实现可见性)

禁止进行指令重排序。(实现有序性)

volatile 只能保证对单次读/写的原子性。i++ 这种操作不能保证原子性

volatile 变量的内存可见性是基于内存屏障Memory Barrier实现。

内存屏障,又称内存栅栏,是一个 CPU 指令。

在程序运行时,为了提高执行性能,编译器和处理器会对指令进行重排序,JMM 为了保证在不同的编译器和 CPU 上有相同的结果,通过插入特定类型的内存屏障来禁止特定类型的编译器重排序和处理器重排序,插入一条内存屏障会告诉编译器和 CPU:不管什么指令都不能和这条 Memory Barrier 指令重排序。
volatile 的应用场景
只有在状态真正独立于程序内其他内容时才能使用 volatile

AQS模型

Abstract Queued Synchronizer抽象队列同步器:AQS是JDK下提供的一套用于实现基于FIFO等待队列的阻塞锁和相关的同步器的一个同步框架。

AQS维护了一个原子变量 volatile int state(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)。许多同步类实现都依赖于它,如常用的ReentrantLock、Semaphore、CountDownLatch…

AQS的核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态,如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。CLH即克雷格Craig, 兰丁Landin, and 海格斯腾 Hagersten的简称

CLH同步队列遵循FIFO,首节点的线程释放同步状态后,将会唤醒它的后继节点next,而后继节点将会在获取同步状态成功时将自己设置为首节点,这个过程非常简单,head执行该节点并断开原首节点的next和当前节点的prev即可,注意在这个过程是不需要使用CAS来保证的,因为只有一个线程能够成功获取到同步状态

内部使用AQS的例子:

以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。

CAS模型

Java5中引入了AutomicInteger、AutomicLong、AutomicReference等特殊的原子性变量类,它们提供的如compareAndSet、incrementAndSet和getAndIncrement等方法都使用了CAS操作。都是由硬件指令来保证的原子方法。

CAS即比较并交换。是解决多线程并行情况下使用锁造成性能损耗的一种机制,CAS操作包含三个操作数——内存位置V、预期原值A和新值B。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在CAS指令之前返回该位置的值。

比较和交换(Conmpare And Swap)是用于实现多线程同步的原子指令。 它将内存位置的内容与给定值进行比较,只有在相同的情况下,将该内存位置的内容修改为新的给定值。这是作为单个原子操作完成的。 原子性保证新值基于最新信息计算; 如果该值在同一时间被另一个线程更新,则写入将失败。操作结果必须说明是否进行替换; 这可以通过一个简单的布尔响应(这个变体通常称为比较和设置),或通过返回从内存位置读取的值来完成

CAS 的特性:

通过调用 JNI 的代码实现
非阻塞算法
非独占锁

CAS 存在的问题:

ABA

循环时间长开销大

只能保证一个共享变量的原子操作

典型案例:原子量
所谓的原子量即操作变量的操作是原子的,该操作不可再分,因此是线程安全的。

为何要使用原子变量呢,
原因是多个线程对单个变量操作也会引起一些问题。在Java5之前,可以通过volatile、synchronized关键字来解决并发访问的安全问题,但这样太麻烦。
Java5后,专门提供了用来进行单变量多线程并发安全访问的工具包java.util.concurrent.atomic,其中的类也很简单。
例如AtomicLong aLong=new AtomicLong(10000); //原子量,每个线程都可以自由操作

原子量实现的计数器

public class Test2 {
   
	public static void main(String[] args) {
   
		Counter counter = new Counter();
		ExecutorService service = Executors.newCachedThreadPool();
		for (int i = 0; i < 1000000; i++) {
   
			service.execute(new Runnable() {
   
				public void run() {
   
					System.out.println(counter.increase());
				}
			});
		}
		service.shutdown();
	}
}


class Counter {
   
	private AtomicInteger counter = new AtomicInteger(0);

	public int increase() {
   
		return counter.incrementAndGet();
	}
}

CAS模型的问题

ABA问题
解决方法:JAVA中提供了AtomicStampedReference / AtomicMarkableReference来处理会发生ABA问题的场景,主要是在对象中额外再增加一个标记来标识对象是否有过变更。

CAS应用场景隐含竞争是短暂的,否则不断的自旋尝试会过度消耗CPU
解决方法加入超时设置

CAS只能保证一个共享变量的原子操作,解决方法是使用锁或者合并多个变量
//AtomicReference提供了以无锁方式访问共享资源的能力

AtomicReference<Integer> ar=new AtomicReference<>(new Integer(1000));
List<Thread> list=new ArrayList<>();
for(int i=0; i<1000; i++){
   
    Thread t=new Thread( ()->{
   
        while(true){
    //自旋操作
            Integer oldValue=ar.get();
            Thread.sleep(10);
            //具体处理逻辑
            Integer newValue=oldValue+1;
            if(ar.compareAndSet(oldValue,newValue))//CAS操作
                break;
        }
    });
    list.add(t);
    t.start();
}
for(Thread temp:list) temp.join();
System.out.println
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值