java中请问如何等待一个线程结束在运行其他的代码?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java中请问如何等待一个线程结束在运行其他的代码?相关的知识,希望对你有一定的参考价值。

情况是这样的,我在一个类a中调用了另外两个类b和c,先调用了继承线程的类b,只有一个线程,然后接着调用了类c,这样就出问题了,因为类c需要类b线程运行完后的结果,但是程序在b的线程还没有运行完时就开始运行类c了,这样就导致了c没有需要的数据,并引发了错误,请各位大大帮忙解决下。说的思路如何搞啊?
哎,我的想法可能有些偏离线程的主题了,要么都用线程,要么不用,谢谢大家了!

使用java的线程同步机制。
现在存在两个线程a和b,伪代码如下:
在a的代码中:
b.start();//启动b线程
synchronized(b) b.wait();//a线程中采用同步,并进行等待
c.execute();
在b线程结束时调用synchronized(this) notify();
这样就可以达到b不结束,a线程就不会继续执行c。

注释:如果看不明白建议先学习一下java线程同步机制。
参考技术A 看你的描述,感觉少说明一点。B生成的数据是存数据库中,还是在缓存中。
但基本解决方法应该有两种:
一种:把C的逻辑代码放入B中,顺序执行。
另一种:把A也做成线程,周期用C去读B生成的数据,可以用if null 关键字来控制有没获取到追问

如果把A也做成线程的话好像不太可能,其实a是一个actionform具体用来操作文件转换的,b类转换一种格式,然后再用c类接着转换成另一种格式,由于b类需要用到一个服务,由于服务是线程不安全的所以启动了线程,而c类只是调用了process引用本地程序而已,如果没有其他方法的话a,b,c
类估计都要继承Thread类才能解决问题,或者除非b不用线程。所以我想能不能有其他的办法。

追答

B类发个执行成功的状态给A,如何?

追问

这种方式我也想过,但是正如1楼说的线程(Thread)是一份独立运行的程序,那么在xxx.start()方法执行的时候加入下面还有其他代码,那么代码还会继续运行的,它不会等待返回值的,如果用一个返回状态来进行if判断那么那么if永远不会被执行会直接跳过的不管是success还是false。我那种想法可能要求过分了点,还是另想其他实现功能的办法的了,不过还是谢谢你。

本回答被提问者采纳
参考技术B 那B类一定要继承线程嘛,用run方法,B线程跑完后才会运行下面的程序 参考技术C 线程锁
1.synchronized

第一把锁

1:设计原理

1.1:循环等待法

  锁的作用是什么?就是使多线程同步执行,避免异步出现的脏读错误。从这个角度出发我们可以很自然的象到该如何设计一个锁尼?

我们可以这样设计,当有两个线程要访问待同步的代码块时,我们将该同步代码块的使用权交给第一个访问的线程,使其能够顺利运行,而在第一个线程没有结束使用的之前,其他线程如果也要访问该同步代码块,则会循环等待,知道第一个线程结束了同步代码块的使用。基于这个想法我们实现下面代码。

1.2:循环等待法的代码实现

package reentrantlockTest;

/**
 * @author :dazhu
 * @date :Created in 2020/4/12 8:43
 * @description:朱超成实现的第一把锁
 * @modified By:
 * @version: v1$
 */
public class ZccLock {
    //锁的初始状态为0
    private int status = 0;

    public void lock() {
        //如果锁状态不为0,则一直循环
        //当其他线程在上一个线程没有unlock时,进入lock方法,则会一直进入循环等待
            while (this.status != 0) {
            }
            //如果状态为0,则置1。使其他线程进入后,一直等待。
            this.status = 1;
    }
    public void unlock(){
        //锁状态置0
        this.status = 0;
    }
}

这是最基本的实现方式我们做个测试demo看一下;

package reentrantlockTest;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @author :dazhu
 * @date :Created in 2020/4/12 8:07
 * @description:
 * @modified By:
 * @version: $
 */
public class Main {
    //可重入锁
    public static ReentrantLock reentrantLock = new ReentrantLock();
    //自建锁
    public  static ZccLock zccLock = new ZccLock();
    public static void main(String[] args) throws InterruptedException {
        for(int i=0;i<10;i++) {
            Thread t1 = new Thread(Integer.toString(i)) {
                @Override
                public void run() {
                    testSync();
                }
            };
            t1.start();
        }
    }

