多线程面试题中经常会被问答,使用多线程交替输出两个数组,现在跟大家分享一下我所学习到的使用多线程的多种方式实现该需求。

如果有什么问题欢迎随时指出,谢谢各位

方式一、LockSupport

LockSupport是concurrent包中的工具类,提供了park(), unpark()两个接口能够对线程进行阻塞和唤醒

代码如下:

public class LockSupport {
    //需要交替打印的数据
    static char[] aI = "1234567".toCharArray();
    static char[] aC = "ABCDEFG".toCharArray();
    
    static Thread t1 = null, t2 = null;

    public static void main(String[] args) {
        //创建线程t1
        t1 = new Thread(()->{
            for (char c : aI) {
                System.out.print(c);
                //唤醒等待的线程t2
                LockSupport.unpark(t2);
                //阻塞当前线程t1
                LockSupport.park();
            }
        }, "t1");

        //创建线程t2
        t2 = new Thread(()->{
            for (char c : aC) {
                /*
                * 需要注意的是,输出语句不能写在阻塞之前,因为在执行输出语句时
                * 另一个线程还没有进入阻塞状态,这时两个线程
                * 两个线程在输出时会有争抢cpu的情况导致输出的数据顺序可能会打乱
                * 1AB2C3D4E5F6G7
                */
                //System.out.print(c);
                //t2线程一进来就阻塞等待唤醒,确保线程t1能够先执行
                LockSupport.park();
                System.out.print(c);
                //唤醒t1线程
                LockSupport.unpark(t1);
            }
        }, "t2");

        t1.start();
        t2.start();

        //输出结果为: 1A2B3C4D5E6F7G
    }
}

方式二、CAS

使用无锁操作实现交替输出

代码如下:

public class Cas {
    //使用枚举,让代码更加规范,可以直接换成别的变量直接代替判断
    enum ReadyToRun {t1, t2}

    static char[] aI = "1234567".toCharArray();
    static char[] aC = "ABCDEFG".toCharArray();

    //定义初始值 t1
    //这里需要添加volatile,确保线程之间的可见性
    static volatile ReadyToRun ready = ReadyToRun.t1;

    public static void main(String[] args) {
        //创建线程t1
        new Thread(()->{
            for (char c : aI) {
                //一直循环判断ready,直到别的线程修改ready的值,才进行下一步操作
                while (ready != ReadyToRun.t1) {}
                System.out.print(c);
                //修改程t2
                ready = ReadyToRun.t2;
            }
        }, "t1").start();

        创建线程t2
        new Thread(()->{
            for (char c : aC) {
                while (ready != ReadyToRun.t2) {}
                System.out.print(c);
                ready = ReadyToRun.t1;
            }
        }, "t2").start();
    }

    //输出结果: 1A2B3C4D5E6F7G
}

方式三、BlockingQueue

这里实现逻辑是使用了 BlockingQueue接口提供的 put(),take()方法和他的实现类ArrayBlockingQueue

put():将指定的元素插入到此队列中,在必要时等待可用空间。

当对列中满了时线程会自动进入等待状态。

take():检索并删除此队列的头部,在必要时等待,直到元素可用为止。

会从队列中获取头部的元素并删除,当队列插满时就会自动进入阻塞状态。

ArrayBlockingQueue:调用构造方法创建一个固定容器

代码如下:

public class ArrayBlockingQueue {
    static char[] aI = "1234567".toCharArray();
    static char[] aC = "ABCDEFG".toCharArray();

    //设置线程容器大小为1
    static BlockingQueue q1 = new ArrayBlockingQueue(1);
    static BlockingQueue q2 = new ArrayBlockingQueue(1);

    public static void main(String[] args) {
        new Thread(()->{
            for (char c : aI) {
                System.out.print(c);
                try {
                    //给t1容器添加一个元素,如果容器满了就会阻塞
                    q1.put("OK");
                    //获取t2容器中的头元素
                    q2.take();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        new Thread(()->{
            for (char c : aC) {
                try {
                    //获取q1容器中的头元素,如果为空说明t1线程还没有执行完成,就会阻塞
                    q1.take();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.print(c);

                try {
                    q2.put("OK");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

方式四、AtomicInterte

实现逻辑与CAS相似,区别在于AtomicInteger类是系统底层保护的int类型,通过对int类型的数据进行封装,提供执行方法的控制进行值的原子操作。

public class AtomicInterte {
    static char[] aI = "1234567".toCharArray();
    static char[] aC = "ABCDEFG".toCharArray();

    //设置初始值
   static AtomicInteger atomicInteger = new AtomicInteger(1);

    public static void main(String[] args) {
        new Thread(()->{
            for (char c : aI) {
                while (atomicInteger.get() != 1) {}
                System.out.print(c);
                atomicInteger.set(2);
            }
        }).start();

        new Thread(()->{
            for (char c : aC) {
                while (atomicInteger.get() != 2) {}
                System.out.print(c);
                atomicInteger.set(1);
            }
        }).start();
    }
}

方式五、Synchronized

使用 Synchronized中的notify(), wait()方法实现

代码如下:

public class Synchronized_notify_wait {
    static char[] aI = "1234567".toCharArray();
    static char[] aC = "ABCDEFG".toCharArray();

    //统一锁对象
    static final Object o = new Object();

    public static void main(String[] args) {
        new Thread(()->{
            synchronized (o) {
                for (char c : aI) {
                    System.out.print(c);
                    
                    try {
                        o.notify();    
                        o.wait();      //让出锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    o.notify(); //必须,否则无法停止程序
                }
            }
        }).start();

        new Thread(()->{
            synchronized (o) {
                for (char c : aC) {
                    System.out.print(c);
                    try {
                        o.notify();
                        o.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                o.notify(); //必须,否则无法停止程序
            }
        }).start();
    }
}

方式六、Lock_Condition

Condition 对象与 wait() 方法和 notify() 方法的作用是大致相同的。

wait() 方法和 notify() 方法是与 synchronized 关键字合作使用的,而 Condition 是与重入锁相关联的。

代码如下:

public class Lock_Condition {
    static char[] aI = "1234567".toCharArray();
    static char[] aC = "ABCDEFG".toCharArray();

    //可重入锁
    static Lock lock = new ReentrantLock();
    //通过lock生成一个condtion与之绑定
    static Condition condition = lock.newCondition();

    public static void main(String[] args) {
        new Thread(()->{
            try {
                lock.lock();
                for (char c : aI) {
                    System.out.print(c);
                    condition.signal();
                    condition.await();
                }
                condition.signal();
            }catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }

        }).start();

        new Thread(()->{
            try {
                lock.lock();
                for (char c : aC) {
                    System.out.print(c);
                    condition.signal();
                    condition.await();
                }
                condition.signal();
            }catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }).start();
    }
}