java多线程(二)- juc包-ReenTrantLock和ReadWriteLock

ReenTrantLock

ReenTrantLock 是 java.util.locks包提供的锁,可用于代替synchronized 实现更细粒度的锁。

class Count {
    private Lock lock = new ReentrantLock();

    private int value = 0;

    public void add(int m) {
        lock.lock();
        try {
            this.value += m;
        } finally {
            lock.unlock();
        }
    }

    public void dec(int m ) {
        lock.lock();
        try {
            this.value -= m;
        } finally {
            lock.unlock();
        }
    }

    public int get() {
        lock.lock();
        try {
            return this.value;
        } finally {
            lock.unlock();
        }
    }
}
public class ReentrantLockTest {
    final static int LOOP = 10000;
    public static void main(String[] args) throws InterruptedException {
        Count count = new Count();
        Thread t1 = new Thread() {
            @Override
            public void run() {
                for (int i = 0; i< LOOP; i++) {
                    count.add(1);
                }
            }
        };

        Thread t2 = new Thread() {
            @Override
            public void run() {
                for(int i = 0 ; i < LOOP; i++) {
                    count.dec(1);
                }
            }
        };
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count.get());
    }
}

ReenTrantLock 和 synchronized的区别

  1. ReenTrantLock
    是一个类,调用lock()获取锁后,需要手动释放,所以通常放在try的finlly中释放锁。synchronized是java提供的关键字,不用手动释放锁。
  2. 在JDK1.5之后synchronized引入了偏向锁,轻量级锁和重量级锁。至此两者性能已相差无几

两者都属于可重入锁(获取锁后可以再次获取),一般来说sychronized内部的锁抢占机制已经够用,在需要用到ReenTrantLock 特性时才需要选用ReenTrantLock。

ReenTrantLock 特性

可实现公平锁

可设置公平性,倾向于等待时间最久的线程获取到锁+会增加额外的开销所以性能没有sychronized高

ReentranLock fairLock = new ReentrantLock(true)

等待可中断

对于synchronized来得到说,如果一个线程在等待锁,那么结果只有两种情况。要么锁继续执行,要么保持等待。而ReentrantLock可以在等待锁的过程中,程序可以取消对锁的请求

public class ReentrantLockInterrupt implements Runnable{

    public static ReentrantLock lock1 = new ReentrantLock();
    public static ReentrantLock lock2 = new ReentrantLock();
    int lock;

    public ReentrantLockInterrupt(int lock) {
        this.lock = lock;
    }
    @Override
    public void run() {
        try{
            if(lock == 1) {
                lock1.lockInterruptibly();
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                lock2.lockInterruptibly();
            } else {
                lock2.lockInterruptibly();
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                lock1.lockInterruptibly();
            }

        } catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            if(lock1.isHeldByCurrentThread()) {
                lock1.unlock();
            }
            if(lock2.isHeldByCurrentThread()) {
                lock2.unlock();
            }
            System.out.println(Thread.currentThread().getId() +": 线程退出");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ReentrantLockInterrupt r1 = new ReentrantLockInterrupt(1);
        ReentrantLockInterrupt r2 = new ReentrantLockInterrupt(2);
        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r2);
        t1.start();
        t2.start();
        Thread.sleep(1000);

        t1.interrupt();

    }
}

以上如果是使用synchronized,会导致死锁,而使用ReentrantLock可以使用interrupt方法中断等待锁,这样就不会出现死锁。

锁申请等待限时

//尝试获取锁,获取不成功也不会等待
public boolean tryLock() {
        return this.sync.nonfairTryAcquire(1);
    }

//设置获取失败后等待多久就放弃等待
    public boolean tryLock(long var1, TimeUnit var3) throws InterruptedException {
        return this.sync.tryAcquireNanos(1, var3.toNanos(var1));
    }

ReentrantLock可以调用上述方法来设置等待的时间,实例如下。

