【多线程】多线程顺序执行的几种方式

272 阅读3分钟

1. join强占cpu


public class Solution {
    public static void joinWay() {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(500);
                    System.out.println("Thread t1");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // 等待t1执行完成
                    t1.join();
                    System.out.println("Thread t2");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // 等待t2执行完成
                    t2.join();
                    System.out.println("Thread t3");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t1.start();
        t2.start();
        t3.start();
    }

    public static void main(String[] args) {
        joinWay();
    }
}

2. 使用wait/notify,这里直接套用之前项目中的大致逻辑

背景是相机拍照连拍,拍照首先要和系统层交互,连拍指令丢给系统层后,返回顺序不确定,比如按照1 2 3 4的顺序,系统层处理完返回的可能是2 1 4 3,需求是按照顺序插入数据库和相册,所以需要按照标号顺序来顺序执行逻辑

/** 定义锁,标识执行到第几个线程 */ 
public class Lock { 
    public Lock() { 
        curNum = 0; 
    } 
    // 当前执行的顺序 
    public int curNum; 
}

定义一个函数,每个线程中调用,判断当前是否轮到自己的标号执行,如果轮到自己,则执行逻辑,执行完唤醒阻塞的线程重新竞争锁;如果没轮到自己,直接wait()释放锁

public static void synWay(int index) { 
    while (true) { 
        synchronized (lock) { 
            if (index == lock.curNum) { 
                System.out.println(index); 
                lock.curNum ++; 
                // 执行完,释放锁 
                lock.notifyAll(); 
            } else { 
                // 不相等,则释放锁 
                try { 
                    lock.wait(); 
                } catch (InterruptedException e) { 
                    e.printStackTrace(); 
                } 
            } 
        } 
    } 
}

3. lock & condition

notify效率不高,每次只能随机唤醒一个等待的线程,当等待线程较多时,可能出现下一个序号执行的线程,在众多线程被唤醒后判断不匹配又释放锁后,很久才被唤醒。
想到ReentrantLock搭配condition,可以实现选择性通知,规避掉notify中间的无效唤醒时间。
但存在一个问题,有多少个序号,就需要提前准备多少个condition对象,序号很多的情况下,对内存不友好。


ReentrantLock lock = new ReentrantLock();
int number = 0;
Condition[] conditions = new Condition[3];

{
    conditions[0] = lock.newCondition();
    conditions[1] = lock.newCondition();
    conditions[2] = lock.newCondition();
}

/** 启动3个线程 */
public static void reenWay() throws InterruptedException {
        Solution s = new Solution();
        new Thread(() -> {
            try {
                s.f(0);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(() -> {
            try {
                s.f(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(() -> {
            try {
                s.f(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
}


public void f(int index) throws InterruptedException {
    lock.lock();
    try {
        while (number != index) {
            conditions[index].await();
        }
        System.out.println("num:" + index);
        Thread.sleep(100);
        if (index != conditions.length - 1) {
            number++;
            conditions[index + 1].signal();
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        lock.unlock();
    }
}

4. 使用单线程线程池

static ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); public static void singleThreadWay() { 
    for (int i = 0; i < 10; i++) { 
        int finalI1 = i; 
        singleThreadExecutor.submit(new Runnable() { 
            @Override public void run() { 
                try { 
                    Thread.sleep(100); 
                    System.out.println(finalI1); 
                } catch (InterruptedException e) { 
                    e.printStackTrace(); 
                } 
            } 
        }); 
    } 
}

5. CountDownLatch

CountDownLatch可以应对一些先并发再统一处理的逻辑,比如大文件分块下载,下载完毕后对文件进行整合、解压等操作。
CountDownLatch维护了一个计数器,可以在构造方法中赋初始值,线程调用countDown()方法原子减,调用await()方法的线程一直阻塞,直到latch值减为0。

public class CountDownLatchEx {

    final static CountDownLatch latch = new CountDownLatch(2);

    private static class SampleThread extends Thread {
        @Override
        public void run() {
            try {
                latch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("SampleThread down");
        }
    }

    private static class WorkThread extends Thread {
        private final String threadName;
        private final int sleepTime;

        public WorkThread(String name, int sleepTime) {
            this.threadName = name;
            this.sleepTime = sleepTime;
        }

        @Override
        public void run() {
            System.out.println("WorkThread " + threadName + " start");
            try {
                Thread.sleep(sleepTime);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            latch.countDown();
            System.out.println("WorkThread down");
        }
    }

    public static void main(String[] args) {
        new SampleThread().start();
        new WorkThread("A", 5000).start();
        new WorkThread("B", 2000).start();
    }
}