[Android多线程-2] 通过实际操作来学习多线程

47 阅读7分钟

多线程交替执行

  • 方案1 使用 Condition + ReentrantLock

    Condition 是个接口,内部定义了方法await和signal,主要用于代替wait和notify方法。其实现对象为 ConditionObject,主要使用在AQS中,用于在AQS中利用Condition的特性来阻塞队列的读写。同时在 ReentrantLock 中,通过newCondition 方法来获取其在ReentrantLock中的Condition对象实例。

    获取的对象实例需要在ReentrantLock的lock和unlock之间使用。

        final ReentrantLock lock = new ReentrantLock();
        final Condition condition1 = lock.newCondition();
        final Condition condition2 = lock.newCondition();
        final Condition condition3 = lock.newCondition();

        final int[] signal = new int[1];

        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                lock.lock();
                if (signal[0] == 0) {
                    System.out.println("> 1 < " + i);
                    signal[0] = 2;
                    condition2.signal();
                }
                try {
                    condition1.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                lock.unlock();
            }
        }).start();
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                lock.lock();
                if (signal[0] == 2) {
                    System.out.println("> 2 < " + i);
                    signal[0] = 3;
                    condition3.signal();
                }
                try {
                    condition2.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                lock.unlock();
            }
        }).start();
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                lock.lock();
                if (signal[0] == 3) {
                    System.out.println("> 3 < " + i);
                    signal[0] = 0;
                    condition1.signal();
                }
                try {
                    condition3.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                lock.unlock();
            }
        }).start();

        Thread.sleep(1000);

运行结果

