多线程(3)

目录

一、线程安全的实现

1.1 阻塞同步

1.2 非阻塞同步

1.3 无同步方案

二、线程锁的特点、性能和使用场景

2.1 多线程的理由

 2.2 多线程的问题

 2.3 四种线程锁


一、线程安全的实现

1.1 阻塞同步

也就是使用锁实现。具体采用锁,有两种选择:内置锁也就是synchronized关键字、JUC下具体锁的实现

1.2 非阻塞同步

基于CAS操作。最直接的实现是JUC下各种原子类的实现。虽然CAS避免了锁带来的性能开销,不过其仅适用于少部分同步场景,没有阻塞同步更加具有普适性。

使用锁会带来一些问题:频繁的线程阻塞、唤醒操作以及用户内核态的切换带来的性能问题,所以引入非阻塞同步

缺点:

  • 未获取同步资源的线程陷入自旋状态,所以对于CPU的消耗很高
  • 仅能操作单个共享资源,对于组合类型还是需要加锁处理,或者重新组合为一个共享资源

1.3 无同步方案

线程的本地存储,主要用于对于一个共享资源都尽可能在同一个线程中执行

二、线程锁的特点、性能和使用场景

2.1 多线程的理由

进程相比,它是一种花销小、切换快、更“节俭”的多任务操作方式

 在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种”昂贵”的多任务工作方式。而在进程中的同时运行多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。

 2.2 多线程的问题

由于多个线程是共同占有所属进程的资源和地址空间的,如果多个线程要同时访问同一个共享资源,这时作为开发者必须考虑如何维护数据一致性,这就是java锁机制(同步机制)的来源。

 2.3 四种线程锁

1、synchronized

在需要同步的方法、类或代码块中加入关键字,它能够保证同一时刻最多只有一个线程执行同一个对象的同步代码,可保证修饰的代码在执行过程中不会被其他线程干扰。

使用synchronized修饰的代码具有原子性和可见性

synchronized (obj) { //⽅法
 .......
 }

尽管Java实现的锁机制有很多种,并且有些锁机制性能也比synchronized高,但还是强烈推荐在多线程应用程序中使用该关键字。因为实现方便,后续工作由JVM来完成,可靠性高。只有在确定锁机制是当前多线程程序的性能瓶颈时,才考虑使用其他机制,如ReentrantLock等。

2、ReentrantLock

ReentantLock继承接口Lock并实现了接口中定义的方法,除了能完成synchronized所能完成的所有工作外,还提供了诸如可响应中断锁、可轮询锁请求、定时锁等避免多线程死锁的方法。

Lock实现的机理依赖于特殊的CPU指令可以认为不受JVM的约束。在并发量较小的多线程应用程序中,ReentrantLock与synchronized性能相差无几,但在高并发量的条件下, synchronized性能会迅速下降几十倍,而ReentrantLock的性能却能依然维持一个水准。

因此我们建议在高并发情况下使用ReentrantLock

ReentrantLock引入两个概念:公平锁与非公平锁

ReentrantLock通过方法lock()和unlock()进行加锁和解锁操作,与synchronized会被JVM自动解锁机制不同。ReentrantLock加锁后需要手动进行解锁。为了避免程序出现异常而无法正常解锁的情况,使用ReentrantLock必须在finally控制块中进行解锁操作。

Lock lock = new ReentrantLock(); 
try {
 lock.lock();
 //...进⾏任务操作
}
finally { 
 lock.unlock(); 
}

3、Semaphore

上述两种锁机制都是互斥锁,互斥是进程同步关系的一种特殊情况,相当于只存在一个临界资源。但是,在实际应用中,可能存在多个临界资源,这时候可以借助Semaphore信号量来完成多个临界资源的访问。

 Semaphore基本能完成ReentrantLock的所有工作,使用方法也与之类似,通过acquire()和release()方法来获取和释放临界资源。

经实测,Semaphone.acquire()方法默认为可响应中断锁,与ReentrantLock.lockInterruptibly()作用效果一致, 也就是说在等待临界资源的过程中可以被Thread.interrupt()方法中断。

此外,Semaphore也实现了可轮询的锁请求与定时锁的功能,除了方法名tryAcquire与tryLock不同,其使用方法与ReentrantLock几乎一致。

Semaphore也提供了公平与非公平锁的机制,也可在构造函数中进行设定。Semaphore的锁释放操作也由手动进行,因此与ReentrantLock一样,为避免线程因抛出异常而无法正常释放锁的情况发生,释放锁的操作也必须在finally代码块中完成。

 4、AtomicInteger

在多线程程序中,诸如++i、i++等运算不具有原子性,是不安全的线程操作之一。通常我们会使用synchronized将该操作变成一个原子操作,但JVM为此类操作特意提供了一些同步类,使得使用更方便,使程序运行效率变得更高效。通常AtomicInteger的性能是ReentrantLock的好几倍。

 总结:

1、synchronized:在资源竞争不是很激烈的情况下,偶尔会有同步的情况下,synchronized是很合适的。

2、ReentrantLock:在资源竞争不激烈的情况下,性能稍微比synchronized差点。但当同步非常激烈的时候,synchronized性能一下子下降几十倍,而ReentrantLock能维持常态。高并发量情况下使用ReentrantLock。

3、Atomic:和上面类似,不激烈情况下,性能比synchronized略逊;而在激烈时,也能维持常态。激烈的时候,Atomic性能会优于ReentrantLock一倍左右。但有个缺点,就是只能同步一个值,一段代码中只能出现一个Atomic的变量。

所以,我们写同步的时候,优先考虑synchronized,如果有特殊需要,再进一步优化。ReentrantLock和Atomic如果用得不好,不仅不能提高性能,还可能带来灾难。 

  • 13
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值