java线程顺序控制

261 阅读4分钟

Future.get()

Future.get()是获取线程的返回值,但是在等待线程返回值的时候会阻塞线程。具体等待情况如下:

示例一

class A implements Callable<Integer> {
    static Logger logger = Logger.getGlobal();
    int i;
    public A(int i) {
        this.i = i;
    }
    public Integer call() throws Exception {
        logger.info(Thread.currentThread().getName() + ", " + i);
        return i;
    }
}

public class TestReturnValue {
    static Logger logger = Logger.getGlobal();
    public static void main(String[] args) throws InterruptedException, ExecutionException {
	ThreadPoolExecutor threadpool = new ThreadPoolExecutor(5, 5, 1000, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(), new ThreadPoolExecutor.CallerRunsPolicy());
	for (int i = 0; i < 5; i++) {
            Callable<Integer> a = new A(i);
            Future<Integer> future = threadpool.submit(a);
            logger.info("" + future.get());
	}
	threadpool.shutdown();
    }
}

选择日志输出是因为日志输出可以显示时间,能够更好的了解这个阻塞。输出结果如下:

七月 28, 2021 3:20:20 下午 com.xxx.threadtest.test5.A call
信息: pool-1-thread-1, 0
七月 28, 2021 3:20:20 下午 com.xxx.threadtest.test5.TestReturnValue main
信息: 0
七月 28, 2021 3:20:20 下午 com.xxx.threadtest.test5.A call
信息: pool-1-thread-2, 1
七月 28, 2021 3:20:20 下午 com.xxx.threadtest.test5.TestReturnValue main
信息: 1
七月 28, 2021 3:20:20 下午 com.xxx.threadtest.test5.A call
信息: pool-1-thread-1, 2
七月 28, 2021 3:20:20 下午 com.xxx.threadtest.test5.TestReturnValue main
信息: 2
七月 28, 2021 3:20:20 下午 com.xxx.threadtest.test5.A call
信息: pool-1-thread-2, 3
七月 28, 2021 3:20:20 下午 com.xxx.threadtest.test5.TestReturnValue main
信息: 3
七月 28, 2021 3:20:20 下午 com.xxx.threadtest.test5.A call
信息: pool-1-thread-1, 4
七月 28, 2021 3:20:20 下午 com.xxx.threadtest.test5.TestReturnValue main
信息: 4

从这个输出结果可以很明显的主义到main线程是在a线程执行完成(获取到线程的返回结果)后再执行的。

示例二

修改一下线程a的代码,如下:

class A implements Callable<Integer> {
    static Logger logger = Logger.getGlobal();
    int i;
    public A(int i) {
        this.i = i;
    }
    public Integer call() throws Exception {
        Thread.sleep(2000);
        logger.info(Thread.currentThread().getName() + ", " + i);
        return i;
    }
}

输出结果:

七月 28, 2021 3:32:05 下午 com.xxx.threadtest.test5.A call
信息: pool-1-thread-1, 0
七月 28, 2021 3:32:05 下午 com.xxx.threadtest.test5.TestReturnValue main
信息: 0
七月 28, 2021 3:32:07 下午 com.xxx.threadtest.test5.A call
信息: pool-1-thread-2, 1
七月 28, 2021 3:32:07 下午 com.xxx.threadtest.test5.TestReturnValue main
信息: 1
七月 28, 2021 3:32:09 下午 com.xxx.threadtest.test5.A call
信息: pool-1-thread-1, 2
七月 28, 2021 3:32:09 下午 com.xxx.threadtest.test5.TestReturnValue main
信息: 2
七月 28, 2021 3:32:11 下午 com.xxx.threadtest.test5.A call
信息: pool-1-thread-2, 3
七月 28, 2021 3:32:11 下午 com.xxx.threadtest.test5.TestReturnValue main
信息: 3
七月 28, 2021 3:32:13 下午 com.xxx.threadtest.test5.A call
信息: pool-1-thread-1, 4
七月 28, 2021 3:32:13 下午 com.xxx.threadtest.test5.TestReturnValue main
信息: 4

从结果可以看出每组数据直接时间差是2s,跟我们设置的等待时间是一样的。所以可以使用Future.get()实现线程的等待。

信号量的使用

问题

有3个线程abcdef,线程池大小为2,即每次最多只能执行2个线程,要求实现场景如下:
线程c必须在线程ab执行完成后执行,其它顺序不管。循环执行,全部线程执行完成后再执行下一轮。

信号量

百度释义:信号量Semaphore,有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。在进入一个关键代码段之前,线程必须获取一个信号量;一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个线程释放信号量。为了完成这个过程,需要创建一个信号量VI,然后将Acquire Semaphore VI以及Release Semaphore VI分别放置在每个关键代码段的首末端。确认这些信号量VI引用的是初始创建的信号量。

问题解决

class AA implements Runnable {
    Semaphore sema;
    public AA(Semaphore sema) {
	this.sema = sema;
    }
    public void run() {
	try {
            sema.acquire(); // 获取信号量
            System.out.println(Thread.currentThread().getName() + "AA");
            sema.release(); // 释放信号量
	} catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class BB implements Runnable {
    Semaphore sema;
    public BB(Semaphore sema) {
	this.sema = sema;
    }
    public void run() {
	try {
            sema.acquire(); // 获取信号量
            System.out.println(Thread.currentThread().getName() + "BB");
//          sema.release(); // 释放信号量
	} catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class CC implements Runnable {
    Semaphore sema;
    public CC(Semaphore sema) {
	this.sema = sema;
    }
    public void run() {
	try {
            sema.acquire(); // 获取信号量
            System.out.println(Thread.currentThread().getName() + "CC");
//          sema.release(); // 释放信号量
	} catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class TestQuestion {
    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor threadpool = new ThreadPoolExecutor(2, 2, 1000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), new ThreadPoolExecutor.CallerRunsPolicy());
        Semaphore sema1 = new Semaphore(1);
        Semaphore sema2 = new Semaphore(1);
        for (int i = 0; i < 5; i++) {
            AA aa = new AA(sema1);
            BB bb = new BB(sema1);
            CC cc = new CC(sema2);
            threadpool.submit(aa);
            threadpool.submit(cc);
            threadpool.submit(bb);
            Thread.sleep(50);
            sema2.release();
            sema1.release();
            System.out.println("--------------");
        }
        threadpool.shutdown();
    }
}

输出结果:

pool-1-thread-1AA
pool-1-thread-2CC
pool-1-thread-1BB
--------------
pool-1-thread-2AA
pool-1-thread-2BB
pool-1-thread-1CC
--------------
pool-1-thread-2AA
pool-1-thread-1CC
pool-1-thread-2BB
--------------
pool-1-thread-1AA
pool-1-thread-1BB
pool-1-thread-2CC
--------------
pool-1-thread-1AA
pool-1-thread-1CC
pool-1-thread-2BB
--------------

从结果中可以看成,b线程始终是在a线程执行完成之后才执行的。这是因为b线程执行需要的信号量是在a线程执行完成后才会释放的。而b,c线程的信号量释放是在主线程中等待了一会,是为了保证所有线程执行完成之后再释放b,c的信号量,其实不太严谨,可以结合上面的get方法其实更好,懒得修改了,就这样吧。