    public static void testSync(){
        //reentrantLock.lock();
        //使用自建锁
        zccLock.lock();
        System.out.println(Thread.currentThread().getName());
        try{
            Thread.sleep(100);
        }
        catch (InterruptedException ie){
            ie.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName());
        //reentrantLock.unlock();
        //使用自建锁
        zccLock.unlock();
    }
}

  经过测试发现;只能循行第一个线程,其他线程似乎并没有从循环等待中退出,也就是status的改变没有被其他线程获得!为什么会这样呢?因为虽然第一个线程在unlock中修改了status为0,但是其他线程的内部缓存中并没有还想其改变为0了,依然里面是旧值,所以依然陷入了循环。那么怎么解决尼?我们可以使用volatile关键字来创建status!这样就可以在第一个线程unlock的时候,其他线程也发现了该值被改变了,从而通过总线嗅探技术来从主存中重新获取新值。经过修改发现这两个线程了同步运行了。

1.3:依然存在的问题

  当我们增加线程数量时,发现众多线程之间依然不会同步执行这是为什么尼?可能原因会是什么尼?根据这个现象,显然线程之间异步执行了该代码块。这是怎么发生的尼?

技术图片

 

  这是由于我们循环比较和置1的这两行代码不是原子操作!怎么理解尼?有其他线程进入lock中的循环比较和置数之间,导致多线程其实自己status依然是0,这样就会多线程就异步执行了!如何解决尼?我们使用同步关键字。。。。。这不是套娃嘛?我们的目的就是创建自己的锁,怎么还是用java的锁关键字了。不过我们可以暂时使用synchronized来作证是不是我们分钟的原因。

package reentrantlockTest;

/**
 * @author :dazhu
 * @date :Created in 2020/4/12 8:43
 * @description:朱超成实现的第一把锁
 * @modified By:
 * @version: v1$
 */
public class ZccLock {
    //锁的初始状态为0
    //使用volatile可以保证,当前线程unlock后,改变的status可以被其他线程循环在while的线程
    //发现到,status变为0了,以适他们退出while,得以运行同步块代码。
    //但是如果不使用volatile的话,每一个线程的中自己内部缓存的status不是最新的,改变后的,而是依然1
    private volatile int status = 0;

    public void lock() {
        //如果锁状态不为0,则一直循环
        //当其他线程在上一个线程没有unlock时,进入lock方法,则会一直进入循环等待
        synchronized (this) {
            while (this.status != 0) {
            }
            /**
             * 依然有问题,如果当t1线程执行到这里后,
             * 发生了切换,进入t2线程,那么t2线程因为t1线程没有完成修改了status=1
             * 所以t2依然可以执行同步的代码段。
             * t1和t2可以异步的执行同步的代码段。
             * 只有将前面的比较和下面的置1做成同步块,才能避免错误。
             * 这不是套娃吗?
             */
            //如果状态为0,则置1。使其他线程进入后,一直等待。
            this.status = 1;
        }
    }
    public void unlock(){
        //锁状态置0
        this.status = 0;
    }
}

结果如下:

技术图片

 

 

 果然,线程之间没有异步执行!那如何解决这个问题尼?只要我们可以将循环比较和辅助做成原子操作就可以啦!有专门的汇编指令保证CAS算法的原子性啦

2:设计优化

  很显然,这种死循环的设计方法,当其他线程进入死循环后,十分浪费cpu的资源,如何改进尼?当进入循环后,不要一直循环,而是主动释放cpu资源阻塞该线程,在合适的时候再取唤醒主动释放cpu资源的这些阻塞线程。主要有以下方法!!!我们依依先进行讲解。

1.1:自旋+yield()

package reentrantlockTest;

/**
 * @author :dazhu
 * @date :Created in 2020/4/12 8:43
 * @description:朱超成实现的第一把锁
 * @modified By:
 * @version: v1$
 */
public class ZccLock {
    //锁的初始状态为0
    //使用volatile可以保证,当前线程unlock后,改变的status可以被其他线程循环在while的线程
    //发现到,status变为0了,以适他们退出while,得以运行同步块代码。
    //但是如果不使用volatile的话,每一个线程的中自己内部缓存的status不是最新的,改变后的,而是依然1
    private volatile int status = 0;

