【八】java.util.concurrent包下的一些类

一、同步锁lock、及其一些常用实现类

同步锁lock,注意(与synchronized不同点):

1)使用lock无论如何都要手动释放锁(否则会发生死锁),所以将其放在finally块中,而synchronized离开块后会自动释放锁,一般不会发生死锁

2)Lock可以让等待锁的线程响应中断,在持有锁的线程长时间不释放锁时,可以中断等待锁的线程去干别的事(通过lock.lockInterruptibly()方法),避免死锁。而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;

lock.lockInterruptibly()方法:当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有再等待,那么此时能够中断线程B的等待过程。(当一个线程获取了锁之后,是不会被中断的,只能中断阻塞过程中的线程。)

3)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。lock.tryLock();(获取到锁返回true,否则返回false,也可以设置等待时间,在等待时间内获取到返回true,否则返回false)

4)Lock可以提高多个线程进行读操作的效率(ReadWriteLock)。

1、可重入锁

可重入锁( ReentrantLock):ReentrantLock是基于AQS实现,重入这里指的是在某线程已经获取锁之后(获取到锁执行期间),该线程可以再次获取锁,进入同步代码块。这里需要强调一下重入的概念中所指的线程是已经获得锁的的线程,这与线程安全不冲突,因为只有一个线程可以获取锁,也就是说始终都是同一个线程(获取锁的线程)在运行同步代码,相当于单线程运行,当然是线程安全的。synchronized关键字也是支持重入的,例如synchronized可以在递归调用中使用。

ReentrantLock提供了公平锁与非公平锁(默认为非公平锁)

公平锁:线程按照申请锁的等待队列顺序获得锁。(指定为公平锁:Lock lock = new ReentrantLock(true);)

非公平锁:线程随机获得锁,不按照申请锁的等待队列。

而synchronized的实现是非公平锁:线程随机获取到锁。

ReentrantLock提供了显式加解锁操作。提供了lock(),unlock()方法进行加解锁的操作,而synchronized是隐式进行加锁与解锁操作(依赖于编译器将其编译为moniterenter与moniterexit)。ReentrantLock对锁的等待可以中断,在持有锁的线程长时间不释放锁时,等待锁的线程可以选择放弃等待,这样就避免了synchronized可能带来的死锁问题。ReentrantLock.tryLock()可以设置等待时间。

eg:

        while(true){

            lock.lock();

            try {

                if(tickets>0){

                    System.out.println(Thread.currentThread().getName()+"成功售票:"+tickets);

                    Thread.sleep(20);

                    tickets--;

                }else{

                    break;

                }

            } finally {

                lock.unlock();

            }

        }

使用lock在进行线程通信时需要使用Condition的await、signal、signalAll方法,而不是Object类中的wait、notify、notifyAll

Condition condition1 = lock.newCondition();

注意:

Object的notify()方法能够唤醒一个正在等待该对象的锁的线程,当有多个线程都在等待该对象的锁的话,则只能唤醒其中一个线程,具体唤醒哪个线程则不得而知。使用wait、notify、notifyAll方法必须在synchronized代码块或者synchronized方法中

调用Condition的await()和signal(),signalAll()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用Conditon中的await()对应Object的wait(); Condition中的signal()对应Object的notify(),但是使用signal可以指定唤醒的线程; Condition中的signalAll()对应Object的notifyAll()

2、读写锁

当前有线程在占用读锁时,需要进行写操作,需要等待读锁释放后才可以获取写锁

当前有线程在占用写锁时,需要进行读操作,需要等待写锁释放后才可以获取读锁

举个例子:当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作会发生冲突现象,但是读操作和读操作不会发生冲突现象。

但是采用synchronized关键字来实现同步的话,就会导致一个问题:

如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程只能等待无法进行读操作。

因此就需要一种机制来使得多个线程都只是进行读操作时,线程之间不会发生冲突,通过Lock就可以办到。

另外,通过Lock可以知道线程有没有成功获取到锁。这个是synchronized无法办到的

/**
* 读写锁:
* 写写、读写互斥,需要等待释放锁
* 读读不互斥,不需要释放锁
* @author JACK
*
*/
public class TestReadWriteLock {
    public static void main(String[] args) {
        ReadAndLockDemo demo = new  ReadAndLockDemo();
        new Thread(new Runnable() {
            @Override
            public void run() {
                demo.set((int)(Math.random()*100));
            }
        },"write").start();
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    demo.get();
                }
            }).start();
        }
    }
}
class ReadAndLockDemo{
    private int number=0;
    ReadWriteLock readWriteLock = new  ReentrantReadWriteLock();
    //读
    public void get(){
        readWriteLock.readLock().lock();//上读锁
        try {
            System.out.println(Thread.currentThread().getName()+":"+number);
        } finally {
            readWriteLock.readLock().unlock();//释放读锁
        }
        
    }
    //写
    public void set(int number){
        readWriteLock.writeLock().lock();//上写锁
        try {
            System.out.println(Thread.currentThread().getName()+number);
            this.number=number;
        } finally {
            readWriteLock.writeLock().unlock();//释放写锁
        }
    }
}

3、闭锁

/**
* CountDownLatch:闭锁,在进行计算时,只有在其他线程计算全部完成,当前运算才执行
* @author JACK
*
*/
public class TestCountDownLatch {
    public static void main(String[] args) {
        CountDownLatch latch = new  CountDownLatch(5);//参数表示?个线程完成后,主线程才运算
        LatchDemo latchDemo = new  LatchDemo(latch);
        long currentTimeMillis =  System.currentTimeMillis();
        for(int i=0;i<5;i++){
            new Thread(latchDemo).start();
        }
        try {
            latch.await();//等待其他线程上的运算完成,当前线程才可以进行运算
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long currentTimeMillis2 =  System.currentTimeMillis();
        System.out.println("耗费时间:"+(currentTimeMillis2-currentTimeMillis));
    }
}
class LatchDemo implements Runnable{
    private CountDownLatch latch;
    
    public LatchDemo(CountDownLatch latch) {
        this.latch=latch;
    }
    @Override
    public void run() {
        System.out.println("线程");
        try{
            for(int i=1;i<10000;i++){
                System.out.println(i);
            }
        }finally{
            latch.countDown();//线程数减1
        }
    }
    
}

猜你喜欢

转载自blog.csdn.net/Jack_PJ/article/details/88015515