public class ReentrantLockTryLock implements Runnable{
    public static ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        try {

            if(lock.tryLock(5, TimeUnit.SECONDS)) {
                Thread.sleep(6000);
            } else {
                System.out.println("获取锁失败");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if( lock.isHeldByCurrentThread() ) {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        ReentrantLockTryLock t1 = new ReentrantLockTryLock();
        Thread th1 = new Thread(t1);
        Thread th2 = new Thread(t1);
        th1.start();
        th2.start();
    }
}

锁可以绑定多个条件

使用synchronized时,使用wait/notify来进行进程间的协调。
使用ReentrantLock + Condition可以替代 synchronized + wait/notify

通过Lock接口的Condition newCondition()方法可以生成一个与当前重入锁锁定的Condition实例。Conditon接口定义如下

public interface Condition {
//会让当前线程等待,同时释放锁。和Object.wait()像似
    void await() throws InterruptedException;
//和await()一样,但是不会响应中断
    void awaitUninterruptibly();

    long awaitNanos(long var1) throws InterruptedException;

    boolean await(long var1, TimeUnit var3) throws InterruptedException;

    boolean awaitUntil(Date var1) throws InterruptedException;
//唤醒一个在等待中的线程,使用前要获取锁,用完后要释放锁,否则被通知的线程不能得到锁,也就无法真正的执行
    void signal();

    void signalAll();
}

示例

class TaskQueue {
    final Queue<String> queue = new LinkedList<>();

    final Lock lock = new ReentrantLock();
    final Condition notEmpty = lock.newCondition();

    public String getTask() throws InterruptedException {
        try{
            lock.lock();
            while (this.queue.isEmpty()) {
                notEmpty.await();
            }
        }  finally {
            lock.unlock();
        }
        return queue.remove();
    }

    public void addTask(String name) {
        try {
            lock.lock();
            this.queue.add(name);
            notEmpty.signalAll();
        }finally {
            lock.unlock();
        }
    }

}

class WorkThread extends Thread {
    TaskQueue taskQueue;

    public WorkThread(TaskQueue taskQueue) {
        this.taskQueue = taskQueue;
    }

    @Override
    public void run() {
        String  name = "";
        while (!isInterrupted()) {
            try {
                name = taskQueue.getTask();
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println("中断线程");
                break;
            }
            String result = "hello: " + name;
            System.out.println(result);
        }

    }
}

public class ReenterLockCondition2 {
    public static void main(String[] args) throws InterruptedException {
        TaskQueue taskQueue = new TaskQueue();
        WorkThread worker = new WorkThread(taskQueue);
        worker.start();
        taskQueue.addTask("Bob");
        Thread.sleep(1000);
        taskQueue.addTask("Alice");
        Thread.sleep(1000);
        taskQueue.addTask("Tim");
        Thread.sleep(1000);
        worker.interrupt();
        worker.join();
        System.out.println("END");
    }
}

lock绑定多条件Condition

实现多线程之间按顺序调用,实现A->B->C三个线程启动,要求A打印5次,B打印10次,C打印15次,ABC都循环10次

class ShareData {
    private int number = 1;

    private Lock lock = new ReentrantLock();
    private Condition cd1 = lock.newCondition();
    private Condition cd2 = lock.newCondition();
    private Condition cd3 = lock.newCondition();

    public void print5() {
        lock.lock();
        try {
            //1 判断,用while , 不要用if
            while (number != 1) {
                cd1.await();
            }
            //2 业务逻辑
            for (int i = 1; i<= 5;i++) {
                System.out.println(Thread.currentThread().getName() + ": number");
            }
            //3 通知
            number = 2;
            cd2.signal();
        } catch (InterruptedException e) {
            System.out.println("1中断");
            e.printStackTrace();
        } finally {
            System.out.println("cd1 释放锁");
            lock.unlock();
        }
    }

    public void print10() {
        lock.lock();
        try {
            //1 判断
            while (number != 2) {
                cd2.await();
            }
            //2 业务,try里放循环?
            for (int i = 1; i<= 10; i++) {
                System.out.println(Thread.currentThread().getName() + ": number");
            }
            //3 通知
            number = 3;
            cd3.signal();
        }catch (InterruptedException e){
            System.out.println("2中断");
            e.printStackTrace();
        } finally {
            System.out.println("cd2 释放锁");
            lock.unlock();
        }
    }

    public void print15() {
        lock.lock();
        try {
            while (number!= 3) {
                cd3.await();
            }
            for (int i = 1; i<= 15; i++) {
                System.out.println(Thread.currentThread().getName() + ": number");
            }
            number = 1;
            cd1.signalAll();
        } catch (InterruptedException e){
            System.out.println("3中断");
            e.printStackTrace();
        } finally {
            System.out.println("cd3 释放锁");
            lock.unlock();
        }
    }
}


public class MoreCondition {

    public static void main(String[] args) {
        ShareData shareData = new ShareData();

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i = 1; i <= 10 ; i++) {
                    shareData.print5();
                }
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i = 1; i <= 10 ; i++) {
                    shareData.print10();

                }
            }
        });

        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i = 1; i <= 10 ; i++) {
                        shareData.print15();
                }
            }
        });

        t1.start();
        t2.start();
        t3.start();
    }

}

