Java 实现同步的几种方式

1、Java 实现同步的几种方式
方式一:使用 synchronized 关键字与 Object#wait()/notifyAll()
  • Object#wait():释放当前锁,使该线程进入等待状态(阻塞状态)。
  • Object#notify():在所有等待线程中随机唤醒一个线程,让它获得锁。
  • Object#notifyAll():唤醒所有等待的线程,让它们一起竞争锁,最后其中之一获得锁。
方式二:使用 ReentrantLock(可重入锁)以及 Condition(条件)。
  • Lock#lock():加锁。
  • Lock#unlock():释放锁。
  • Condition#await():释放当前锁,使该线程进入等待状态(阻塞状态)。
  • Condition#signal():在所有等待线程中随机唤醒一个线程,让它获得锁。
  • Condition#signalAll():唤醒所有等待的线程,让它们一起竞争锁,最后其中之一获得锁。
方式三:使用 Semaphore(信号量)
  • new Semaphore(1):互斥的信号量。
  • Semaphore#acquire():获取一个许可,如果没有就等待。
  • Semaphore#release():释放一个许可。
注意:方式可能不仅仅上述几种,这里只是写出来相对应常见的三种方式。
  • java.util.concurrent.* 并发包下的工具类,分别有:
  • (1)ReentrantLock(可重入锁):指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class),不会因为之前已经获取过还没释放而阻塞。
  • (2)Semaphore(信号量):是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。
  • (3)CountDownLatch: 允许一个或多个线程等待其他线程完成操作。
  • (4)CyclicBarrier :字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。
2、Java 死锁
  • 所谓死锁,就是多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
2.1 死锁触发的四大条件?
  • (1)互斥使用:即当资源被一个线程使用(占有)时,别的线程不能使用;
  • (2)不可抢占:即资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放;
  • (3)请求与保持:即当资源请求者在请求其他的资源的同时保持对原有资源的占有;
  • (4)循环等待:即存在一个等待队列:P1 占有 P2 的资源,P2 占有 P1 的资源,这样就形成了一个等待环路。
2.2 如何避免死锁?
  • (1)加锁顺序:即线程按照一定的顺序加锁;
  • (2)加锁时限:即线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁;
  • (3)死锁检测。死锁检测是一个更好的死锁预防机制,它主要是针对那些不可能实现按序加锁并且锁超时也不可行的场景。每当一个线程获得了锁,会在线程和锁相关的数据结构中(map、graph 等等)将其记下。除此之外,每当有线程请求锁,也需要记录在这个数据结构中。当一个线程请求锁失败时,这个线程可以遍历锁的关系图看看是否有死锁发生。
2.3 当检测出死锁后,这些线程该做些什么?
  • 方案一:释放所有锁,回退,并且等待一段随机的时间后重试;
  • 方案二:给这些线程设置优先级,让一个(或几个)线程回退,剩下的线程就像没发生死锁一样继续保持着它们需要的锁。如果赋予这些线程的优先级是固定不变的,同一批线程总是会拥有更高的优先级。为避免这个问题,可以在死锁发生的时候设置随机的优先级。
2.4 死锁示例:
/**
 * 题目:
 * 写一个死锁程序
 * <p>
 * 解题思路: synchronized 或 Lock
 */
public class TH_01_死锁 {

    private final static Object lockA = new Object();
    private final static Object lockB = new Object();

    public static void main(String[] args) {
        new ThreadA().start();
        new ThreadB().start();
    }