> 1 < 0
> 2 < 0
> 3 < 0
> 1 < 1
> 2 < 1
> 3 < 1
> 1 < 2
> 2 < 2
> 3 < 2
> 1 < 3
> 2 < 3
> 3 < 3
> 1 < 4
> 2 < 4
> 3 < 4
  • 方案2 使用 synchronized + wait + notify

    使用 synchronized,表示同步代码块,其实含义和ReentrantLock基本作用一样,只是在一些方面上有些区别,但是配合wait使用时候,要求wait的对象必须是 synchronized 锁住的对象,所以这就要求我们无法在一个synchronized代码块中使用多个对象的wait,而只能使用其锁对象的wait和notify,否则会抛出异常 IllegalMonitorStateException。

        final Object obj = new Object();
        final int[] signal = new int[1];

        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                synchronized (obj) {
                    if (signal[0] == 0) {
                        System.out.println("> 1 < " + i);
                        signal[0] = 1;
                        obj.notifyAll();
                    }
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                synchronized (obj) {
                    if (signal[0] == 1) {
                        System.out.println("> 2 < " + i);
                        signal[0] = 2;
                        obj.notifyAll();
                    }
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                synchronized (obj) {
                    if (signal[0] == 2) {
                        System.out.println("> 3 < " + i);
                        signal[0] = 0;
                        obj.notifyAll();
                    }
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        Thread.sleep(1000);

运行结果

> 1 < 0
> 2 < 0
> 3 < 1
> 1 < 2
> 2 < 2
> 3 < 3
> 1 < 4
> 2 < 4

多线程顺序运行

  • 方案1 join

    使用join的地方,当前线程会等待join的线程执行完毕后,再继续执行当前线程。join方法是线程内部的特有方法,实现原理其实是Thread内部定义了个Object对象作为锁lock。

    join方法为Synchronized修饰,配合wait方法暂停当前线程的运行。等待run方法执行完毕后,才会释放该锁。

        final Thread t1 = new Thread(() -> System.out.println("t1 start"));
        final Thread t2 = new Thread(() -> {
            System.out.println("i am t2 start");
            try {
                t1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("i am t2 end");
        });
        final Thread t3 = new Thread(() -> {
            System.out.println("i am t3 start");
            try {
                t2.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("i am t3 end");
        });

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

        Thread.sleep(2000);

运行结果

i am t2 start
t1 start
i am t3 start
i am t2 end
i am t3 end
  • 方案2 CountDownLatch

    该工具类专门为线程而生,实例化时候可以传入一个int值,表示想要阻拦几个异步动作,对于想要等待的位置调用方法await进行等待,其他地方调用countDown来消耗阻拦,当countDown方法调用次数等于实例化时候传入的int值时候,await方法执行结束,开始继续执行后边代码块。其内部使用了AQS机制进行等待和释放。

        final CountDownLatch countDownLatch1 = new CountDownLatch(1);
        final CountDownLatch countDownLatch2 = new CountDownLatch(1);

        Thread t1 = new Thread(() -> {
            System.out.println("i am t1 start");
            System.out.println("i am t1 end");
            countDownLatch1.countDown();
        });

        Thread t2 = new Thread(() -> {
            System.out.println("i am t2 start");
            try {
                countDownLatch1.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("i am t2 end");
            countDownLatch2.countDown();
        });

        Thread t3 = new Thread(() -> {
            System.out.println("i am t3 start");
            try {
                countDownLatch2.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("i am t3 end");
        });

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

        Thread.sleep(2000);

运行结果

i am t1 start
i am t2 start
i am t3 start
i am t1 end
i am t2 end
i am t3 end
  • 方案3 Condition + ReentrantLock

    利用Condition的signal和await方法,按照顺序依次唤醒我们想要的线程来实现。

        final ReentrantLock lock = new ReentrantLock();
        final Condition condition1 = lock.newCondition();
        final Condition condition2 = lock.newCondition();

        Thread t1 = new Thread(() -> {
            System.out.println("i am t1 start");
            lock.lock();
            System.out.println("i am t1 end");
            condition1.signal();
            lock.unlock();
        });

        Thread t2 = new Thread(() -> {
            System.out.println("i am t2 start");
            lock.lock();
            try {
                condition1.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("i am t2 end");
            condition2.signal();
            lock.unlock();
        });

        Thread t3 = new Thread(() -> {
            System.out.println("i am t3 start");
            lock.lock();
            try {
                condition2.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("i am t3 end");
            lock.unlock();
        });

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

        Thread.sleep(1000);

运行结果

i am t3 start
i am t1 start
i am t2 start
i am t1 end
i am t2 end
i am t3 end
  • 方案4 ConditionVariable

    该类其实是对synchroized + wait + notifyAll三者的一个封装,对外暴露open,block和close方法。作用和CountDownLatch类似,但是它可以重复使用。重复使用时候,需要调用close方法进行重置。

        ConditionVariable variable1 = new ConditionVariable();
        ConditionVariable variable2 = new ConditionVariable();

        Thread t1 = new Thread(() -> {
            Log.d("zzz","i am t1 " + System.currentTimeMillis());
            variable1.open();
        });
        Thread t2 = new Thread(() -> {
            variable1.block();
            Log.d("zzz","i am t2 " + System.currentTimeMillis());
            variable2.open();
        });
        Thread t3 = new Thread(() -> {
            variable2.block();
            Log.d("zzz", "i am t3 " + System.currentTimeMillis());
        });

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

        Thread.sleep(500);

运行结果

i am t1 1687867126908
i am t2 1687867126908
i am t3 1687867126908

多线程同时运行

  • 方案1 CountDownLatch

    使用CountDownLatch的await方法在各个线程的run方法中进行等待,当countDown被调用时候,各个线程同时触发await方法,线程得以继续运行。

        final CountDownLatch countDownLatch = new CountDownLatch(1);

        Thread t1 = new Thread(() -> {
            System.out.println("i am t1 start");
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("i am t1 end, time:" + System.currentTimeMillis());
        });

        Thread t2 = new Thread(() -> {
            System.out.println("i am t2 start");
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("i am t2 end, time:" + System.currentTimeMillis());
        });

        Thread t3 = new Thread(() -> {
            System.out.println("i am t3 start");
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("i am t3 end, time:" + System.currentTimeMillis());
        });

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

        Thread.sleep(500);
        countDownLatch.countDown();
        Thread.sleep(1000);

运行结果

i am t3 start
i am t2 start
i am t1 start
i am t3 end, time:1687856571244
i am t2 end, time:1687856571244
i am t1 end, time:1687856571244
  • 方案2 Semaphore

    Semaphore是为了约束当前代码/资源可以同时被多少个线程访问而存在。Semaphore实例化时候需要传入一个int,表示并发数,传3则表示可以被acquire方法调用三次。线程中调用acquire方法时候,内部计数器会自动+1,如果计数器大于int值,那么acquire方法就会阻塞,直到其他地方调用了release方法释放了一个锁,那么这里才可以继续执行。

    对于多线程同时开始运行,就可以使用该类,实例化传入对应并发数,且直接通过acquire获取完其并发额度,这样后续每个线程执行中,遇到这个方法都会阻塞。线程起来后,直接通过release方法释放完所有的额度,这样所有阻塞的线程可以同时继续执行。

        final Semaphore semaphore = new Semaphore(3);
        semaphore.acquire(3);

        Thread t1 = new Thread(() -> {
            System.out.println("i am t1 start");
            try {
                semaphore.acquire(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("i am t1 end, time:" + System.currentTimeMillis());
        });

        Thread t2 = new Thread(() -> {
            System.out.println("i am t2 start");
            try {
                semaphore.acquire(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("i am t2 end, time:" + System.currentTimeMillis());
        });

        Thread t3 = new Thread(() -> {
            System.out.println("i am t3 start");
            try {
                semaphore.acquire(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("i am t3 end, time:" + System.currentTimeMillis());
        });

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

        Thread.sleep(500);
        semaphore.release(3);
        Thread.sleep(1000);

运行结果

i am t2 start
i am t3 start
i am t1 start
i am t3 end, time:1687856648821
i am t1 end, time:1687856648821
i am t2 end, time:1687856648821
  • 方案3 【不是很准确】 cyclicBarrier

    cyclicBarrier表面意思是栅栏的意思,就是直到所有目标都到达了栅栏这里,栅栏才会打开,所有目标继续前进。比如打王者,我方队友5个玩家,相当于5个线程,只有当5个线程都执行完匹配的动作,那么才会进行下一步,进入到选择人物界面。这里就可以利用cyclicBarrier.保证当前的多个线程可以在不结束的情况下,同时到达一个地点,然后再各自继续执行。

    这里也可以使用cyclicBarrier来控制,但是每个线程开始的时间总会有几毫秒的误差,所以精度很高的情况下,这种方法就不合适了。

        final CyclicBarrier cyclicBarrier = new CyclicBarrier(3);

        Thread t1 = new Thread(() -> {
            System.out.println("i am t1 start");
            try {
                cyclicBarrier.await();
                System.out.println("i am t1 end, time:" + System.currentTimeMillis());
            } catch (BrokenBarrierException | InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread t2 = new Thread(() -> {
            System.out.println("i am t2 start");
            try {
                cyclicBarrier.await();
                System.out.println("i am t2 end, time:" + System.currentTimeMillis());
            } catch (BrokenBarrierException | InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread t3 = new Thread(() -> {
            System.out.println("i am t3 start");
            try {
                cyclicBarrier.await();
                System.out.println("i am t3 end, time:" + System.currentTimeMillis());
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
        });

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

        Thread.sleep(1000);

运行结果

i am t1 start
i am t3 start
i am t2 start
i am t2 end, time:1687856779900
i am t1 end, time:1687856779900
i am t3 end, time:1687856779900
  • 方案4 ConditionVariable

    仿照CountDownLatch可以实现该功能。

        final ConditionVariable variable = new ConditionVariable();

        Thread t1 = new Thread(() -> {
            variable.block();
            Log.d("zzz","i am t1 " + System.currentTimeMillis());
        });
        Thread t2 = new Thread(() -> {
            variable.block();
            Log.d("zzz","i am t2 " + System.currentTimeMillis());
        });
        Thread t3 = new Thread(() -> {
            variable.block();
            Log.d("zzz", "i am t3 " + System.currentTimeMillis());
        });

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

        Thread.sleep(50);
        variable.open();

运行结果

i am t3 1687867425101
i am t1 1687867425102
i am t2 1687867425102

多线程全部执行完毕后,执行其他线程

  • 方案1 cyclicBarrier

    CyclicBarrier 本意就是为此而存在,实例化时候可以传一个runnable,当所有目标线程执行结束后,会先运行该Runnable,然后再继续向下运行。

        final CyclicBarrier cyclicBarrier = new CyclicBarrier(3, () ->         System.out.println("i am barrier"));
        Thread t1 = new Thread(() -> {
            System.out.println("i am t1 start");
            try {
                cyclicBarrier.await();
                System.out.println("i am t1 end, time:" + System.currentTimeMillis());
            } catch (BrokenBarrierException | InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread t2 = new Thread(() -> {
            System.out.println("i am t2 start");
            try {
                cyclicBarrier.await();
                System.out.println("i am t2 end, time:" + System.currentTimeMillis());
            } catch (BrokenBarrierException | InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread t3 = new Thread(() -> {
            System.out.println("i am t3 start");
            try {
                cyclicBarrier.await();
                System.out.println("i am t3 end, time:" + System.currentTimeMillis());
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
        });

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

        Thread.sleep(1000);

运行结果

i am t3 start
i am t2 start
i am t1 start
i am barrier
i am t1 end, time:1687856872010
i am t3 end, time:1687856872010
i am t2 end, time:1687856872010
  • 方案2 CountDownLatch

    CyclicBarrier可以通过reset重新利用,而CountDownLatch只可以用一次,下次需要重新实例化。

        final CountDownLatch countDownLatch = new CountDownLatch(3);
        Thread barrier = new Thread(() -> {
            System.out.println("i am barrier start");
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("i ma barrier");
        });
        barrier.start();
        Thread t1 = new Thread(() -> {
            System.out.println("i am t1 start");
            countDownLatch.countDown();
        });

        Thread t2 = new Thread(() -> {
            System.out.println("i am t2 start");
            countDownLatch.countDown();
        });

        Thread t3 = new Thread(() -> {
            System.out.println("i am t3 start");
            countDownLatch.countDown();
        });
        
        t1.start();
        t2.start();
        t3.start();

        Thread.sleep(1000);

运行结果

i am barrier start
i am t3 start
i am t1 start
i am t2 start
i ma barrier