zoukankan      html  css  js  c++  java
  • Java多线程 synchronized与ReentrantLock用法

    使用 synchronized修饰,表示该方法是加锁的方法。使用相同this锁的方法,在任意时刻只有一个方法会被执行,在多线程中是竞争关系。除此之外多线程还存在依赖关系。例如,一个线程须等待另一个线程返回结果后,才能继续执行。Java中提供了相应的机制。

    1、synchronized、wait、notify

    考虑一个实际的流水线作业场景,一个线程负责生产产品,另一个线程负责在流水线上装配。由于生产时间不确定,为了不错过传送带上的产品,装配线程需要不断检查当前传送带上有无产品。为了简化逻辑,这里只有一个线程负责生产,一个线程负责装配。

    import java.util.Arrays;
    import java.util.LinkedList;
    import java.util.Queue;
    
    public class ThreadDispatch {
        public static void main(String[] args) {
            var pack = new PackQueue();
            // 负责每隔1秒装入一次数据
            Thread t1 = new Thread() {
                @Override
                public void run() {
                    for (int i = 0; i < 10; i++) {
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        pack.addPack(Integer.toString(i));
                    }
                }
            };
    
            // 每隔0.6秒钟取出数据
            Thread t2 = new Thread() {
                @Override
                public void run() {
                    for (int i = 0; i < 20; i++) {
                        try {
                            Thread.sleep(600);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        String s = pack.getPack();
                        System.out.println(s);
                    }
                }
            };
            t1.start();
            t2.start();
        }
    }
    
    class PackQueue {
        private Queue<String> q = new LinkedList<>();
    
        public void addPack(String s) {
            this.q.add(s);
        }
    
        public String getPack() {
            if (this.q.isEmpty()) {
                return "empty";
            }
            return this.q.remove();
        }
    
    }
    //empty
    //0
    //empty
    //1
    //2
    //empty
    //3
    //empty
    //4
    //5
    //empty
    //6
    //empty
    //7
    //8
    //empty
    //9
    //empty
    //empty
    //empty

    以上逻辑是使用循环实现的。为了不错过传送带上的商品,装配线程t2需不断定时检查。这对设置检查的频率提出了要求。频率稍慢会错过产品,频率过快会浪费性能。最好的方式是,线程1生产好产品后,通知线程2装配,这样解决了“来不及”和“速度过快”的问题。

    public class ThreadDispatch {
        public static void main(String[] args) {
            var pack = new PackQueue();
            // 负责每隔1秒装入一次数据
            Thread t1 = new Thread() {
                @Override
                public void run() {
                    for (int i = 0; i < 10; i++) {
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        pack.addPack(Integer.toString(i));
                    }
                }
            };
    
            // 检查有无数据,没有就等待
            Thread t2 = new Thread() {
                @Override
                public void run() {
                    try {
                        while(true) {
                            String s = pack.getPack();
                            System.out.println(s);
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    
                }
            };
            t1.start();
            t2.start();
        }
    }
    
    class PackQueue {
        private Queue<String> q = new LinkedList<>();
    
        public synchronized void addPack(String s) {
            this.q.add(s);
            this.notify();
        }
    
        public synchronized String getPack() throws InterruptedException {
            if (this.q.isEmpty()) {
            // return "empty";
                this.wait();
            }
            return this.q.remove();
        }
    }
    // 0
    // 1
    // 2
    // 3
    // 4
    // 5
    // 6
    // 7
    // 8
    // 9

    优化之后,装配线程t2 不再“无节制”检查工作区,而是等待t1线程通知后工作。需要注意的是,这里只有一个装配线程t2,如果有多个线程,需要调用this.notifyAll取代this.notify,表示唤醒所有正在等待this锁的线程。唤醒多个线程,最终也只会有一个线程获取this锁,其余线程继续等待。另外,t2 线程在 t1 线程停止生产后永远也醒不过来了。考虑为 t2 线程指定一个wait超时时间,超时后会自动醒来。

    2、 ReentrantLock、Condition

    java5中引入了高级的处理并发的java.util.concurrent包,相比较synchronized机制,提供了尝试获取锁、超时等待等更多功能;不同于synchronized 在Java语言层面实现自动释放锁而不必考虑异常,ReentrantLock由Java代码实现,因此需要正确捕获异常和释放锁。使用 ReentrantLock 重写示例:

    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    class PackQueue {
        private Queue<String> q = new LinkedList<>();
        private final Lock lock = new ReentrantLock();
        private final Condition condition = lock.newCondition();
    
        public void addPack(String s) {
            lock.lock();
            try {
                this.q.add(s);
                condition.signalAll();
            } finally {
                lock.unlock();
            }
    
        }
    
        public String getPack() throws InterruptedException {
            lock.lock();
            try {
                if (this.q.isEmpty()) {
                    condition.await(2, TimeUnit.SECONDS);
                }
                return this.q.remove();
            } finally {
                lock.unlock();
            }
    
        }
    }

    值得注意的是,判断队列为空后,调用 condition.await(2, TimeUnit.SECONDS); 表示线程会自动超时醒来。由于此时队列为空,调用remove方法会报错;不过线程可以自动唤醒了。总结一下:

     
    synchronized
    ReentrantLock
    加锁 通常使用 synchronized 修饰方法,表示使用this实例加锁
    private final Lock lock = new ReentrantLock();
    lock.lock()
    释放锁 自动释放
    lock.unlock();
    线程等待
    this.wait();
    private final Condition condition = lock.newCondition();
    condition.await();
    线程唤醒
    this.notify();
    this.notifyAll();
    condition. signal();
    condition.signalAll();
    锁类型 可重入锁 可重入锁
    尝试获取锁 不支持
    lock.tryLock(1, TimeUnit.SECONDS)

    超时自动唤醒

    不支持
    condition.await(2, TimeUnit.SECONDS);
     
  • 相关阅读:
    C C++ 数字后面加 LL是什么意思
    stdio.h cstdio的区别
    printf scanf cin cout的区别与特征
    PAT Basic 1020
    PAT Basic 1012
    PAT Basic 1046
    PAT Basic 1026
    PAT Basic 1011
    PAT Basic 1016
    PAT Basic 1009
  • 原文地址:https://www.cnblogs.com/engeng/p/15555323.html
Copyright © 2011-2022 走看看