并发编程-锁

ReentrantLock

在Java多线程中,可以使用synchronized关键字来实现线程之间的同步互斥,但在JDK1.5中新增加ReentranLock类也可以达到同样的效果,并且在扩展功能上更加强大,比如具有嗅探锁定、多路分支通知等,而且在使用上也比synchronized更加灵活。从运行结果来看,当前线程打印完毕之后将锁进行释放,其它线程才可以继续 打印。线程打印的数组是分级打印,因为当前线程已经持有锁,但线程之间打印的顺序是随机的。调用lock方法的线程就会持有对象锁,其它的线程只能等待锁被释放(调用unlock方法)才可以再次争抢锁。效果和synchronized关键字一样,线程之间还是按照顺序执行

public class TestReentrantLock {
    private Lock lock;
    public TestReentrantLock(Lock lock) {
        this.lock = lock;
    }

    public  void printNumber() {
        try {
            lock.lock();
            for (int i = 0; i <= 6; i++) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        TestReentrantLock reentrantLock = new TestReentrantLock(lock);
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                reentrantLock.printNumber();
            }).start();
        }
    }
}

ReentrantLock实现wait和notifty

ReentrantLock 实现了 Lock 接口,并提供了与synchronized 相同的互斥性和内存可见性,ReentrantLock也可以实现等待/通知模式的功能,但需要借助Condition对象。Condition类是在JDK5中出现的技术。synchronized就相当于整个Lock对象中只有一个单一Condition对象,所有的线程都是注册在它一个对象的身上。线程开始notifyAll时需要通知所有正在等待的线程,没有选择权,会出现相当大效率问题。

public class TestReentrantLock {
    public static void main(String[] args) throws InterruptedException {
        ReentrantLockService service = new ReentrantLockService();
        Thread thread = new AwaitThread(service);
        thread.start();
        TimeUnit.SECONDS.sleep(3);
        service.signal();
    }
}
class ReentrantLockService {
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void await() {
        lock.lock();
        System.out.println(Thread.currentThread().getName() + "begin await:" + System.currentTimeMillis());
        try {
            condition.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        lock.unlock();
    }

    public void signal() {
        lock.lock();
        System.out.println(Thread.currentThread().getName() + "begin signal:" + System.currentTimeMillis());
        condition.signal();
        lock.unlock();
    }
}
class AwaitThread extends Thread {
    private ReentrantLockService service;
    public AwaitThread(ReentrantLockService service) {
        this.service = service;
    }
    @Override
    public void run() {
        service.await();
    }
}

Condition接口来实现多路通知

在一个Lock对象里面可以创建多个Condition实例(Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得Condition 实例,使用其 newCondition() 方法),线程对象可以注册在指定的Condition中,从而可以有选择性地进行线程通知,在调试线程上更加灵活。在使用notify/notifyAll方法进行通知时,被通知线程是由JVM随机选择的,但使用ReentrantLock结合Condition类可以实现选择性通知,这个功能是非常重要的,而且在Condition在中是默认提供的,单个Lock可能与多个Condition对象关联

public class TestMultiReentrantLock {

    public static void main(String[] args) throws InterruptedException {
        MultiReentrantLockService service = new MultiReentrantLockService();
        new Thread((service::await1)).start();
        new Thread((service::await2)).start();

        TimeUnit.SECONDS.sleep(5);
        service.signal();
    }
}

class MultiReentrantLockService {
    private ReentrantLock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();//单个Lock可能与多个Condition对象关联
    private Condition condition2 = lock.newCondition();//单个Lock可能与多个Condition对象关联

    public void await1() {
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + " begin await1...");
            condition1.await();
            System.out.println(Thread.currentThread().getName() + " signal await1...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        finally {
            lock.unlock();
        }
    }

    public void await2() {
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + " begin await2...");
            condition2.await();
            System.out.println(Thread.currentThread().getName() + " signal await2...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        finally {
            lock.unlock();
        }
    }

    public void signal() {
        try {
            lock.lock();
            condition1.signalAll();
            System.out.println(Thread.currentThread().getName() + " begin signal...");
        } catch (Exception e) {
            e.printStackTrace();
        }
        finally {
            lock.unlock();
        }
    }
}

PS:
在 Condition 对象中,与 wait、notify 和 notifyAll 方法对应的分别是await、signal 和 signalAll

面试题:开启3个线程,这三个线程的Name分别为A、B、C,每个线程将自己的Name在屏幕上打印5遍

public class TestReentrantLock {

    public static void main(String[] args) {
        Print print = new Print();
        new Thread(()->{
            for (int i = 0; i < 5; i++) {
                print.printA();
            }
        }, "A").start();

        new Thread(()->{
            for (int i = 0; i < 5; i++) {
                print.printB();
            }
        }, "B").start();

        new Thread(()->{
            for (int i = 0; i < 5; i++) {
                print.printC();
            }
        }, "C").start();

    }
}

class Print {
    private String flag = "A";
    final ReentrantLock lock = new ReentrantLock();
    final Condition conditionA = lock.newCondition();
    final Condition conditionB = lock.newCondition();
    final Condition conditionC = lock.newCondition();

