线程之创建和内部方法调用区别

385 阅读4分钟

Thread的创建

Android中有两种方式可以创建一个Thread,可以直接new 一个Thread对象,或者实现一个Runnable对象给Thread来创建Thread对象。


class Thread implements Runnable

Thread thread = new Thread(){
            @Override
            public void run() {
                System.out.println("-------------1");
            }
        };
        thread.start();


Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("-------------2");

            }
        });
        thread1.start();

Thread本身实现了Runnable接口,当调用Thread的run方法时,会判断是否设置了单独的Runnable对象,若设置了则执行Runnable的run方法,如果没有设置我们需要重写Thread的run方法。

Thread的中断

Thread中提供了以下三个方法来中断和判断中断Thread:

public void interrupt(){} //中断线程

public boolean isInterrupted(){} //判断线程是否被中断

public static boolean interrupted(){} //判断线程是否被中断并清除中断状态

若当前线程处于阻塞状态或者试图执行一个阻塞操作时,调用Thread对象的interrupt方法来中断线程会导致InterruptedException异常,同时中断状态将会被复位(由中断状态变为非中断状态)。

public class SyncTask implements Runnable {

    static int count = 0;

    static  SyncTask syncTask = new SyncTask();


    @Override
    public void run() {

        synchronized (this){
            for(int i = 0;i < 5;i++){
                count++;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println("interrupt="+Thread.currentThread().isInterrupted());
                }
               }
        }
    }


//执行函数
Thread thread1=new Thread(syncTask,"thread1");
thread1.start();
try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        thread1.interrupt();
}

//执行结果
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
interrupt=false

可以看到当线程调用Thread.sleep时,线程进入阻塞状态,此时调用interrupt方法会抛出异常。如果线程处于运行状态,我们可以发现就算调用interrupt方法依然不会中断线程.

@Override
    public void run() {

        synchronized (this){
            while (true){
                System.out.println("itr="+Thread.currentThread().isInterrupted());
            }
        }
    }
    

//执行函数
Thread thread1=new Thread(syncTask,"thread1");
thread1.start();
try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        thread1.interrupt();
}

//执行结果
。。。
itr=false
itr=true
itr=true
itr=true
itr=true
。。。

发现指示中断的标志改变了,而线程并没有中断。所以需要我们在线程中判断是否中断了线程,来主动跳出循环结束线程。

 @Override
    public void run() {

        synchronized (this){
            while (true){

                if(Thread.currentThread().isInterrupted()) return;
                System.out.println("itr="+Thread.currentThread().isInterrupted());
            }
        }
    }

这里根据isInterrupted来判断是否中断线程,结束循环。看到这里我们可以得出如何结束线程的两种方法:

  1. 当线程处于阻塞状态或试图执行一个阻塞操作时,调用interrupt方法会抛出interrupt异常并将中断复位,此时我们可以拦截异常执行结束线程操作。
  2. 当线程处于运行状态时,需要调用interrupt方法,并在线程的run方法中判断线程中断状态来结束线程。

join方法

join() // join(0);
join(long millis, int nanos)
join(long millis)

调用join方法的线程对象所在的线程会进入WAITING状态,等待线程对象run方法执行完毕,或者millis时间到达后才会继续往下执行。

例如以下代码:main线程会等到20s之后才会执行。

public static void main(String[] args) {

        TestThread testThread = new TestThread();
        testThread.start();
        try {
            testThread.join(20000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("main thread is run ");
        System.out.println("---------------------");
    }

    static class TestThread extends Thread {
        @Override
        public void run() {
            System.out.println("child thread is run ");
            long time = System.currentTimeMillis()+20000;
            while (System.currentTimeMillis()<time){

            }
            System.out.println("child thread is end ");
        }
    }

如果我们把join方法中millis改为2000,表明child线程执行2s后,会唤醒main线程,main线程继续执行。

join方法内部采用wait和notifyAll来实现线程的等待和唤醒。

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

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

        if (millis == 0) {
            while (isAlive()) {
                lock.wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                lock.wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
        }
    }

main线程调用child线程的join方法,获得child线程的对象锁,当调用wait方法后,main线程释放对象锁,进入WAITING状态等待唤醒,不在往下执行。这里的isAlive方法判断当前子线程是否存活,若子线程不存活则main线程不在等待。

到这里我们需要考虑一下,main线程进入阻塞状态后,是如何被唤醒的?

结果就是: 唤醒操作由Thread对象的系统操作来完成,当线程执行结束后,在thread.cpp中会调用notifyAll方法唤醒所有等待的线程。

thread.cpp讲解了具体的原因

wait、notify、notifyAll使用

wait、notify、notifyAll必须在synchronized修饰的方法或者代码块中使用,否则会抛出IllegalMonitorStateException异常。因为在使用这三类方法时Thread必须获取当前对象锁的monitor监控器对象。注意wait之后,其他线程必须调用notify才会继续执行当前线程,同一时候只有一个线程可以持有monitor监控器对象,未持有monitor监控器对象的Thread调用notifyAll时。会抛出IllegalMonitorStateException异常。

Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
	at java.lang.Object.notifyAll(Native Method)
	at com.mdy.string_struct.MyThread$TestThread.run(MyThread.java:29)

wait 和sleep区别

  1. sleep属于Thread的静态方法,当Thread被中断时,调用sleep方法会抛出InterruptedException异常。wait属于Obkect对象的方法,必须在synchronized修饰的方法或者代码块中使用。

  2. wait会使当前线程暂停进入阻塞状态,并释放监控器monitor对象。sleep只会阻塞当前线程并不会释放monitor对象。调用notify、notifyAll后,也不是立即释放monitor对象,而是在synchronized方法结束后才自动释放锁。