并发编程(4)concurrent包

一.概述

    java.util.concurrent包是专门为java并发编程而设计的包,有如下分类:

     (1)locks:显示锁(互斥锁和速写锁)相关                

    (2)atomic:原子变量类相关,是构建非阻塞算法的基础               

    (3)executor:线程池相关            

    (4)collections:并发容器相关    

    (5)tools部分:同步工具相关,如信号量,闭锁,栅栏等功能

                                

二.原子类

     这个包里面提供了一组原子变量类。其基本的特性就是在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具有排他性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择一个另一个线程进入,这只是一种逻辑上的理解。实际上是借助硬件的相关指令来实现的,不会阻塞线程(或者说只是在硬件级别上阻塞了)。可以对基本数据、数组中的基本数据、对类中的基本数据进行操作

  • 标量类(Scalar):AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
  • 数组类:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
  • 更新器类:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
  • 复合变量类:AtomicMarkableReference,AtomicStampedReference

1.标量类

    第一组AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference这四种基本类型用来处理布尔,整数,长整数,对象四种数据,其内部实现不是简单的使用synchronized,而是一个更为高效的方式CAS (compare and swap) + volatile和native方法,从而避免了synchronized的高开销,执行效率大为提升。

    CAS(Compare and swap)比较和替换是设计并发算法时用到的一种技术。简单来说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替换当前变量的值。——实现比较和交换的原子性操作

       AutomicInteger实现片段

    (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;  
    }  
}  
    对于AutomicInteger,AutomicLong还提供了一些特别的方法

(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;操作。

    使用AutomicReference创建线程安全的堆栈

2.数组类

      AtomicIntegerArray,AtomicLongArray还有AtomicReferenceArray类进一步扩展了原子操作,对这些类型的数组提供了支持。他们内部并不是像AtomicInteger一样维持一个valatile变量,而是全部由native方法实现

    AutomicIntegerArray实现片段:
  
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);  
} 


3.更新器类

      第三组AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater基于反射的实用工具,可以对指定类的指定 volatile 字段进行原子更新。API非常简单,但是有一些约束:字段必须是volatile类型的非静态变量


三.lock

  1.  锁在多线程编程中有很重要的作用,synchronized比较常见也很常用,但是Lock提供了更广泛的锁操作,处理多线程同步的问题也更加优雅和灵活

    注意:lock需要显示的获取和释放锁,繁琐

                synchronized不需要显示的获取和释放锁,简单

    

2.lock 与synchronized的区别

    (1)synchronized代码块不能够保证公平性

    (2)对synchronized代码块的访问不能设置锁等待超时时间,不能中断响应

    (3)synchronized必须完整的包含在单个方法里,而一个lock对象可以把lock和unlock方法放在不同的方法里 

3.ReentrantLock  重入锁

    ReentrantLock通过自定义队列同步器(AQS-AbstractQueuedSychronized,是实现锁的关键)来实现锁的获取与释放。

   “可重入”,就是支持重进入的锁,它表示该锁能够支持一个线程对资源的重复加锁。

   该锁还支持获取锁时的公平和非公平性选择。“公平”是指“不同的线程获取锁的机制是公平的”,而“不公平”是指“不同的线程获取锁的机制是非公平的”。

    (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 异常;如果释放次数少了,相当于线程还持有这个锁,其他线程就无法进入临界区。

(2)中断响应(lockinterruptibly)

        使用重入锁,等待锁的线程可以被中断。也就是在等待锁的过程中,程序可以根据需要取消对锁的需求。(避免死锁)

 

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


(4).锁申请等待限时(tryLock)

除了中断响应外,锁申请等待限时也可以避免死锁

可以使用一次trylock方法进行一次限时的等待

(代码同上,不再重复写了)

lock1.tryLock(5, TimeUnit.SECONDS);

(5).公平锁

默认情况下,锁的申请都是非公平的。也就是说,如果线程 1 与线程 2,都申请获得锁 A,那么谁获得锁不是一定的,是由系统在等待队列中随机挑选的。这就好比,买票的人不排队,售票姐姐只能随机挑一个人卖给他,这显然是不公平的。而公平锁,它会按照时间的先后顺序,保证先到先得。公平锁的特点是:不会产生饥饿现象。

重入锁允许对公平性进行设置,构造函数
public ReentrantLock(boolean fair)


4.读写锁(ReentrantReadWriteLock)

1.读锁是共享锁,写锁是排他锁

2.测试

public class ReadWriteDemo {
    private Map<String ,Object> 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();
    }
}

说明写锁是排他锁(其它暂时就不测了)



猜你喜欢

转载自blog.csdn.net/qq_34645958/article/details/80948632