关键词

java基本教程之join方法详解 java多线程教程

Java中的join()方法是多线程编程常用的一个方法,它的作用是让调用该方法的线程等待被调用线程执行完毕后再继续执行。本文将详细讲解join()方法的使用和注意事项。

什么是join()方法

在介绍join()方法之前,我们先回忆一下多线程的基础。在Java中,当创建一个线程对象并调用start()方法后,线程对象就会进入就绪状态,等待CPU分配时间片段并执行。当然,在实际应用中,很多时候我们需要协调不同线程的执行顺序。比如,在执行某些任务时,我们需要先让某个线程执行完毕后再执行其他线程。这时,就需要使用join()方法来实现。

join()方法是Thread类中的一个方法,它有一个可选参数,表示等待的时间,单位是毫秒。当某个线程调用了其他线程的join()方法时,它会被阻塞并等待其他线程执行完毕后继续执行。如果指定了超时时间,则等待的最大时间就是这个超时时间。

下面是一个示例代码,演示join()方法的基本用法:

public class JoinDemo {

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.out.println("t1开始执行");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t1执行完毕");
        });

        Thread t2 = new Thread(() -> {
            System.out.println("t2开始执行");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t2执行完毕");
        });

        t1.start();
        t1.join(); // t1执行完毕后才会执行t2
        t2.start();
    }
}

在上面的代码中,我们首先创建了两个线程t1和t2,并分别用lambda表达式实现了它们的run()方法。我们希望在执行t2时先等待t1执行完毕,因此我们在t1调用完start()方法后接着调用了join()方法,指定t1执行完毕后才会执行t2。这样,t2就会一直等待t1执行完毕,直到t1执行完毕后才会开始执行。

运行上面的代码,输出结果如下:

t1开始执行
t1执行完毕
t2开始执行
t2执行完毕

可以看到,在调用了t1.join()方法后,主线程会一直等待t1执行完毕后再执行后续代码,因此先输出了t1执行完毕的语句。只有当t1执行完毕后,t2才会开始执行。

join()方法的注意事项

虽然join()方法是实现多线程协作的常用方法,但也有些需要注意的事项。

  1. join()方法抛出InterruptedException

当主线程或其他线程调用某个线程的join()方法时,如果该线程被中断,join()方法会抛出InterruptedException异常。因此,我们在调用join()方法时需要捕获该异常。

  1. join()方法会释放锁

当线程调用其他线程的join()方法时,它会持有被调用线程的锁,等待被调用线程执行完毕后,被调用线程释放锁后才会开始继续执行。这意味着,在调用join()方法时需要注意对共享变量的访问,以避免出现死锁等问题。

下面是一个示例代码,展示了join()方法可能导致的死锁问题:

public class JoinDeadlockDemo {

    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            synchronized (lock1) {
                System.out.println("t1持有lock1,等待lock2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock2) {
                    System.out.println("t1持有lock1和lock2");
                }
            }
        });

        Thread t2 = new Thread(() -> {
            synchronized (lock2) {
                System.out.println("t2持有lock2,等待lock1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock1) {
                    System.out.println("t2持有lock1和lock2");
                }
            }
        });

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

        // 下面这两行代码会导致死锁
        t1.join(); 
        t2.join();
    }
}

在上面的代码中,我们创建了两个线程t1和t2,并通过synchronized关键字使它们持有lock1和lock2两个对象的锁。我们在t1和t2中都加入了一段等待时间和输出语句,在输出语句之前,先释放lock2的锁,再获取lock1的锁,确保执行t1和t2时都会先获取lock1对象的锁。

在主线程启动t1和t2后,接着调用了t1.join()和t2.join()方法,让主线程等待t1和t2执行完毕后再继续执行。由于t1持有lock1的锁,等待t2释放lock2的锁;而t2持有lock2的锁,等待t1释放lock1的锁。这就造成了t1和t2相互等待的死锁现象。

运行上面的代码,可以看到程序一直处于等待状态,没有任何输出。

为避免出现死锁等问题,在使用join()方法时,我们需要注意对共享变量的访问,并根据实际情况采取不同的策略。比如,可以通过设置适当的超时时间来避免死锁问题,或在持有锁的代码块中尽快释放锁等。

本文链接:http://task.lmcjl.com/news/12988.html

展开阅读全文