线程讲解(六)

在程序的设计语言中,锁提供了一种数据安全访问的方式,锁一般分为加锁和解锁两个操作.对共享数据操作前,要先进行加锁;操作完成后,再进行解锁.
加锁以后的临界区只能被持有锁的线程占有,其他线程不能进入这段临界区,只能等待.
Java 语言提供同步锁,可重入锁和读写锁等同步机制,用于确保数据访问的正确性.

同步锁

在 JAVA 语言中,从 JDK1.0 开始就支持同步锁的使用了.它可以采用两形式:同步方法和同步代码块.
不论哪种方式,都需要使用 synchronized 关键字,但两者表现形式不同.

同步方法

采用 synchronized 作为方法的修饰词,将方法整体限定在同步控制区域内;同一时刻只能有一个线程对其进行访问.

同步块

同步块是使用 synchronized 修饰的一块代码,它不像同步方法那样使整个方法都是被同步控制,而是针对某一块代码进行同步控制.
同步块需要明确地指出监视器对象,通常加载 synchronized 后的小括内,使用比较多的情况是使用当前对象 this 作为监视器对象.

比较

同步块比同步方法可以实现更细粒度的同步控制,但同步方法的使用更加简便,不用考虑同步对象等因素.但是,有时整个方法加上 synchronized 块,程序性能并不好,这是因为函数内部可能需要同步的只有小部分共享数据而已.
需要注意的是,这两种方法都是使用 JVM 内置的监视器.

可重入锁

可重入锁是一种无阻塞的同步机制,它在 java.util.concurrent.locks 包下;定义的形式如下:
public class ReentrantLock extends Object implements Lock ,Serializable
可重入锁是互斥锁,它和同步锁具有基本相同的行为和语义,但是比同步锁功能更强大.如获取锁时的公平性设置,测试锁 trylock,测试锁是否正在被持有,锁的获取顺序等.

读写锁

读写锁从 JDK1.5 版本开始引入的一种锁机制,它维护一对相互关联的锁:读锁和写锁.在没有线程持有写锁的情况下,读锁可以由多个线程同时持有;写锁是排他锁,只能有一个线程持有.
读写锁允许多个线程同时读,只允许一个线程同时写.
读写锁 ReentrantReadWriteLock 类定义的一般形式:
public class ReentrantReadWriteLock extends Object implements ReadWriteLock,Serializable

邮戳锁

邮戳锁是 JDK1.8 版本后引入的一种锁机制,与 ReentrantReadWriteLock 类似,该锁可以用于控制读写访问.邮戳锁的定义形式:
public class StampedLock extends Object implements Serializable
从邮戳锁的定义可以看出,它是从类 Object 直接继承,与 ReentrantReadWriteLock 类似,它实现了 Serializable 接口.由于邮戳锁支持多种锁模式,所以这个类没有直接实现接口 Lock 和接口 ReadWriteLock.

死锁和活锁

在使用锁的时候,要注意避免死锁和活锁的问题,两者都会引起线程等待,降低程序的执行效率.

死锁

死锁是指两个或者多个线程在执行过程中,因竞争资源而相互等待的现象.处于死锁状态的线程无法继续运行,只有死锁解除才能继续.

活锁

活锁指程序在执行过程中,由于某些条件发送,会导致程序一直处于等待状态.与死锁类似,任务的处理一直处于等待状态,得不到解决,无法继续进行下去;与死锁不同的是,活锁有可能解开,但死锁不行.

volatile 变量

当多个线程对变量进行操作时,实际上每个线程都拥有自己的本地存储,在本地存储中有该变量的私有拷贝,变量的操作结果先放入本地存储,然后再复制回主存储区域.
如果一旦遇到多线程访问某一个类的域变量的情况,我们就使用锁进行同步控制,有时带来的开销可能比较大.因此,java 语言提供了一种稍弱的同步机制,即以 volatile 关键词修饰变量,它提供了对于实例域并发访问的功能.

原子操作

一个操作是原子的,表示该操作要么全部做,要么全部不做.
在同步控制操作中,原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何上下文切换.有了原子操作后,一般不需要对程序加锁,通过原子操作可以实现程序的同步.
在 java.util.concurrent.atomic 包中提供了 AtomicBoolean,AtomicInteger,AtomicLong,AtomicIntegerArray和AtomicReference等原子类.这些原子类为单一变量提供了一种无锁的,线程安全的访问方式,每一个类提供了对于相应类型的变量进行原子更新的方法.

基本类型的原子类

基本类型的原子类包括:AtomicBoolean,AtomicInteger,AtomicLong等.

一般引用类型的原子类

一般引用类型的原子类:AtomicReference.

ABA 问题

原子操作相对于无锁,无阻塞的操作有一定的优势,但是它存在ABA问题.
CAS 操作一般通过比较某一个对象引用的当前值和期望值,如果当前值和期望值相等,则将其替换为新值.
CAS 操作的核心是看某一个值是否已经改变,也就是说,要保证在对某一变量操作时该变量没有被其他线程改变过,只要没有改变,就可以更新.
如果期间某一个线程对该值进行了改变,然后又恢复了原值,则这种情况就是ABA问题.

扩展的原子引用类

AtomicMarkableReference
AtomicMarkableReference 类是一个线程安全的类,该类封装了一个对象的引用 reference 和一个布尔值 mark,可以原子性地对这两个值进行更新.
AtomicStampedReference
AtomicStampedReference 类维护了一个对象的引用 reference 和一个整数值 stamp,这两个值可以原子地进行更新.

原子操作数组类

定义一个数组的原子操作:

AtomicInteger [] aiArray = new AtomicInteger[100];

JDK 中也提供了相关的类,可以使用原子操作数组类 AtomicIntegerArray 类来定义原子数组:

AtomicIntegerArray aia = new AtomicIntegerArray(100);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

腹黑的乌鸡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值