java.util.concurrent包是专门为java并发编程而设计的包,有如下分类:
(1)locks:显示锁(互斥锁和速写锁)相关
(2)atomic:原子变量类相关,是构建非阻塞算法的基础
(3)executor:线程池相关
(4)collections:并发容器相关
(5)tools部分:同步工具相关,如信号量,闭锁,栅栏等功能
这个包里面提供了一组原子变量类。其基本的特性就是在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具有排他性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择一个另一个线程进入,这只是一种逻辑上的理解。实际上是借助硬件的相关指令来实现的,不会阻塞线程(或者说只是在硬件级别上阻塞了)。可以对基本数据、数组中的基本数据、对类中的基本数据进行操作
第一组AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference这四种基本类型用来处理布尔,整数,长整数,对象四种数据,其内部实现不是简单的使用synchronized,而是一个更为高效的方式CAS (compare and swap) + volatile和native方法,从而避免了synchronized的高开销,执行效率大为提升。
CAS(Compare and swap)比较和替换是设计并发算法时用到的一种技术。简单来说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替换当前变量的值。——实现比较和交换的原子性操作
(1)set( )和get( )方法:可以原子地设定和获取atomic的数据。类似于volatile,保证数据会在主存中设置或读取
(2)compareAndSet( ) :这2个方法接受2个参数,一个是期望数据(expected),一个是新数据(new);如果atomic里面的数据和期望数据一 致,则将新数据设定给atomic的数据,返回true,表明成功;否则就不设定,并返回false。
(3)getAndSet( )方法:原子的将数据设置为新数据,同时返回先前的旧数据
private static final Unsafe unsafe = Unsafe.getUnsafe();
private volatile int value;
public final int get() {
return value;
}
public final void set(int newValue) {
value = newValue;
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
public final int getAndSet(int newValue) {
for (;;) {
int current = get();
if (compareAndSet(current, newValue))
return current;
}
}
(4)getAndIncrement( ):以原子方式将当前值加 1,相当于线程安全的i++操作。
(5)incrementAndGet( ):以原子方式将当前值加 1, 相当于线程安全的++i操作。
(6)getAndDecrement( ):以原子方式将当前值减 1, 相当于线程安全的i--操作。
(7)decrementAndGet ( ):以原子方式将当前值减 1,相当于线程安全的--i操作。
(8)addAndGet( ): 以原子方式将给定值与当前值相加, 实际上就是等于线程安全的i =i+delta操作。
(9)getAndAdd( ):以原子方式将给定值与当前值相加, 相当于线程安全的t=i;i+=delta;return t;操作。
AtomicIntegerArray,AtomicLongArray还有AtomicReferenceArray类进一步扩展了原子操作,对这些类型的数组提供了支持。他们内部并不是像AtomicInteger一样维持一个valatile变量,而是全部由native方法实现
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final int base = unsafe.arrayBaseOffset(int[].class);
private static final int scale = unsafe.arrayIndexScale(int[].class);
private final int[] array;
public final int get(int i) {
return unsafe.getIntVolatile(array, rawIndex(i));
}
public final void set(int i, int newValue) {
unsafe.putIntVolatile(array, rawIndex(i), newValue);
}
第三组AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater基于反射的实用工具,可以对指定类的指定 volatile
字段进行原子更新。API非常简单,但是有一些约束:字段必须是volatile类型的非静态变量
1. 锁在多线程编程中有很重要的作用,synchronized比较常见也很常用,但是Lock提供了更广泛的锁操作,处理多线程同步的问题也更加优雅和灵活
注意:lock需要显示的获取和释放锁,繁琐
synchronized不需要显示的获取和释放锁,简单
(1)synchronized代码块不能够保证公平性
(2)对synchronized代码块的访问不能设置锁等待超时时间,不能中断响应
(3)synchronized必须完整的包含在单个方法里,而一个lock对象可以把lock和unlock方法放在不同的方法里
“可重入”,就是支持重进入的锁,它表示该锁能够支持一个线程对资源的重复加锁。
该锁还支持获取锁时的公平和非公平性选择。“公平”是指“不同的线程获取锁的机制是公平的”,而“不公平”是指“不同的线程获取锁的机制是非公平的”。
(1)示例
public class ReenterLock implements Runnable{
public static ReentrantLock lock = new ReentrantLock();
public static int i;
@Override
public void run() {
for (int j=0;j<1000;j++){
lock.lock();
//lock.lock();
i++;
lock.unlock();
//lock.unlock();
System.out.println(Thread.currentThread().getName()+"-----"+i);
}
}
public static void main(String[] args) {
ReenterLock reenterLock = new ReenterLock();
Thread thread1 = new Thread(reenterLock);
Thread thread2 = new Thread(reenterLock);
thread1.start();
thread2.start();
}
}
为什么称作是“重入”?这是因为这种锁是可以反复进入的。将上面代码中注释部分去除注释,也就是连续两次获得同一把锁,两次释放同一把锁,这是允许的。
注意,获得锁次数与释放锁次数要相同,如果释放锁次数多了,会抛出 java.lang.IllegalMonitorStateException 异常;如果释放次数少了,相当于线程还持有这个锁,其他线程就无法进入临界区。
使用重入锁,等待锁的线程可以被中断。也就是在等待锁的过程中,程序可以根据需要取消对锁的需求。(避免死锁)
import java.util.concurrent.locks.ReentrantLock;
public class ReinLock{
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new DeadLock(true));
Thread t2 = new Thread(new DeadLock(false));
t1.start();
t2.start();
Thread.sleep(5000);
//中断响应
t2.interrupt();
}
}
class DeadLock implements Runnable{
private static ReentrantLock lock1= new ReentrantLock();
private static ReentrantLock lock2= new ReentrantLock();
boolean flag;
DeadLock(boolean flag){
this.flag = flag;
}
@Override
public void run() {
if (flag){
try {
lock1.lockInterruptibly();
System.out.println("t1 get lock1...");
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
System.out.println("t1 get lock2...begin");
lock2.lockInterruptibly();
System.out.println("t1 get lock2...success");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
if (lock1.isHeldByCurrentThread()){
lock1.unlock();
}
if (lock2.isHeldByCurrentThread()){
lock2.unlock();
}
}
}else {
try {
lock2.lockInterruptibly();
System.out.println("t2 get lock2...");
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
System.out.println("t2 get lock1...begin");
lock1.lockInterruptibly();
System.out.println("t2 get lock1...success");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
if (lock1.isHeldByCurrentThread()){
lock1.unlock();
}
if (lock2.isHeldByCurrentThread()){
lock2.unlock();
}
}
}
}
}
可以看到,产生死锁后由于t2.interrupt() ,释放掉锁,t1才顺利获得到lock2
除了中断响应外,锁申请等待限时也可以避免死锁
可以使用一次trylock方法进行一次限时的等待
(代码同上,不再重复写了)
lock1.tryLock(5, TimeUnit.SECONDS);
默认情况下,锁的申请都是非公平的。也就是说,如果线程 1 与线程 2,都申请获得锁 A,那么谁获得锁不是一定的,是由系统在等待队列中随机挑选的。这就好比,买票的人不排队,售票姐姐只能随机挑一个人卖给他,这显然是不公平的。而公平锁,它会按照时间的先后顺序,保证先到先得。公平锁的特点是:不会产生饥饿现象。
public ReentrantLock(boolean fair)
1.读锁是共享锁,写锁是排他锁
2.测试
public class ReadWriteDemo {
private Map map = new HashMap<>();
//获取读写锁
private ReadWriteLock lock = new ReentrantReadWriteLock();
//获取读锁
private Lock readLock = lock.readLock();
//获取写锁
private Lock writeLock = lock.writeLock();
public Object get(String key){
readLock.lock();
try {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return map.get(key);
}finally {
readLock.unlock();
}
}
public void put(String key,Object value) {
writeLock.lock();
try {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"开始执行写操作...");
map.put(key, value);
}finally {
writeLock.unlock();
System.out.println(Thread.currentThread().getName()+"写操作执行完毕...");
}
}
}
public class ReadWriteTest {
public static void main(String[] args) {
ReadWriteDemo demo = new ReadWriteDemo();
new Thread(new Runnable() {
@Override
public void run() {
demo.put("a", 10);
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
demo.put("b", 20);
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
demo.put("c", 30);
}
}).start();
}
}
说明写锁是排他锁(其它暂时就不测了)