4

Thread和Runnable两种方式的比较

  • Runnable方式可以避免Thread方式由于Java单继承特性带来的缺陷。
  • Runnable的代码可以被多个线程(Thread实例)共享,适合与多个线程处理同一资源的情况。

首先我们来看下代码:

这是实现Runnable接口的方式

class MyRunnable implements Runnable {

    private int ticketsCont = 5;

    @Override
    public void run() {
        while (ticketsCont > 0) {
            ticketsCont--;
            System.out.println(Thread.currentThread().getName() + "卖了1张票,剩余的票数为:" + ticketsCont);
        }
    }
}

public class TicketsRunnable {
    public static void main(String[] args) {

        MyRunnable myRunnable = new MyRunnable();
        Thread thread1 = new Thread(myRunnable, "窗口1");
        Thread thread2 = new Thread(myRunnable, "窗口2");
        Thread thread3 = new Thread(myRunnable, "窗口3");

        thread1.start();
        thread2.start();
        thread3.start();
    }
}

//运行结果:
//窗口1卖了1张票,剩余的票数为:4
//窗口1卖了1张票,剩余的票数为:3
//窗口1卖了1张票,剩余的票数为:2
//窗口1卖了1张票,剩余的票数为:1
//窗口1卖了1张票,剩余的票数为:0

这是继承Thread类的方式

class MyThread extends Thread {
    //火车票的总数
    private int ticketsCont = 5;
    //线程的名字
    private String name;


    public MyThread(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        while (ticketsCont > 0) {
            ticketsCont--;
            System.out.println(name + "卖了一张票,剩余的票数为:" + ticketsCont);
        }
    }
}

