java并发_Thread类源码学习

一、创建线程

Thread在使用时,一种方式是构造一个Thread的子类,通过覆盖run方法实现。

        Thread t = new Thread() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        };
        t.start();

还有是使用Runnable接口的方法

 Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        };
        Thread t = new Thread(runnable);
        t.start();

java8中又为我们提供了lambda语法。

        Runnable runnable = () -> {
            System.out.println("hello");
        };
        Thread t = new Thread(runnable);
        t.start();

lambda语法其实通过lambda工厂构造了一个Runnable实例。

为什么构造Thread类时传入Runnable实例,就会调用Runnable实例的方法呢?

在调用start方法后,会回调Thread实例的run方法,如果Runable实例不为null,就会调用Runable实例的run方法。

    private Runnable target;

    public Thread(Runnable target) {
       //此处省略300字
       this.target = target;
       //此处省略300字
    }

    public void run() {
        if (target != null) {
            target.run();
        }
    }

二、线程中断(stop、interrupt、isInterrupted和interrupted)

1、stop方法

stop方法用于停止一个线程,但是这个方法已经被废弃。

为什么stop()方法被废弃而不被使用呢?

原因是stop()方法太过于暴力,会强行把执行一半的线程终止。这样会就不会保证线程的资源正确释放,通常是没有给与线程完成资源释放工作的机会,因此会导致程序工作在不确定的状态下。

我都使用过文件流,文件流打开后必须关闭。请看下面一个例子:

class FileRunnable implements Runnable {
    @Override
    public void run() {
        BufferedOutputStream bos=null;
        try {
            bos =new BufferedOutputStream( new FileOutputStream("C:\\abc.txt"));
            System.out.println("数据开始写入缓存");
            Thread.sleep(1000);
            bos.write(1);//停留1秒模拟大量数据需要写入缓存
            System.out.println("数据写入缓存成功");

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            flushAndClose(bos);
        }
    }

    private void flushAndClose(OutputStream outputStream) {
        if (outputStream != null) {
            try {
                Thread.sleep(1000);
                outputStream.flush();//停留1秒模拟大量数据需要从缓存写入到文件
                System.out.println("flush 成功");

                outputStream.close();
                System.out.println("close 成功");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

FileRunnable类的目的是写数据到一个文件,然后再关闭这个文件流,不关闭流是十分危险的。

public static void main(String args[]) throws Exception {
        Thread td = new Thread(new FileRunnable());
        td.start();
        Thread.sleep(500);
        td.stop();
    }

结果:

结果分析:线程sleep 500毫秒后调用了stop方法,td线程应该正在写入数据到缓存,stop的出现打断线程,立即退出run方法,但是有由于try-catch-finally的缘故,退出之前必须执行finally语句块,我们在finally语句块中成功关闭了流,也就是释放了资源。

在这个例子中我们发现使用try-catch-finally语句块是有机会释放资源的。但是如果线程运行到finally语句,突然stop方法被调用又会出现什么情况呢?

    public static void main(String args[]) throws Exception {
        Thread td = new Thread(new FileRunnable());
        td.start();
        Thread.sleep(1500);
        td.stop();
    }

结果:

结果分析:线程sleep 1500毫秒后调用了stop方法,td线程应该正在将缓存中的数据写入到文件,stop的出现打断线程,立即退出run方法,这一次我们的流没有被关闭,资源没有被释放,这是非常危险的。stop像是一个幽灵,随时可能出现,不给你任何反应的时间。

2、interrupt方法

stop方法不好用,我们用什么方法替代stop方法呢?没错是interrupt方法。不像stop方法的暴力,interrupt方法更加柔和。interrupt是线程中断并不是终止,interrupt方法好像是告诉线程请你停下来,线程停还是不停完全是由线程自己决定

 public static void main(String args[]) throws Exception {
        Thread td = new Thread(){
            public void run() {
                while (true){
                    System.out.println("1");
                }
            }
        };
        td.start();
        Thread.sleep(10);
        td.interrupt();
    }

这个例子中即使调用了interrupt方法,td线程也不会停止,td不理会interrupt,会无限次打印1。

isInterrupted方法用来检测interrupt方法是否被调用过,如果被调用返回true,否则返回false。

    public static void main(String args[]) throws Exception {
        Thread td = new Thread(){
            @Override
            public void run() {
                while (!this.isInterrupted()){
                    System.out.println("1");
                }
                System.out.println("interrupt 方法被调用");
            }
        };
        td.start();
        Thread.sleep(10);
        td.interrupt();
    }

结果:

当在一个被阻塞的线程(调用sleep或wait)上调用interrupt方法时,sleep或wait 会抛出interrupted Exception异常,出现了这种异常说明interrupt方法被调用,异常抛出后线程的中断状态将会被重置

   实例:

    public static void main(String args[]) throws Exception {
        Thread td = new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    System.out.println("InterruptedException被捕获");
                    System.out.println("isInterrupted:"+this.isInterrupted());
                }
            }
        };
        td.start();
        Thread.sleep(10);
        td.interrupt();
    }

