暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

JAVA并发编程-线程按顺序执行

仑哥讲JAVA 2019-09-02
591

    在面试和实际业务需求中,会有一些情况,需要在A线程执行结束后再执行B线程,或者线程按照一定规则有序执行。

    举个例子,如何让两个线程交替输出1 2 3 4...10,也就是A线程输出13579,B线程输出246810。

    首先可以想到利用锁的机制,对同一个资源进行加锁,在适当的情况释放锁,通过Object对象的notifyAll/notify和wait方法实现。官方对这些方法的说明如下:

void notifyAll() 
Wakes up all threads that are waiting on this object’s monitor. 
译:唤醒在此对象监视器上等待的所有线程

void wait( ) 
Causes the current thread to wait until another thread invokes the notify() method or the notifyAll( ) method for this object. 
译:导致当前的线程等待,直到其他线程调用此对象的notify( ) 方法或 notifyAll( ) 方法

    这些方法都是Object的而非Thread的,更多带有参数的说明可以查看具体方法说明,由上可知:

    wait( ),notify( ),notifyAll( )都不属于Thread类,而是属于Object基础类,也就是每个对象都有wait( ),notify( ),notifyAll( ) 的功能,因为每个对象都有锁,锁是每个对象的基础,当然操作锁的方法也是最基础了。

    很容易的就可以想到可以利用此机制实现交替输出,首先维护一个共享的参数i和一个锁对象,线程A判断参数i,如果是奇数就输出,并且加一,然后释放锁,线程B判断偶数,如果是偶数就输出,并且加一,然后释放锁。简单的代码如下:

 1package com.allen.design.thread;
2
3public class ThreadOrder {
4    public static Object object = new Object();
5    public static Integer i = 1;
6
7    public static void main(String[] args{
8        new Thread(new ThreadA(),"线程A").start();
9        new Thread(new ThreadB(),"线程B").start();
10    }
11}
12
13class ThreadA implements Runnable {
14
15    @Override
16    public void run(
{
17        synchronized (ThreadOrder.object) {
18            while (true) {
19                if(ThreadOrder.i>=10){
20                    break;
21                }
22                if (ThreadOrder.i % 2 == 0) {
23                    try {
24                        ThreadOrder.object.wait();
25                    } catch (InterruptedException e) {
26                        e.printStackTrace();
27                    }
28                }
29                System.out.println(Thread.currentThread().getName()+":"+ThreadOrder.i);
30                ThreadOrder.i++;
31                ThreadOrder.object.notifyAll();
32            }
33        }
34    }
35}
36
37class ThreadB implements Runnable {
38    @Override
39    public void run(
{
40        synchronized (ThreadOrder.object) {
41            while (true) {
42                if(ThreadOrder.i>=10){
43                    break;
44                }
45                if (ThreadOrder.i % 2 == 1) {
46                    try {
47                        ThreadOrder.object.wait();
48                    } catch (InterruptedException e) {
49                        e.printStackTrace();
50                    }
51                }
52                System.out.println(Thread.currentThread().getName()+":"+ThreadOrder.i);
53                ThreadOrder.i++;
54                ThreadOrder.object.notifyAll();
55            }
56        }
57
58    }
59}

    上述方式同样适用于生产消费者问题的解决,输出结果如下:

    但是这个概念是较为难以理解的,例如线程A在何时被唤醒,为什么会被唤醒,什么时候又会释放锁,释放后为什么线程B就可以得到。

    JUC(JAVA并发包)给我们提供另外一种锁的实现方式,相对容易理解一些,JUC包中顶层有Lock接口,结构如下

    Lock接口有如下方法:

    ReentrantLock(可重入锁)主要依赖Sync实现了Lock接口,锁的性能大为提升,其关键在于基础工具Sync,而Sync继承了AbstractQueuedSynchronizer(AQS),AQS是JUC包实现同步的基础工具。AQS中定义了一个volatile int state变量作为共享资源,如果线程获取资源失败,则进入同步FIFO队列中等待;如果成功获取资源就执行临界区代码。执行完释放资源时,会通知同步队列中的等待线程来获取资源后出队并执行。

    使用可重入锁实现AB线程按顺序输出的代码如下:

 1package com.allen.design.thread;
2
3import java.util.concurrent.TimeUnit;
4import java.util.concurrent.locks.Lock;
5import java.util.concurrent.locks.ReentrantLock;
6
7public class ThreadOrderCyclic {
8    public static Integer i = 1;
9    public static void main(String[] args{
10        ReentrantLock lock = new ReentrantLock();
11        new Thread(new ThreadC(lock), "线程A").start();
12        new Thread(new ThreadD(lock), "线程B").start();
13
14    }
15
16}
17
18class ThreadC implements Runnable{
19    private Lock lock;
20    ThreadC(Lock lock){
21        this.lock = lock;
22    }
23    @Override
24    public void run(
{
25        while (true){
26            if(ThreadOrderCyclic.i>=10){
27                break;
28            }
29            try {
30                lock.tryLock(1, TimeUnit.SECONDS);
31            } catch (InterruptedException e) {
32                e.printStackTrace();
33            }
34
35            if(ThreadOrderCyclic.i%2==1){
36                System.out.println(Thread.currentThread().getName() + ":" + ThreadOrderCyclic.i);
37                ThreadOrderCyclic.i++;
38            }
39            lock.unlock();
40        }
41    }
42}
43class ThreadD implements Runnable{
44    private Lock lock;
45    ThreadD(Lock lock){
46        this.lock = lock;
47    }
48    @Override
49    public void run(
{
50        while (true){
51            if(ThreadOrderCyclic.i>=10){
52                break;
53            }
54            try {
55                lock.tryLock(1, TimeUnit.SECONDS);
56            } catch (InterruptedException e) {
57                e.printStackTrace();
58            }
59            if(ThreadOrderCyclic.i%2==0){
60                System.out.println(Thread.currentThread().getName() + ":" + ThreadOrderCyclic.i);
61                ThreadOrderCyclic.i++;
62            }
63            lock.unlock();
64        }
65    }
66}

    输出与使用synchronized锁方式相同,但是可重入锁的阅读性上高了很多,并且提高了锁的性能。

    并发包中还有更多锁与线程的工具类,例如时间维度信号量CountDownLathc,信号维度信号量Semaphore,可循环使用的信号量CyclicBarrier,以及线程池,线程本地变量等,可以在确保性能和安全的前提下,大大降低并发编程的难度,后面将会逐一介绍。

文章转载自仑哥讲JAVA,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论