    public void lock() {
        //如果锁状态不为0,则一直循环
        //当其他线程在上一个线程没有unlock时,进入lock方法,则会一直进入循环等待
        //死循环的cpu代价很大,所以使用费自旋+yield()
        while (this.status != 0) {
            Thread.currentThread().yield();
        }
        this.status = 1;
    }
    public void unlock(){
        //锁状态置0
        this.status = 0;
    }
}

  在进入while循环的线程里,使用yiedl进行停止该线程的执行,但是本线程主动释放了该cpu资源后,cpu再次进行线程调度,这个调度的顺序是不受我们控制的,很有可能依然会调度该该主动释放cpu资源的线程。所以这个方法不合适。那么自旋+sleep()方法尼?

1.2:自旋+sleep()

package reentrantlockTest;

/**
 * @author :dazhu
 * @date :Created in 2020/4/12 8:43
 * @description:朱超成实现的第一把锁
 * @modified By:
 * @version: v1$
 */
public class ZccLock {
    //锁的初始状态为0
    //使用volatile可以保证,当前线程unlock后,改变的status可以被其他线程循环在while的线程
    //发现到,status变为0了,以适他们退出while,得以运行同步块代码。
    //但是如果不使用volatile的话,每一个线程的中自己内部缓存的status不是最新的,改变后的,而是依然1
    private volatile int status = 0;

    public void lock() {
        //如果锁状态不为0,则一直循环
        //当其他线程在上一个线程没有unlock时,进入lock方法,则会一直进入循环等待
        //死循环的cpu代价很大,所以使用自旋+sleep()
        while (this.status != 0) {
            Thread.currentThread().sleep(1000);
        }
        this.status = 1;
    }
    public void unlock(){
        //锁状态置0
        this.status = 0;
    }
}

  这种方法的能不能解决问题尼?似乎也不是最合适的解决问题的方法。这是因为一个线程等待时间多长合适尼?等待太长容易错过已经释放地同步资源,等待太短依然会造成cpu地资源的浪费。

1.3:自旋+park()+Queue

package reentrantlockTest;

/**
 * @author :dazhu
 * @date :Created in 2020/4/12 8:43
 * @description:朱超成实现的第一把锁
 * @modified By:
 * @version: v1$
 */
public class ZccLock {
    //锁的初始状态为0
    //使用volatile可以保证,当前线程unlock后,改变的status可以被其他线程循环在while的线程
    //发现到,status变为0了,以适他们退出while,得以运行同步块代码。
    //但是如果不使用volatile的话,每一个线程的中自己内部缓存的status不是最新的,改变后的,而是依然1
    private volatile int status = 0;
    private Queue queue =  new LinkedList();

    

    public void lock() {
        //如果锁状态不为0,则一直循环
        //当其他线程在上一个线程没有unlock时,进入lock方法,则会一直进入循环等待
        //死循环的cpu代价很大,所以使用费自旋+yield()
        while (this.status != 0) {
            Thread.currentThread().park();
        }
        this.status = 1;
    }
    public void unlock(){
        //锁状态置0
        this.status = 0;
        lockNotify()
    }

    public void park(){
        //将当前线程加入队列
        queue.add(Thread.currentThread);
        releaseCPU();
    }
    public void lockNotify(){
        //取出队列头部地线程
        Thread t = queue.getHead();
        //唤醒该等待线程
        unpark(t);
    }
}    

  这样就可以实现避免进入死循环了,当进while地线程会被阻塞并进入等待队列,然后当占有同步资源地线程unlock后,会使用lockNotify将该阻塞线程从queue中取出唤醒,避免一直进入死循环浪费cpu资源。ReentrantLock就是这样的一种实现方法,下一阶我们分析ReentrantLock地源码哈

以上是关于java中请问如何等待一个线程结束在运行其他的代码?的主要内容,如果未能解决你的问题,请参考以下文章

如何在JAVA中让一个线程死亡或结束

如何实现java主线程等待子线程执行完毕之后再执行

c# 怎么等待线程池中所有线程都运行结束在运行主线程

如何使“主线程”等待“子线程”执行结束后再继续执行

如何使“主线程”等待“子线程”执行结束后再继续执行

windows API主线程如何等待子线程结束后再继续运行