 结果:

sleep抛出异常后,线程状态被重置,isInterrupted会返回false。

3、interrupted方法

前面我们学习了interrupt方法和isinterrupted方法,这两个方法都是对象方法,但是interrupted方法时静态方法。其实isinterrupted方法和interrupted方法都是用来判断当前线程是否为中断状态,不同的是isinterrupted返回线程状态后会做其他操作,而interrupted方法如果返回true,也就是说线程处于中断状态,会重置线程状态。

    public boolean isInterrupted() {
        return isInterrupted(false);
    }

    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }

    private native boolean isInterrupted(boolean ClearInterrupted);

实例:

public static void main(String args[]) throws Exception {
        Thread td = new Thread() {
            @Override
            public void run() {
                int i=0;
                while (i<1000){i++;}//耽误点时间
                System.out.println(this.isInterrupted());//true
                System.out.println(this.isInterrupted());//true
                System.out.println(Thread.interrupted());//true
                System.out.println(Thread.interrupted());//false
                System.out.println(this.isInterrupted());//false
            }
        };
        td.start();
        td.interrupt();
    }

结果:

三、currentThread方法

currentThread方法用于获取当前线程对象,上面的例子中我们都是使用this获取当前线程对象的。有this为什么还要currentThread方法呢?

在使用Thread子类覆盖父类run方法时,这时的this和Thread.currentThread返回值时相等的,都是指向Thread子类对象。

使用Runnable接口的方法时,我们先是构造了一个Runnable的

public static void main(String args[]) throws Exception {
        new App().test();
    }

public void test() {
        //情形1
        Thread td1 = new Thread() {
            @Override
            public void run() {
                System.out.println(this.equals(Thread.currentThread()));//true
            }
        };
        td1.start();
        //情形2
        Thread td2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(this.equals(Thread.currentThread()));//false
            }
        });
        td2.start();
        //情形3
        Thread td3 = new Thread(() -> {
            System.out.println(this.equals(Thread.currentThread()));//false
        });
        td3.start();
    }

 四、join方法