public class TicketsThread {
    public static void main(String[] args) {
        MyThread t1 = new MyThread("窗口1");
        MyThread t2 = new MyThread("窗口2");
        MyThread t3 = new MyThread("窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}
//运行结果:
//窗口1卖了一张票,剩余的票数为:4
//窗口1卖了一张票,剩余的票数为:3
//窗口1卖了一张票,剩余的票数为:2
//窗口1卖了一张票,剩余的票数为:1
//窗口1卖了一张票,剩余的票数为:0
//窗口3卖了一张票,剩余的票数为:4
//窗口3卖了一张票,剩余的票数为:3
//窗口3卖了一张票,剩余的票数为:2
//窗口3卖了一张票,剩余的票数为:1
//窗口3卖了一张票,剩余的票数为:0
//窗口2卖了一张票,剩余的票数为:4
//窗口2卖了一张票,剩余的票数为:3
//窗口2卖了一张票,剩余的票数为:2
//窗口2卖了一张票,剩余的票数为:1
//窗口2卖了一张票,剩余的票数为:0

运行得出结果,你就会发现两种方式的不同:

  • 继承Thread类的方式会启动三个线程,每个线程都会卖5张票。
  • 实现Runnable接口的会启动三个线程,三个线程共享一个资源。也就是三个线程卖5张票。

出现这种情况的原因是两种不同的线程实现方式本身就决定了其是否能进行资源共享:

Thread:

一个线程只能启动一次,通过Thread实现线程时,线程和线程所要执行的任务是捆绑在一起的。
也就使得一个任务只能启动一个线程,不同的线程执行的任务是不相同的,所以两个线程之间是不能共享资源的,也没必要。
当然如果一定要Thread的实现资源共享,那么可以在共享变量加上static关键字。

Runnable:

一个任务可以启动多个线程,通过Runnable方式实现的线程,实际是开辟一个线程,将任务传
递进去,由此线程执行。可以实例化多个 Thread对象,将同一任务传递进去,也就是一个任务可以
启动多个线程来执行它。这些线程执行的是同一个任务,所以他们的资源是共享。

所以说Runnable适合多个线程处理同一个资源的情况。

线程的生命周期

image

  • 创建:新建一个新的线程对象,如Thread thread=new Thread();
  • 就绪:创建了线程对象后,调用了线程的start()方法(注意:此时线程只是进入了线程队列,等待获取cpu

服务,具备了运行的条件,但是并不一定已经开始运行了)。

  • 运行处于就绪状态的线程,一旦获取了cpu资源,便进入到了运行状态,开始执行run()方法里面的逻辑。
  • 终止:线程的run()方法中的逻辑执行完毕,或者线程调用了stop()方法,线程便进入终止状态。
  • 阻塞:一个正在执行的线程在某些情况下,由于某种原因而暂时让出了cpu资源,暂停了自己的执行,便进入了阻塞状态,如调用了sleep()方法。

守护线程

Java线程有两类

  • 用户线程:运行在前台,执行具体的任务,如任务的主线程,连接网络的子线程等都是用户线程。
  • 守护线程: 运行在后台,为其他的前台线程服务

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

直接上代码:

class DaemonRunnable implements Runnable {

    private int count;

    @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(String.valueOf(filename), true);
        int count = 0;
        while (count < 999) {
            os.write(("\r\n world" + count).getBytes());
            System.out.println("守护线程" + Thread.currentThread().getName() + "向文件中写入了world" + count++);
            Thread.sleep(1000);
        }
    }
}

public class DaemonDemo {
    public static void main(String []args){
        System.out.println("进入主线程"+Thread.currentThread().getName());
        DaemonRunnable daemonRunnable=new DaemonRunnable();
        Thread thread= new Thread(daemonRunnable);
        thread.setDaemon(true);//设置为守护线程
        thread.start();
        Scanner scanner=new Scanner(System.in);
        scanner.next();
        System.out.println("退出主线程"+Thread.currentThread().getName());
    }
}

这时候通过的在Console中随便输入字符让主线程停止。

进入主线程main
进入守护线程Thread-0
守护线程Thread-0向文件中写入了world0
守护线程Thread-0向文件中写入了world1
守护线程Thread-0向文件中写入了world2
守护线程Thread-0向文件中写入了world3
11
退出主线程main

可以看到用户线程停止的时候,守护线程也停止了。

注意事项

  • setDaemon(true)必须在start()方法之前调用,否则会抛出IllegalThreadStateException异常
  • 在守护线程中产生的新线程也是守护线程
  • 不是所有的任务都可以分配给守护线程来执行。比如读写操作或者逻辑计算

使用jstack

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

jstack是一个.exe的命令行程序,在jdk安装目录的bin目录下。

我的jdk是默认安装在C盘的,所以路径是:C:\Program Files\Java\jdk1.8.0_131\bin

在bin目录下打开Terminal,输入jstack。

C:\Program Files\Java\jdk1.8.0_131\bin>jstack
Usage:
    jstack [-l] <pid>
        (to connect to running process)
    jstack -F [-m] [-l] <pid>
        (to connect to a hung process)
    jstack [-m] [-l] <executable> <core>
        (to connect to a core file)
    jstack [-m] [-l] [server_id@]<remote server IP or hostname>
        (to connect to a remote debug server)

Options:
    -F  to force a thread dump. Use when jstack <pid> does not respond (process is hung)
    -m  to print both java and native frames (mixed mode)
    -l  long listing. Prints additional information about locks
    -h or -help to print this help message

可以看到jstack是通过pid码来获取线程快照信息的。

我们可以打开任务管理器的 -> 详细服务,就可以看到当前正在运行的程序的pid码。

然后我们启动主线程开始运行,在Terminal中输入:jstack -l 4528

4528是我当前运行的程序的pid码,如果没有相应的pid码,那么会提示拒绝访问。

C:\Program Files\Java\jdk1.8.0_131\bin>jstack -l 804
2017-06-24 11:01:25
// 使用的Java虚拟机版本为Oracle的HotSpot 64位服务器版本
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.131-b11 mixed mode):

"Thread-0" #11 daemon prio=5 os_prio=0 tid=0x00000000188ca800 nid=0x2560 waiting on condition [0x000000001941f000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at Thread.demo.DaemonRunnable.writeToFile(DaemonDemo.java:32)
        at Thread.demo.DaemonRunnable.run(DaemonDemo.java:18)
        at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
        - None

"Service Thread" #10 daemon prio=9 os_prio=0 tid=0x00000000187fc800 nid=0x252c runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"C1 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x00000000187cf000 nid=0x2038 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x0000000018774800 nid=0x20c8 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x0000000018771800 nid=0x2298 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x000000001876f800 nid=0x16e0 runnable [0x0000000018e1e000]
   java.lang.Thread.State: RUNNABLE
        at java.net.SocketInputStream.socketRead0(Native Method)
        at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
        at java.net.SocketInputStream.read(SocketInputStream.java:171)
        at java.net.SocketInputStream.read(SocketInputStream.java:141)
        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 <0x00000000d6132b90> (a java.io.InputStreamReader)
        at java.io.InputStreamReader.read(InputStreamReader.java:184)
        at java.io.BufferedReader.fill(BufferedReader.java:161)
        at java.io.BufferedReader.readLine(BufferedReader.java:324)
        - locked <0x00000000d6132b90> (a java.io.InputStreamReader)
        at java.io.BufferedReader.readLine(BufferedReader.java:389)
        at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64)

   Locked ownable synchronizers:
        - None

