美文网首页
细说多线程之Thread VS Runnable

细说多线程之Thread VS Runnable

作者: Hey_Shaw | 来源:发表于2018-04-16 11:31 被阅读75次

    线程创建的两种方式

    继承 Thread 类:

    class MyThread extends Thread{
        
        ....
    
        @Override 
        public void run(){
            .....
        }
    }
    
    MyThread mt = new MyThread();   // 创建线程
    mt.start();                     // 启动线程 - 调用start方法,线程进入线程队列中,等待CPU服务,一旦获取到CPU的时间片,就会转到run方法中执行,线程启动。run方法执行完,线程消亡。
    

    实现Runnable接口:

    class MyThread implements Runnable{
        
        ....
    
        @Override 
        public void run(){
            .....
        }
    }
    
    MyThread mt = new MyThread();   
    Thread td = new Thread(mt);     // 创建线程
    td.start();                         // 启动线程
    
    • Runnable方式可以避免Thread方式由于Java单继承特性带来的缺陷

    • Runnable代码可以被多个线程(Thread实例)共享,适合于多个线程处理同一资源的情况

    /**
     * Thread模拟火车站卖票
     * 
     * @author Administrator
     *
     */
    public class TicketsThread {
    
        public static void main(String[] args) {
            
            // 创建三个线程,模拟三个窗口卖票
            MyThread mt1 = new MyThread("窗口1");
            MyThread mt2 = new MyThread("窗口2");
            MyThread mt3 = new MyThread("窗口3");
    
            // 启动三个线程,即窗口开始卖票
            mt1.start();
            mt2.start();
            mt3.start();
        }
    }
    
    class MyThread extends Thread {
    
        private int ticketsCont = 5; // 一共5张火车票
        private String name; // 窗口,也即是线程的名字
    
        public MyThread(String name) {
            this.name = name;
        }
    
        @Override
        public void run() {
            while (ticketsCont > 0) {
                ticketsCont--; // 如果还有票就卖掉一张
                System.out.println(name + "卖了一张票,剩余票数为:" + ticketsCont);
            }
        }
    
    }
    
    /**
     * Runnable模拟火车站卖票
     * 
     * @author Administrator
     *
     */
    public class TicktsRunnable {
    
        public static void main(String[] args) {
            
            // 创建三个线程,模拟三个窗口卖票(共享一个Runnable对象)
            MyRunnable mr = new MyRunnable();
            Thread th1 = new Thread(mr,"窗口1") ;
            Thread th2 = new Thread(mr,"窗口2") ;
            Thread th3 = new Thread(mr,"窗口3") ;
            
            /* 不共享
            MyRunnable mr1 = new MyRunnable();
            MyRunnable mr2 = new MyRunnable();
            MyRunnable mr3 = new MyRunnable();
            Thread th1 = new Thread(mr1,"窗口1") ;
            Thread th2 = new Thread(mr2,"窗口2") ;
            Thread th3 = new Thread(mr3,"窗口3") ;
            */
    
    
            // 启动三个线程,即窗口开始卖票
            th1.start();
            th2.start();
            th3.start();
        }
    
    }
    
    class MyRunnable implements Runnable {
    
        private int ticketsCont = 5; // 一共5张火车票
    
        @Override
        public void run() {
            while (ticketsCont > 0) {
                ticketsCont--; // 如果还有票就卖掉一张
                System.out.println(Thread.currentThread().getName() + "卖了一张票,剩余票数为:" + ticketsCont);
            }
        }
    
    }
    

    线程的生命周期

    003.png

    创建:新建一个线程对象,如 Thread thd = new Thread()

    就绪状态:创建了线程对象之后,调用了线程的start()方法(注意:此时线程只是进入了线程队列,等待获取CPU服务,具备了运行的条件,但并不一定已经开始运行了)

    运行:处于就绪状态的线程,一旦获取了CPU资源,便进入到运行状态,开始执行run()方法里面的逻辑。

    终止:线程的run()方法执行完毕,或者线程调用了stop()方法(已淘汰),线程便进入终止状态

    阻塞:一个正在执行的线程在某些情况下,由于某种原因而暂时让出CPU资源,暂停了自己的执行,便进入了阻塞状态,如调用了sleep()方法。

    004.png

    守护线程

    用户线程:运行在前台,执行具体的任务。程序的主线程、连接网络的子线程等都是用户线程。

    守护线程:运行在后台,为其他前台线程服务。
    特点:一旦所有用户线程都结束运行,守护线程会随JVM一起结束工作。
    应用:数据库连接池中的监测线程
    JVM虚拟机启动后的监测线程
    最常见的守护线程:垃圾回收线程

    如何设置守护线程:

    可以通过调用Thread类的setDaemon(true)方法来设置当前的线程为守护线程
    注意:
    - setDaemon(true)必须在start()方法之前调用,否则会抛出IllegalThreadStateException异常
    - 在守护线程中产生的新线程也是守护线程
    - 不是所有的任务都可以分配给守护线程来执行,比如读写操作或者计算逻辑(一旦所有的用户线程都退出运行,守护线程失去存在的必要,随JVM一起结束工作,如果在守护线程中一些读写操作执行到一半,所有用户线程退出,守护线程结束,读写操作未进行完,程序崩溃)

    005.png
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.OutputStream;
    import java.util.Scanner;
    
    /**
     * 模拟场景:两个线程,一个主线程,一个守护线程; 守护线程很长时间内不断往文件写数据,主线程阻塞等待来自键盘的输入,
     * 一旦主线程获取用户的输入,阻塞结束,主线程继续运行直至结束。 一旦主线程结束,用户线程则消亡。这是及时数据未写完,守护线程也会随虚拟机一起结束运行。
     * 
     * @author Administrator
     *
     */
    public class DaemonThread {
    
        public static void main(String[] args) {
    
            System.out.println("程序进入主线程" + Thread.currentThread().getName());
    
            DaemonRunnable dr = new DaemonRunnable();
    
            Thread tr = new Thread(dr);
            tr.setDaemon(true);
            tr.start();
    
            Scanner sc = new Scanner(System.in);
            sc.next();
    
            System.out.println("程序退出主线程" + Thread.currentThread().getName());
    
        }
    
    }
    
    class DaemonRunnable implements Runnable {
    
        @Override
        public void run() {
    
            System.out.println("程序进入守护现场" + Thread.currentThread().getName());
    
            try {
                writeToFile();
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            System.out.println("退出守护现场" + Thread.currentThread().getName());
    
        }
    
        private void writeToFile() throws Exception {
            File fileName = new File("E:" + File.separator + "Daemon.txt");
            OutputStream os = new FileOutputStream(fileName, true);
    
            int count = 0;
            while (count < 999) {
                os.write(("\r\nword" + count).getBytes());
                System.out.println("守护线程" + Thread.currentThread().getName() + "向文件中写入了word" + count++);
                Thread.sleep(1000);
            }
    
        }
    }
    
    

    jmap.exe :生成堆快照

    jstat.exe :监测虚拟机运行状态,可以查看JVM中类加载情况,所持有的情况

    jconsole.exe:界面化工具,和jstat一样,也是用来监测虚拟机的运行状态

    jstack

    • 作用:生成JVM当前时刻线程的快照(threaddump,即当前进程中所有线程的信息)
    • 目的:帮助定位程序问题出现的原因,如长时间停顿、CPU占用率过高等。

    线程状态。 线程可以处于以下状态之一:

    • NEW
      尚未启动的线程处于此状态。
    • RUNNABLE
      在Java虚拟机中执行的线程处于此状态。
    • BLOCKED
      被阻塞等待监视器锁定的线程处于此状态。
    • WAITING
      正在等待另一个线程执行特定动作的线程处于此状态。
    • TIMED_WAITING
      正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
    • TERMINATED
      已退出的线程处于此状态。

    一个线程可以在给定时间点处于一个状态。 这些状态是不反映任何操作系统线程状态的虚拟机状态。

    $ jstack -l 58860       // jstack -l pid 
    2018-04-14 14:18:08 // 当前线程快照生成的时间
    Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.144-b01 mixed mode):   // Eclipse运行时使用的JRE的类型和版本(HotSpot(TM) 64-Bit),虚拟机分为服务器端(Server)版本和客户端(Client)版本
    
    // daemon表示是守护线程,tid nid 标识线程的参数,都是16进制的,结合top -h指令可以很方便的定位CPU占用率很高的线程
    "Thread-0" #10 daemon prio=5 os_prio=0 tid=0x000000001adaf000 nid=0xe56c waiting on condition [0x000000001b87f000]
       java.lang.Thread.State: TIMED_WAITING (sleeping)   // 线程状态 - 阻塞状态
            at java.lang.Thread.sleep(Native Method)   // sleep()方法
            at com.thread.imooc.DaemonRunnable.writeToFile(DaemonThread.java:61)
            at com.thread.imooc.DaemonRunnable.run(DaemonThread.java:44)
            at java.lang.Thread.run(Thread.java:748)
    
       Locked ownable synchronizers:   // 告诉我们当前线程是否处于线程块内
            - None  // 没有
    
    "Service Thread" #9 daemon prio=9 os_prio=0 tid=0x000000001ace2800 nid=0xe460 runnable [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
       Locked ownable synchronizers:
            - None
    
    "C1 CompilerThread2" #8 daemon prio=9 os_prio=2 tid=0x000000001acd1000 nid=0xe474 waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
       Locked ownable synchronizers:
            - None
    
    "C2 CompilerThread1" #7 daemon prio=9 os_prio=2 tid=0x000000001acce000 nid=0xe520 waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
       Locked ownable synchronizers:
            - None
    
    "C2 CompilerThread0" #6 daemon prio=9 os_prio=2 tid=0x000000001accb800 nid=0xd95c waiting on condition [0x0000000000000000]   // 调用JITing,实时编译装卸class
       java.lang.Thread.State: RUNNABLE
    
       Locked ownable synchronizers:
            - None
    
    "Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000000001acca000 nid=0xe540 waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
       Locked ownable synchronizers:
            - None
    
    "Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x0000000019a7e000 nid=0xe580 runnable [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE   // 负责分发内部事件
    
       Locked ownable synchronizers:
            - None
    
    "Finalizer" #3 daemon prio=8 os_prio=1 tid=0x0000000019a48000 nid=0xe578 in Object.wait() [0x000000001ac7f000]
       java.lang.Thread.State: WAITING (on object monitor)   //  负责调用Finalizer方法
            at java.lang.Object.wait(Native Method)
            - waiting on <0x00000000d6e08f20> (a java.lang.ref.ReferenceQueue$Lock)
            at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
            - locked <0x00000000d6e08f20> (a java.lang.ref.ReferenceQueue$Lock)
            at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
            at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)
    
       Locked ownable synchronizers:
            - None
    
    "Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x0000000019a43800 nid=0xe574 in Object.wait() [0x000000001ab7f000]   // 负责处理引用
       java.lang.Thread.State: WAITING (on object monitor)
            at java.lang.Object.wait(Native Method)
            - waiting on <0x00000000d6e06ba8> (a java.lang.ref.Reference$Lock)
            at java.lang.Object.wait(Object.java:502)
            at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
            - locked <0x00000000d6e06ba8> (a java.lang.ref.Reference$Lock)
            at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
    
       Locked ownable synchronizers:
            - None
    
    "main" #1 prio=5 os_prio=0 tid=0x0000000000b71000 nid=0xe174 runnable [0x0000000004d2f000]
       java.lang.Thread.State: RUNNABLE   // 主线程
            at java.io.FileInputStream.readBytes(Native Method)
            at java.io.FileInputStream.read(FileInputStream.java:255)
            at java.io.BufferedInputStream.read1(BufferedInputStream.java:284)
            at java.io.BufferedInputStream.read(BufferedInputStream.java:345)
            - locked <0x00000000d6e5a1b8> (a java.io.BufferedInputStream)
            at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
            at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
            at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
            - locked <0x00000000d6ed7398> (a java.io.InputStreamReader)
            at java.io.InputStreamReader.read(InputStreamReader.java:184)
            at java.io.Reader.read(Reader.java:100)
            at java.util.Scanner.readInput(Scanner.java:804)
            at java.util.Scanner.next(Scanner.java:1369)
            at com.thread.imooc.DaemonThread.main(DaemonThread.java:28)
    
       Locked ownable synchronizers:
            - None
    
    "VM Thread" os_prio=2 tid=0x0000000019a3c800 nid=0xe59c runnable  // 虚机内部线程,在Jconsole下看不见
    
    "GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000000b87800 nid=0xe5bc runnable
    
    "GC task thread#1 (ParallelGC)" os_prio=0 tid=0x0000000000b89800 nid=0xe558 runnable
    
    "GC task thread#2 (ParallelGC)" os_prio=0 tid=0x0000000000b8b000 nid=0xe55c runnable
    
    "GC task thread#3 (ParallelGC)" os_prio=0 tid=0x0000000000b8c800 nid=0xe52c runnable
    
    "VM Periodic Task Thread" os_prio=2 tid=0x000000001aced000 nid=0xe470 waiting on condition  // 虚机内部线程,在Jconsole下看不见
    
    JNI global references: 6
    

    相关文章

      网友评论

          本文标题:细说多线程之Thread VS Runnable

          本文链接:https://www.haomeiwen.com/subject/jshkkftx.html