Thread类中的join方法的主要作用就是同步,它可以使得线程之间的并行执行变为串行执行。具体看代码:

    public static void main(String args[]) throws Exception {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                try {
                    for (int i = 0; i < 3; i++) {
                        System.out.println("t1");
                        Thread.sleep(1000);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        };
        Thread t2 = new Thread() {
            @Override
            public void run() {
                try {
                    for (int i = 0; i < 3; i++) {
                        System.out.println("t2");
                        Thread.sleep(1000);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        };

        t1.start();
        t1.join();
        System.out.println("t1执行完毕");
        t2.start();
        t2.join();
        System.out.println("t2执行完毕");
    }

结果:

 

程序在main线程中调用t1线程的join方法,则main线程放弃cpu控制权,并返回t1线程继续执行直到线程t1执行完毕所以结果是t1线程执行完后,才到主线程执行,相当于在main线程中同步t1线程,t1执行完了,main线程才有执行的机会。

上面注释也大概说明了join方法的作用:在A线程中调用了B线程的join()方法时,表示只有当B线程执行完毕时,A线程才能继续执行。注意,这里调用的join方法是没有传参的,join方法其实也可以传递一个参数给它的。

join方法中如果传入参数,则表示这样的意思:如果A线程中掉用B线程的join(10),则表示A线程会等待B线程执行10毫秒,10毫秒过后,A、B线程并行执行。需要注意的是,jdk规定,join(0)的意思不是A线程等待B线程0秒,而是A线程等待B线程无限时间,直到B线程执行完毕,即join(0)等价于join()。

join方法实现原理

有了上面的例子,我们大概知道join方法的作用了,那么,join方法实现的原理是什么呢?其实,join方法是通过调用线程的wait方法来达到同步的目的的。例如,A线程中调用了B线程的join方法,则相当于A线程调用了B线程的wait方法,在调用了B线程的wait方法后,A线程就会进入阻塞状态,具体看下面的源码:
 

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) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

从源码中可以看到:join方法的原理就是调用相应线程的wait方法进行等待操作的,例如A线程中调用了B线程的join方法,则相当于在A线程中调用了B线程的wait方法,当B线程执行完(或者到达等待时间),B线程会自动调用自身的notifyAll方法唤醒A线程,从而达到同步的目的。

 五、获取线程状态(getState)

一般来说,线程包括以下这几个状态:创建(new)、就绪(runnable)、运行(running)、阻塞(blocked)、计时等待(time_waiting)、等待(waiting)、消亡(dead / terminated)。一共7种状态。

 getState方法返回线程当前状态,实际上是返回一枚举类State。

public enum State {
        NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED;
    }

State状态一共有6种:创建(new)、就绪(runnable)、阻塞(blocked)、计时等待(time_waiting)、等待(waiting)、消亡(terminated)。有没有发现少了"运行(running)"状态,为什么缺少这种状态呢?这里的就绪(runnable)被看成一个复合状态,它包括两个子状态:runnable和running。

六、yield方法

Thread.yield()方法也是一个静态方法。作用是:让当前运行线程回到可运行状态,于是处于可运行状态的线程又争夺cpu的控制权,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。

七、守护线程

在Java中有两类线程:User Thread(用户线程)Daemon Thread(守护线程)。其实User Thread线程和Daemon Thread守护线程本质上来说去没啥区别的,唯一的区别之处就在虚拟机的离开:守护线程是为用户线程提供服务的,如果用户线程全部停止(死掉),那么守护线程也就丢去了存在的意义,所以守护线程会随着虚拟机的退出而被自动杀死。守护线程由于被外力强制杀死,如果守护线程正在执行一些任务,那么该任务可能无法完成

用户也可以自行的设定守护线程,方法:public final void setDaemon(boolean on);但是有几点需要注意:

1)、thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。

2)、 在Daemon线程中产生的新线程也是Daemon的。  (这一点又是有着本质的区别了:守护进程fork()出来的子进程不再是守护进程,尽管它把父进程的进程相关信息复制过去了,但是子进程的进程的父进程不是init进程,所谓的守护进程本质上说就是“父进程挂掉,init收养,然后文件0,1,2都是/dev/null,当前目录到/”)

3)、不是所有的应用都可以分配给Daemon线程来进行服务,比如读写操作或者计算逻辑。因为在Daemon Thread还没来的及进行操作时,虚拟机可能已经退出了。

实例:

public static void main(String args[])  {

        final Thread main = Thread.currentThread();
        final Thread userThread = new Thread() {
            public void run() {
                while (true) ;//第一个while(true)
            }
        };

        final Thread daemonThread = new Thread() {
            @Override
            public void run() {
                while (true) {
                    System.out.print("main线程isAlive:" + main.isAlive());
                    System.out.print("    userThread线程isAlive:" + userThread.isAlive());
                    System.out.print("    daemonThread线程isAlive:" + this.isAlive());
                    System.out.println("");
                    try {
                        Thread.sleep(1000);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        };

        userThread.start();
        daemonThread.setDaemon(true);
        daemonThread.start();
        while (true) ;//第二个while(true)
    }

这个例子中有三个线程,分别是:main函数所在线程为主线程,main函数又创建了userThread和daemonThread 两个线程。

该例子中有两个“while(true);”

1)、如果两个“while(true);”都未注释掉,main线程和userThread线程都会结束:

无限次打印……

2)、如果main线程的“while(true);”被注释掉,main线程会很快结束,而userThread线程会一直运行:

 无限次打印……

3)、如果userThread线程的“while(true);”被注释掉,userThread线程会很快结束,而mian线程会一直运行:

 无限次打印…… 

4)、如果“while(true);”都被注释掉,main和userThread线程会很快结束,这两个线程结束后,daemoneThread就被kill掉: 

  这里只打印了一次。

猜你喜欢

转载自blog.csdn.net/hong10086/article/details/82080441