"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x0000000017418000 nid=0x1404 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x00000000173ce800 nid=0x1b20 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000000000288d800 nid=0xf64 in Object.wait() [0x000000001871f000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000d5f08ec8> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
        - locked <0x00000000d5f08ec8> (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=0x0000000002882000 nid=0x148c in Object.wait() [0x000000001861f000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000d5f06b68> (a java.lang.ref.Reference$Lock)
        at java.lang.Object.wait(Object.java:502)
        at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
        - locked <0x00000000d5f06b68> (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=0x000000000085e800 nid=0x27d4 runnable [0x000000000215e000]
   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 <0x00000000d5f5a498> (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 <0x00000000d60ada70> (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 Thread.demo.DaemonDemo.main(DaemonDemo.java:45)

   Locked ownable synchronizers:
        - None

"VM Thread" os_prio=2 tid=0x0000000017386800 nid=0x1f68 runnable

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00000000027a7800 nid=0x2a24 runnable

"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00000000027a9000 nid=0x71c runnable

"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00000000027aa800 nid=0x16bc runnable

"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00000000027ac000 nid=0x1d24 runnable

"VM Periodic Task Thread" os_prio=2 tid=0x000000001882e000 nid=0x1e30 waiting on condition

JNI global references: 33

我就拿Thread-0这个线程的信息做解释:

Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.131-b11 mixed mode)

表示使用的是Oracle HotSpot64版本的JVM,为服务器版本,客户端版本为Client

信息解释:

"Thread-0" #11 daemon prio=5 os_prio=0 tid=0x00000000188ca800 nid=0x2560 waiting on condition [0x000000001941f000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at Thread.demo.DaemonRunnable.writeToFile(DaemonDemo.java:32)
        at Thread.demo.DaemonRunnable.run(DaemonDemo.java:18)
        at java.lang.Thread.run(Thread.java:748)
   Locked ownable synchronizers:
        - None
  • Thread-0表示这个线程的名字
  • daemon表示该线程为守护线程
  • prio=5表示线程的优先级
  • tidnid也是线程的16进制信息,结合其他指令可以很方便的定位出cpu占有率很高的线程
  • java.lang.Thread.State: TIMED_WAITING (sleeping)表示线程的状态为TIMED_WAITING,这是调用了sleep()方法产生的结果
  • 需要注意的是,如果没有加上-l参数的话,Locked ownable synchronizers:- None这一段信息是不会显示出来的,这段表示的是锁的

额外信息,None表示为没有。

至于其他的线程信息的话,有兴趣的人可以去查阅相关的资料,在这里就不去做更多的介绍。


凉皮
74 声望9 粉丝

引用和评论

0 条评论