    static class ThreadA extends Thread {
        @Override
        public void run() {
            try {
                System.out.println("ThreadA 开始执行");
                synchronized (lockA) {
                    System.out.println("ThreadA 锁住了 lockA");
                    Thread.sleep(1000);// 此处等待是给 ThreadB 能锁住机会
                    synchronized (lockB) {
                        System.out.println("ThreadA 锁住了 lockB");
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    static class ThreadB extends Thread {
        @Override
        public void run() {
            System.out.println("ThreadB 开始执行");
            try {
                synchronized (lockB) {
                    System.out.println("ThreadB 锁住了 lockB");
                    Thread.sleep(1000);// 此处等待是给 ThreadB 能锁住机会
                    synchronized (lockA) {
                        System.out.println("ThreadB 锁住了 lockA");
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

========== 运行结果 ==========
ThreadA 开始执行
ThreadA 锁住了 lockA
ThreadB 开始执行
ThreadB 锁住了 lockB
2.5 死锁解决方案示例
/**
 * 题目:
 * 写一个死锁解决方案程序
 * <p>
 * 解题思路: synchronized 或 Lock
 */
public class TH_01_死锁解决方案 {

    private final static Object lockA = new Object();
    private final static Object lockB = new Object();

    public static void main(String[] args) {
        new ThreadA().start();
        new ThreadB().start();
    }

    static class ThreadA extends Thread {
        @Override
        public void run() {
            try {
                System.out.println("ThreadA 开始执行");
                synchronized (lockA) {
                    System.out.println("ThreadA 锁住了 lockA");
                    Thread.sleep(1000);// 此处等待是给 ThreadB 能锁住机会
                    synchronized (lockB) {
                        System.out.println("ThreadA 锁住了 lockB");
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    static class ThreadB extends Thread {
        @Override
        public void run() {
            System.out.println("ThreadB 开始执行");
            try {
                synchronized (lockA) {
                    System.out.println("ThreadB 锁住了 lockA");
                    Thread.sleep(1000);// 此处等待是给 ThreadB 能锁住机会
                    synchronized (lockB) {
                        System.out.println("ThreadB 锁住了 lockB");
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

========== 运行结果 ==========
ThreadA 开始执行
ThreadA 锁住了 lockA
ThreadB 开始执行
ThreadA 锁住了 lockB
ThreadB 锁住了 lockA
ThreadB 锁住了 lockB
3、AB 两条线程交替打印
/**
 * 题目:
 * AB两个线程交替打印0-100的数字
 * <p>
 * 解题思路:
 * 基于 synchronized 与 wait()/notifyAll()
 * 基于 ReentractLock(可重入锁) 和 Condition(条件)
 * 基于 Semaphore(信号量)
 */
public class TH_02_AB两条线程交替打印 {

    private static int count = 0;
    private final static Object object = new Object();

    public static void main(String[] args) {
        new ThreadA().start();
        new ThreadB().start();
    }

    static class ThreadA extends Thread {
        @Override
        public void run() {
            while (true) {
                try {
                    synchronized (object) {
                        if (count < 100) {
                            if ((count & 1) == 1) {
                                object.wait();
                            }
                            System.out.println("ThreadA:" + count);
                            count++;
                            object.notifyAll();
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    static class ThreadB extends Thread {
        @Override
        public void run() {
            while (true) {
                try {
                    synchronized (object) {
                        if (count < 100) {
                            if ((count & 1) == 0) {
                                object.wait();
                            }
                            System.out.println("ThreadB:" + count);
                            count++;
                            object.notifyAll();
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
4、生产消费者
  • 基于 synchronized 与 wait()/notifyAll()
/**
 * 题目:
 * 写一个程序实现生产者消费者
 * <p>
 * 解题思路:
 * 基于 synchronized 关键字与 Object#wait()/notifyAll()
 * Object#wait():释放当前锁,使该线程进入等待状态(阻塞状态)
 * Object#notify():在所有等待线程中随机唤醒一个线程,让它获得锁
 * Object#notifyAll():唤醒所有等待的线程,让它们一起竞争锁,最后其中之一获得锁
 * ThreadA 为生产者,ThreadB 为消费者
 */
public class TH_03_1_生产消费者模式synchronized {

    private static int count = 0;
    private final static int MAX_COUNT = 5;
    private final static Object object = new Object();

    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            new ThreadA().start();
            new ThreadB().start();
        }
    }

    static class ThreadA extends Thread {
        @Override
        public void run() {
            while (true) {
                try {
                    synchronized (object) {
                        while (count == MAX_COUNT) {
                            System.out.println("数据已满");
                            object.wait();
                        }
                        count++;
                        System.out.println("生产者产生了一个数据,当前总数:" + count);
                        object.notifyAll();
                    }
                    Thread.sleep(new Random().nextInt(1000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    static class ThreadB extends Thread {
        @Override
        public void run() {
            while (true) {
                try {
                    synchronized (object) {
                        while (count <= 0) {
                            System.out.println("数据为空");
                            object.wait();
                        }
                        count--;
                        System.out.println("消费者消费了一个数据,当前总数:" + count);
                        object.notifyAll();
                    }
                    Thread.sleep(new Random().nextInt(1000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
  • 基于 ReentractLock(可重入锁) 和 Condition(条件)
/**
 * 题目:
 * 写一个程序实现生产者消费者
 * <p>
 * 解题思路:
 * 基于 ReentractLock(可重入锁) 和 Condition(条件)
 * Lock#lock():加锁
 * Lock#unlock():释放锁
 * Condition#await():释放当前锁,使该线程进入等待状态(阻塞状态)
 * Condition#signal():在所有等待线程中随机唤醒一个线程,让它获得锁
 * Condition#signalAll():唤醒所有等待的线程,让它们一起竞争锁,最后其中之一获得锁
 * ThreadA 为生产者,ThreadB 为消费者
 */
public class TH_03_2_生产消费者模式ReentractLock {

    private static int count = 0;
    private final static int MAX_COUNT = 5;
    private final static Lock lock = new ReentrantLock();
    private final static Condition fullCondition = lock.newCondition();
    private final static Condition emptyCondition = lock.newCondition();

    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            new ThreadA().start();
            new ThreadB().start();
        }
    }

    static class ThreadA extends Thread {
        @Override
        public void run() {
            while (true) {
                try {
                    lock.lock();
                    while (count == MAX_COUNT) {
                        System.out.println("数据已满");
                        fullCondition.await();
                    }
                    count++;
                    System.out.println("生产者产生了一个数据,当前总数:" + count);
                    // 唤醒所有消费者
                    emptyCondition.signalAll();
                    Thread.sleep(new Random().nextInt(1000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // 最后要注意手动释放锁
                    lock.unlock();
                }
            }
        }
    }

    static class ThreadB extends Thread {
        @Override
        public void run() {
            while (true) {
                try {
                    lock.lock();
                    while (count < 1) {
                        System.out.println("数据为空");
                        emptyCondition.await();
                    }
                    count--;
                    System.out.println("消费者消费了一个数据,当前总数:" + count);
                    // 唤醒所有生产者
                    fullCondition.signalAll();
                    Thread.sleep(new Random().nextInt(1000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // 最后要注意手动释放锁
                    lock.unlock();
                }
            }
        }
    }
}
  • 基于 Semaphore(信号量)
/**
 * 题目:
 * 写一个程序实现生产者消费者
 * <p>
 * 解题思路:
 * 基于 Semaphore(信号量)
 * new Semaphore(1):互斥的信号量
 * Semaphore#acquire():获取一个许可,如果没有就等待
 * Semaphore#release():释放一个许可
 * ThreadA 为生产者,ThreadB 为消费者
 */
public class TH_03_3_生产消费者模式Semaphore {

    private static int count = 0;
    private static final int MAX_COUNT = 5;
    // 实现数据消耗互斥的信号量
    private static final Semaphore semaphore = new Semaphore(1);
    // 控制生产者的信号量
    private static final Semaphore fullSemaphore = new Semaphore(MAX_COUNT);
    // 控制消费者的信号量
    private static final Semaphore emptySemaphore = new Semaphore(0);

    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            new ThreadA().start();
            new ThreadB().start();
        }
    }

    static class ThreadA extends Thread {
        @Override
        public void run() {
            while (true) {
                try {
                    // 请求一个生产者信号量,如果当前信号量为 MAX_COUNT 时,则阻塞
                    fullSemaphore.acquire();
                    semaphore.acquire();
                    count++;
                    System.out.println("生产者产生了一个数据,当前总数:" + count);
                    semaphore.release();
                    // 设置消费者可以消费的信号量个数 +1
                    emptySemaphore.release();
                    Thread.sleep(new Random().nextInt(1000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    static class ThreadB extends Thread {
        @Override
        public void run() {
            while (true) {
                try {
                    // 请求一个消费者信号量,如果当前信号量为 0 时,则阻塞
                    emptySemaphore.acquire();
                    semaphore.acquire();
                    count--;
                    System.out.println("消费者消费了一个数据,当前总数:" + count);
                    semaphore.release();
                    fullSemaphore.release();
                    Thread.sleep(new Random().nextInt(1000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
5、让多线程按照自己指定的顺序执行
  • 基于 Thread.join()
/**
 * 题目:
 * 让多线程按照自己指定的顺序执行
 * <p>
 * 解题思路:
 * 基于 Thread.join():是 Theard 的方法,作用是调用线程需等待该 join() 线程执行完成后,才能继续用下运行。
 * 基于 synchronized 与 wait()/notifyAll()
 * 基于 ReentractLock(可重入锁) 和 Condition(条件)
 * 基于 Semaphore(信号量)
 */
public class TH_04_1_多线程按指定顺序执行join {

    public static void main(String[] args) {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程1执行了...");
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    thread1.join();
                    System.out.println("线程2执行了...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    thread2.join();
                    System.out.println("线程3执行了...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        // 线程随机打乱开启都能保证先1后2再3
        thread2.start();
        thread3.start();
        thread1.start();
    }
}
  • 基于 synchronized 与 wait()/notifyAll()
/**
 * 题目:
 * 让多线程按照自己指定的顺序执行
 * <p>
 * 解题思路:
 * 基于 Thread.join():是 Theard 的方法,作用是调用线程需等待该 join() 线程执行完成后,才能继续用下运行。
 * 基于 synchronized 与 wait()/notifyAll()
 * 基于 ReentractLock(可重入锁) 和 Condition(条件)
 * 基于 Semaphore(信号量)
 */
public class TH_04_2_多线程按指定顺序执行synchronized {

    private static Object object1 = new Object();
    private static Object object2 = new Object();
    private static Boolean threadRun1 = false;
    private static Boolean threadRun2 = false;

    public static void main(String[] args) {
        final Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (object1) {
                    System.out.println("线程1执行了...");
                    threadRun1 = true;
                    object1.notify();
                }
            }
        });
        final Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (object1) {
                    try {
                        if (!threadRun1) object1.wait();
                        synchronized (object2) {
                            System.out.println("线程2执行了...");
                            object2.notify();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (object2) {
                    try {
                        if (!threadRun2) object2.wait();
                        System.out.println("线程3执行了...");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        // 线程随机打乱开启都能保证先1后2再3
        thread3.start();
        thread2.start();
        thread1.start();
    }
}
  • 基于 ReentractLock(可重入锁) 和 Condition(条件)
/**
 * 题目:
 * 让多线程按照自己指定的顺序执行
 * <p>
 * 解题思路:
 * 基于 Thread.join():是 Theard 的方法,作用是调用线程需等待该 join() 线程执行完成后,才能继续用下运行。
 * 基于 synchronized 与 wait()/notifyAll()
 * 基于 ReentractLock(可重入锁) 和 Condition(条件)
 * 基于 Semaphore(信号量)
 */
public class TH_04_3_多线程按指定顺序执行ReentrantLock {

    private static Lock lock = new ReentrantLock();
    private static Condition condition1 = lock.newCondition();
    private static Condition condition2 = lock.newCondition();
    private static Boolean threadRun1 = false;
    private static Boolean threadRun2 = false;

    public static void main(String[] args) throws InterruptedException {
        final Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                System.out.println("线程1执行了...");
                threadRun1 = true;
                condition1.signalAll();
                lock.unlock();
            }
        });
        final Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                try {
                    if (!threadRun1) condition1.await();
                    System.out.println("线程2执行了...");
                    threadRun2 = true;
                    condition2.signalAll();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                lock.unlock();
            }
        });
        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                try {
                    if (!threadRun2) condition2.await();
                    System.out.println("线程3执行了...");
                    lock.unlock();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        // 线程随机打乱开启都能保证先1后2再3
        thread3.start();
        thread2.start();
        thread1.start();
    }
}
  • 基于 Semaphore(信号量)
/**
 * 题目:
 * 让多线程按照自己指定的顺序执行
 * <p>
 * 解题思路:
 * 基于 Thread.join():是 Theard 的方法,作用是调用线程需等待该 join() 线程执行完成后,才能继续用下运行。
 * 基于 synchronized 与 wait()/notifyAll()
 * 基于 ReentractLock(可重入锁) 和 Condition(条件)
 * 基于 Semaphore(信号量)
 */
public class TH_04_4_多线程按指定顺序执行Semaphore {

    private static Semaphore semaphore1 = new Semaphore(1,true);
    private static Semaphore semaphore2 = new Semaphore(0);
    private static Semaphore semaphore3 = new Semaphore(0);

    public static void main(String[] args) {
        final Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    semaphore1.acquire();
                    System.out.println("线程1执行了...");
                    semaphore2.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        final Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    semaphore2.acquire();
                    System.out.println("线程2执行了...");
                    semaphore3.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        final Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    semaphore3.acquire();
                    System.out.println("线程3执行了...");
                    semaphore1.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        // 线程随机打乱开启都能保证先1后2再3
        thread3.start();
        thread2.start();
        thread1.start();
    }
}
6、参考文献
  • https://developer.aliyun.com/article/52846#slide-4
  • https://www.liaoxuefeng.com/wiki/1252599548343744/1306580960149538
  • https://fangjian0423.github.io/2016/04/18/java-synchronize-way/
  • https://zhuanlan.zhihu.com/p/80787379
  • 10
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值