    public void printA() {
        try {
            lock.lock();
            if (!"A".equals(flag)) {
                conditionA.await();
            }
            System.out.print(Thread.currentThread().getName());
            flag = "B";
            conditionB.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        finally {
            lock.unlock();
        }
    }

    public void printB() {
        try {
            lock.lock();
            if (!"B".equals(flag)) {
                conditionB.await();
            }
            System.out.print(Thread.currentThread().getName());
            flag = "C";
            conditionC.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        finally {
            lock.unlock();
        }
    }

    public void printC() {
        try {
            lock.lock();
            if (!"C".equals(flag)) {
                conditionC.await();
            }
            System.out.print(Thread.currentThread().getName());
            flag = "A";
            conditionA.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        finally {
            lock.unlock();
        }
    }
}

tryLock和TryLock(time, uint)

public class TestTryLock {

    public static void main(String[] args) {
        TryLockService tryLockService = new TryLockService();
        Thread A = new Thread(tryLockService::tryLock);
        A.setName("A");
        A.start();

        Thread B = new Thread(() -> tryLockService.tryLockTime(4L));
        B.setName("B");
        B.start();
    }
}

class TryLockService {
    final ReentrantLock lock = new ReentrantLock();
    public void tryLock() {
        System.out.println(Thread.currentThread().getName() + " start.....");
        if (lock.tryLock()) {
            System.out.println(Thread.currentThread().getName() + " 获取到锁!");
        }
        else {
            System.out.println(Thread.currentThread().getName() + " 未获取到锁!");
        }
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        finally {
            System.out.println(Thread.currentThread().getName() + " 释放锁!!");
            lock.unlock();
        }
    }

    public void tryLockTime(Long time) {
        try {
            System.out.println(Thread.currentThread().getName() + " start.....");
            if (lock.tryLock(time, TimeUnit.SECONDS)) {
                System.out.println(Thread.currentThread().getName() + " 获取到锁!");
            }
            else {
                System.out.println(Thread.currentThread().getName() + " 未获取到锁!");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        finally {
            System.out.println(Thread.currentThread().getName() + " 释放锁!!");
            lock.unlock();
        }
    }
}

公平锁和非公平锁

在java的锁机制中,公平和非公平的参考物是什么,简单的来说,如果一个线程组里,能保证每个线程都能拿到锁,那么这个锁就是公平锁。相反,如果保证不了每个线程都能拿到锁,也就是存在有线程饿死,那么这个锁就是非公平锁。
那如何能保证每个线程都能拿到锁呢,队列FIFO是一个完美的解决方案,也就是先进先出,java的ReenTrantLock也就是用队列实现的公平锁和非公平锁。在公平的锁中,如果有另一个线程持有锁或者有其他线程在等待队列中等待这个锁,那么新发出的请求的线程将被放入到队列中。而非公平锁上,只有当锁被某个线程持有时,新发出请求的线程才会被放入队列中(此时和公平锁是一样的)。所以,它们的差别在于非公平锁会有更多的机会去抢占锁。

public class TestReentrantLock {
    public static void main(String[] args) {
        TestReentrantLockService t1 = new TestReentrantLockService();
        for (int i = 0; i < 50; i++) {
            new Thread(t1).start();
        }
    }
}

class TestReentrantLockService extends Thread {
    final ReentrantLock lock = new ReentrantLock(true); //true公平锁,false非公平锁

    @Override
    public void run() {

        for (int i = 0; i < 50; i++) {
            try {
                lock.lock();
                System.out.println(Thread.currentThread().getName() + "获得锁:" + i);
            } catch (Exception e) {
                e.printStackTrace();
            }
            finally {
                lock.unlock();
            }
        }
    }
}

ReadWriteLock读写锁(写加锁,读不需要)

并发读写操作保证安全的常规解决办法是在读写操作上加入互斥锁,这种情况下并发读写效率会打折扣,因为大多数情况下我们对同一数据读操作的频率会高于写操作,而线程与线程间的并发读操作是不涉及并发安全问题的,所以没有必要给读操作加互斥锁,只要保证读写、写写并发操作上锁是互斥的就行。
ReadWriteLock 维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。只要没有 writer,读取锁可以由多个 reader 线程同时保持。写入锁是独占的,ReadWriteLock 读取操作通常不会改变共享资源,但执行写入操作时,必须独占方式来获取锁。对于读取操作占多数的数据结构。 ReadWriteLock 能提供比独占锁更高的并发性。而对于只读的数据结构,其中包含的不变性可以完全不需要考虑加锁操作。
上面基本都废话,主要核心:
“读-读” 不互斥
“读-写” 互斥
“写-写” 互斥

public class TestReadWriteLock {
    public static void main(String[] args) {
        ReadWriteLockService service = new ReadWriteLockService();
        for (int i = 0; i < 20; i++) {
            char c = (char) ('A' + i);
            new Thread(() -> {
                service.read();
            }, "A").start();
            new Thread(() -> {
                service.write();
            }, "B").start();
        }

    }
}

class ReadWriteLockService {
    private String value = "";

    final ReadWriteLock lock = new ReentrantReadWriteLock();
    final Lock readLock = lock.readLock();
    final Lock writeLock = lock.writeLock();

    public void read() {
        try {
            readLock.lock();
            TimeUnit.SECONDS.sleep(1);
            System.out.println(Thread.currentThread().getName() + " read..." + value);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            readLock.unlock();
        }
    }

    public void write() {
        try {
            writeLock.lock();
            TimeUnit.SECONDS.sleep(1);
            value = System.currentTimeMillis() + "";
            System.out.println(Thread.currentThread().getName() + " write..." + value);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            writeLock.unlock();
        }
    }
}

LockSupport读写锁(写加锁,读不需要)

  1. LockSupport 不需要加锁就可以实现阻塞和唤醒
  2. 如果一个线程处理等待状态,连续调用了两次park方法。线程永远无法被唤醒
  3. unpark 和 park 方法不需要考虑调用先后顺序
public class TestLockSupport {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                try {
                    if (i == 3) {
                        LockSupport.park();
                    }
                    System.out.println(i);
                    TimeUnit.MILLISECONDS.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "A");
        thread.start();
        LockSupport.unpark(thread);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值