Java多线程之间几种通信方式

前言

  • 本篇文章主要介绍Java基础多线程之间通信的一些玩法,并结合一些面试题来实践的

1.基于synchronized和java类锁的wait()和notify()

这是一道面试题,让字母和数字交叉打印(第一种)

/**
 * @author wanghp
 * @version 1.0
 * @date 2020/6/25 23:03
 * Thread.sleep与Object.wait区别
 * Thread.sleep需要指定休眠时间,时间一到可继续运行;和锁机制无关,没有加锁也不用释放锁
 * Object.wait需要在synchronized中调用,否则报IllegalMonitorStateException错误。wait方法会释放锁,需要调用相同锁对象Object.notify来唤醒线程
 */

public class JavaNotifyWait {
    static char[] charsNumber = "1234567".toCharArray();
    static char[] charsC = "ABCDEFG".toCharArray();
    static Thread t1 = null, t2 = null;

    static Object obj = new JavaNotifyWait();


    public static void charsNumber() {
        try {
            synchronized (obj) {
                for (char c : charsNumber) {
                    obj.notify();
                    System.out.println("A----------notify");
                    System.out.println(c);
                    obj.wait();
                    System.out.println("A----------wait");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
        }
    }

    public static void charsC() {
        try {
            synchronized (obj) {
                for (char c : charsC) {
                    obj.notify();
                    System.out.println("B----------notify");
                    System.out.println(c);
                    obj.wait();
                    System.out.println("B----------wait");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
        }
    }

    public static void main(String[] args) {
        t1 = new Thread(() -> charsNumber(), "t1");
        t2 = new Thread(() -> charsC(), "t2");
        t1.start();
        t2.start();
    }
}

2.基于LockSupport的park()和unpark()

面试题:让字母和数字交叉打印(第二种)

/**
 * @author wanghp
 * @version 1.0
 * @date 2020/6/25 23:03
 */

public class LockSupportDemo {
    static char[] charsNumber = "1234567".toCharArray();
    static char[] charsC = "ABCDEFG".toCharArray();
    static Thread t1 = null, t2 = null;

    //t1 exec method
    public static void charsNumber() {
        for (char c : charsNumber) {
            System.out.println(c);
            //唤醒当前打印字母的线程t2
            LockSupport.unpark(t2);
            //阻塞当前打印数字的线程t1
            LockSupport.park(t1);
        }
    }

    //t2 exec method
    public static void charsC() {
        for (char c : charsC) {
            //阻塞当前打印字母的线程t2
            LockSupport.park(t2);
            System.out.println(c);
            //唤醒打印数字的线程t1
            LockSupport.unpark(t1);
        }
    }

    public static void main(String[] args) {
        //打印数字的线程
        t1 = new Thread(() -> charsNumber(), "t1");
        //打印字母的线程
        t2 = new Thread(() -> charsC(), "t2");
        t1.start();
        t2.start();
    }
}

3.基于Lock和Condition的await()和signal()

面试题:手写一个阻塞式队列

/**
 * @author wanghp
 * @version 1.0
 * @date 2020/7/28 10:23
 * 结论:就是用if判断的话,唤醒后线程会从wait之后的代码开始运行,但是不会重新判断if条件,
 * 直接继续运行if代码块之后的代码,而如果使用while的话,也会从wait之后的代码运行,但是唤醒后会重新判断循环条件,
 * 如果不成立再执行while代码块之后的代码块,成立的话继续wait。
 */

public class BlockedList {
    //传入true表示使用公平锁,false为非公平锁,默认为非公平锁
    static Lock lock = new ReentrantLock(false);
    //设置一个添加一个移除
    static Condition add = lock.newCondition();
    static Condition remove = lock.newCondition();

    //定义一个集合,存储数据
    static ArrayList<Integer> list = new ArrayList<>();

    public static void add(int val) throws InterruptedException {
        try {
            lock.lock();
            //  System.out.println("进入add方法,当前线程为:" + Thread.currentThread().getName());
            //为什么用while请看上面注释,当调用await方法,当前线程释放了锁,也就是说进来的线程都将阻塞在这里,
            //这块他就会进入到阻塞队列,一旦被唤醒,线程继续执行,这里使用非公平锁,可以从打印信息看出线程不存在先后顺序之分
            while (list.size() >= 2) {
                System.out.println("等待添加..." + Thread.currentThread().getName());
                add.await();
                System.out.println("开始添加" + Thread.currentThread().getName() + "  size:" + list.size());
            }
            list.add(val);
            System.out.println("add data:" + val);
        } finally {
            //节点会从condition队列移动到AQS等待队列,则进入正常锁的获取流程
            remove.signal();
            lock.unlock();
        }
    }

    public static void remove() throws InterruptedException {
        try {
            lock.lock();
            if (list.size() == 2) {
                //每2个满了拿前一个
                System.out.println("remove data:" + list.get(0));
                list.remove(0);
                System.out.println("移除完毕");
                remove.await();
                System.out.println("等待移除...");
            }
        } finally {
            //节点会从condition队列移动到AQS等待队列,则进入正常锁的获取流程
            add.signal();
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 1; i <= 10; i++) {
            int finalI = i;
            new Thread(() -> {
                try {
                    add(finalI);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, "t" + finalI).start();
        }
        //本地测试,避免电脑hold不住
        for (int i = 1; i <= 1000; i++) {
            //进行查询移除
            new Thread(() -> {
                try {
                    remove();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }

    }
}

4.多线程之间join()玩法

面试题:让线程顺序执行

/**
 * @author wanghp
 * @version 1.0
 * @date 2020/6/18 22:41
 */

public class JoinDemo {

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -> {
            System.out.println("t1");
        });
        Thread t2 = new Thread(() -> {
            System.out.println("t2");
        });
        Thread t3 = new Thread(() -> {
            System.out.println("t3");
        });
        t1.start();
        t1.join();  //他会阻塞主线程,什么时候释放先不管
        //等待释放,具体什么时候释放看join()方法
        //获取到t1线程的执行结果,应用场景,我们假如想要看到t1线程的执行结果就可以这样使用,或者建立一个happens-before规则,
        //TODO  或者让主线程可见,用join
        t2.start();
        t2.join();  //建立一个 happens-before原则
        t3.start();
    }
}

最后总结一下,一般Lock是和Conditionawait()signal()搭配,synchronized和类锁的wait()notify()使用的。
Q:具体为什么?
1.每一个对象都有一个与之对应的监视器
2.每一个监视器里面都有一个该对象的锁和一个等待队列和一个同步队列

你可以简单理解为synchronized锁对象,进来的线程获得了该对象锁,同时拥有了该对象锁的监视器,有了监视器就可以调用wait()、notify()以及notifyAll()方法,然后其他线程竞争的锁须和当前线程的对象锁是一个,竞争失败的线程会放到阻塞队列,这时候当前线程释放锁之后,调用唤醒方法会将处于阻塞队列的线程放到同步队列,这里注意notify()和notifyAll(),前者只取一个,后者为全部,具体先后看CPU抢占率的。提到这个先后,Lock其实有公平锁和非公平锁,他们其实很像,主要一点就是公平锁是FIFO这样的,而非公平锁可能根据放入的先后没关系,还得根据竞争cpu来进行抢占锁的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

遇见更优秀的自己

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值