Thread类的常见方法,join和yield的区别

      在 Java 中,创建线程去执行子任务一般有四种方式:

       1.继承Thread类,并重写run方法

       2.创建一个类去实现Runnable接口,然后将这个类以参数的形式传递给Thread类。

       3.实现Callable接口

       4.使用线程池的方法创建线程

       这四种方法或多或少都要和Thread打交道,所以先研究好Thread类是进行java并发编程的第一步,我们对前两种方式进行简单分析,后两者在使用线程池再介绍。

      第一种:继承Thread类,并重写run方法

public class ThreadTest extends Thread{
    private int num;
    public ThreadTest(int num) {
        this.num = num;
    }

    @Override
    public void run() {
        while((num--)>0){
            System.out.println(num);
        }
    }
}
class Test1{
    public static void main(String[] args) {
        final int NUM=10;
        for (int i = 0; i < 2; i++) {
            ThreadTest t = new ThreadTest(NUM);  //新建一个线程
            t.start();  //启动线程
        }
    }
}

       通过继承Thread类创建的线程有一个缺点,就是该类不能再继承其他类了,另外,每个线程都是独立的,所以上面的例子中,两个线程都会输出10次。

      第二种:创建一个类去实现Runnable接口,然后将这个类以参数的形式传递给Thread类。

public class RunnableTest implements Runnable {
    private int num=10;

    public RunnableTest(int num) {
        this.num = num;
    }

    @Override
    public void run() {
        while((num--)>0){
            System.out.println(num);
        }
    }
}
class Test{
    public static void main(String[] args) {
        RunnableTest task = new RunnableTest(10);
        for (int i = 0; i < 2; i++) {
            Thread t = new Thread(task);
            t.start();  //启动一个线程
        }
    }
}

       通过实现Runnable接口,这时可以成这个类为一个任务类,将这个类以参数的形式传递给Thread,这时两个线程只会输出10次,我们可以把Thread看作线程,实现Runnable接口的类看作任务,也就是两个线程执行的是同一个任务。

       接下来分析一下,Thread类的常见方法。

       1.run方法

        我们先来看一下run的源码


 //private Runnable target;  
 public void run() {
      if (target != null) {  //target为Thread类的成员变量,可以通过构造方法的方式传入target
           target.run();
       }
 }

     其中target为Runnable类型的变量,如果从构造方法中传入Runnable的实现类,target就不为空,调用target的run方法,如果继承了Thread类,就重写了run方法,这是就调用了子类中的run方法了 。

    所以,run()方法是不需要用户来调用的。当通过start()方法启动一个线程之后,一旦线程获得了CPU执行时间,便进入run()方法体去执行具体的任务。 一般来说,有两种方式可以达到重写run()方法的效果:

       直接重写:直接继承Thread类并重写run()方法;

       间接重写:通过Thread构造函数传入Runnable对象 (注意,实际上重写的是 Runnable对象 的run() 方法)。

    2.start方法

     start() 用来启动一个线程,当调用该方法后,相应线程就会进入就绪状态,该线程中的run()方法会在某个时机被调用

   3)sleep 方法

     调用sleep方法相当于让线程进入阻塞状态。该方法有如下两条特征: 如果调用了sleep方法,必须捕获InterruptedException异常或者将该异常向上层抛出; sleep方法不会释放该线程所拥有的资源(例如:锁),也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象。

4)join 方法

    先来看一下join的源码

public final void join() throws InterruptedException {
    join(0);
}
public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {  //millis为0时表示无限等待,只要你还活着,我就等你到天荒地老
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

   当前线程调用其他线程的join方法,会阻塞当前线程,直到其他线程执行完毕,才会进入就绪状态。

      join方法是被Synchronized关键字所修饰,访问时,需要获得其他线程对象的锁,如果有两个线程同时调用另外一个线程的join方法,会有一个线程成功得到锁,而另外一个则必须等待,进入阻塞状态,而在得到锁之后,才会执行join方法。

      join()方法是通过wait()方法 (Object 提供的方法) 实现的。当 millis == 0 时,会进入 while(isAlive()) 循环,并且只要子线程是活的, 宿主线程就不停的等待。 join方法同样会会让线程交出CPU执行权限; join方法同样会让线程释放对一个对象持有的锁;

5)yield 方法

  调用 yield()方法会让当前线程交出CPU资源,让CPU去执行其他的线程。但是,yield()不能控制具体的交出CPU的时间。需要注意的是,yield()方法只能让 拥有相同优先级的线程 有获取 CPU 执行时间的机会;

       调用yield()方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新得到 CPU 的执行;

       它同样不会释放锁。

发布了225 篇原创文章 · 获赞 30 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/qq_35634181/article/details/83040267