ReentrantLock实现原理

ReentrantLock实现Lock接口,在ReentrantLock中引用了AbstractQueuedSynchronizer的子类,所有的同步操作都是依靠AbstractQueuedSynchronizer(队列同步器)实现。

public class ReentrantLock implements Lock, java.io.Serializable {
 private final Sync sync;
 abstract static class Sync extends AbstractQueuedSynchronizer {
 abstract void lock();

        /**
         * Performs non-fair tryLock.  tryAcquire is implemented in
         * subclasses, but both need nonfair try for trylock method.
         */
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }
							 
							  
		final ConditionObject newCondition() {
            return new ConditionObject();
        }
	}
							  
							  
		 static final class NonfairSync extends Sync {
     
        /**
        以CAS的方式尝试获取锁,如果获取失败,则申请锁
         */
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }


    static final class FairSync extends Sync {
  //直接申请锁。先申请先排队,公平锁,而非公平锁是CAS操作成功则取锁成功
        final void lock() {
            acquire(1);
        }

       
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
			//比非公平锁多了hasQueuedPredecessors(),该函数的逻辑是判断队列里是否只有一个元素,或者元素是失效的,或者元素就是当前线程的。只有这样,才能进行CAS操作。
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }
}

总结:ReetrantLock中有公平锁和非公平锁之分,底层都是通过 队列同步器AQS实现的。非公平锁的加锁过程是先CAS,失败则申请锁。而公平锁则是直接申请锁。

compareAndSetState是通过Unsafe类实现的,Unsage类封装了CAS方法(是一个Native方法)

/**
     * tips: 1.compareAndSetState的实现主要是通过Unsafe类实现的。
     *       2.之所以命名为Unsafe,是因为这个类对于JVM来说是不安全的,我们平时也是使用不了这个类的。
     *       3.Unsafe类内封装了一些可以直接操作指定内存位置的接口,是不是感觉和C有点像了?
     *       4.Unsafe类封装了CAS操作,来达到乐观的锁的争抢的效果
     */
    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

ReadWriteLock

使用ReadWriteLock可以提高读取效率:

  • ReadWriteLock只允许一个线程写入(其他线程不能读写)

  • ReadWriteLock允许多个线程同时读取

  • ReadWriteLock适合读多写少的场景


class Counter {
    private ReadWriteLock lock = new ReentrantReadWriteLock();
    private Lock readLock = lock.readLock();
    private Lock writeLock = lock.writeLock();

    private int value = 0;
    public void add(int m ) {
        writeLock.lock();
        try {
            this.value += m;
        } finally {
            writeLock.unlock();
        }
    }

    public void dec(int m) {
        writeLock.lock();
        try {
            this.value -= m;
        } finally {
            writeLock.unlock();
        }
    }

    public int get() {
        readLock.lock();
        try {
            return this.value;
        } finally {
            readLock.unlock();
        }
    }
}

public class ReadWriteLockTest {
    final static int LOOP = 100;
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        Thread t1 = new Thread() {
            public void run() {
                for (int i = 0; i < LOOP; i++) {
                    counter.add(1);
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        Thread t2 = new Thread() {
            public void run() {
                for (int i = 0; i < LOOP ; i++) {
                    counter.dec(1);
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        Thread t3 = new Thread() {
            public void run() {
                for (int i = 0; i < LOOP * 2; i++) {
                    System.out.println(counter.get());
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t1.start();
        t2.start();
        t3.start();
        t1.join();
        t2.join();
        System.out.println(counter.get());

    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值