9. 多线程 Part 2 --- 学习笔记


9.4 线程操作的相关方法

          在Java实现多线程的程序中,虽然Thread类实现了Runnable接口,但是操作线程的主要方法并不在Runnable接口中,而是在Thread类中。下面列出Thread类中的主要方法。

 

      9.4.1 取得和设置线程名称

           在Thread类中可以通过getName()方法取得线程的名称,还可以通过setName()方法设置线程的名称。

           线程的名称一般在启动线程前就设置好了,但也允许为已经运行的线程设置名称。 允许两个Thread对象有相同的名称,但应该尽量避免这种情况的方法。

  • 如果没有设置名称,系统会为其自动分配名称。   在线程操作中,如果没有为一个线程指定一个名称,则系统在使用时会为线程分配一个名称,名称的格式是: Thread-Xx

例子: 取得和设置线程的名称

class MyThread implements Runnable{
    public void run(){
        for(int i = 0; i < 3; i++){
            System.out.println(Thread.currentThread().getName() + "运行, i = " + i);
        }
    }
}
public class ThreadNameDemo{
    public static void main(String args[]){
        MyThread my = new MyThread();
        new Thread(my).start();
        new Thread(my, "线程-A").start();
        new Thread(my, "线程-B").start();
        new Thread(my).start();
        new Thread(my).start();
    }
}

运行结果:

-------------------------------------------------
Thread-0运行, i = 0
Thread-0运行, i = 1
Thread-0运行, i = 2
线程-A运行, i = 0
线程-A运行, i = 1
线程-A运行, i = 2
线程-B运行, i = 0
线程-B运行, i = 1
线程-B运行, i = 2
Thread-1运行, i = 0
Thread-1运行, i = 1
Thread-1运行, i = 2
Thread-2运行, i = 0
Thread-2运行, i = 1
Thread-2运行, i = 2

-------------------------------------------------

    从运行结果可以发现,没有设置线程名称的3个线程对象的名称都是很有规律的。分别是Thread-0、Thread-1、Thread-2。从之前讲解到的static关键字可以知道,在Thread类中必然存在一个static类型的属性,用于为线程自动命名。

范例2

class MyThread implements Runnable{           //实现Runnable接口
    public void run(){                        //覆写接口中的run()方法
        for (int i = 0; i < 3; i++){
            System.out.println(Thread.currentThread().getName() + "运行, i = " + i);
        }
    }
}
public class CurrentThreadDemo{
    public static void main(String args[]){
        MyThread my = new MyThread();        //实例化MyThread对象
        new Thread(my,"线程-A").start();   //启动一个新的线程
        my.run();                            //直接调用Mythread中的run()方法
    }
}

运行结果如下:

-------------------------------------------------
main运行, i = 0
main运行, i = 1
main运行, i = 2
线程-A运行, i = 0
线程-A运行, i = 1
线程-A运行, i = 2

-------------------------------------------------

   在以上程序中,主方法直接通过Runnable接口的子类对象调用其中的run()方法,另外一个是通过线程对象调用start()方法启动的,从结果可以发现,主方法实际上也是一个线程。

    ********在java中所有的线程都是同时启动的,哪个线程先抢到CPU资源,哪个就先运行********

  • java是多线程编程语言,所以java程序运行时也是以线程的方式运行的,那么主方法也就是一个线程main。
  • 每当使用java命令执行一个类时,实际上都会启动一个JVM,每一个JVM实际上就是在操作系统中启动了一个进程(可以通过运行命令来验证),java本身也具备了垃圾的收集机制。所以在java运行时至少会启动两个线程:  一个是main线程,另外一个就是垃圾收集线程GC!!!

      9.4.2 判断线程是否启动

            通过Thread类中的start()方法通知CPU这个线程已经准备好启动,然后等待系统给它分配CPU资源,运行此线程。

            用户可以通过Thread类中的isAlive()方法来测试线程是否已经启动并且仍然在执行。

范例:判断线程是否启动

class MyThread implements Runnable{           //实现Runnable接口
    public void run(){                        //覆写接口中的run()方法
        for (int i = 0; i < 2; i++){
            System.out.println(Thread.currentThread().getName() + "运行, i = " + i);
        }
    }
}
public class ThreadAliveDemo{
    public static void main(String args[]){
        MyThread my = new MyThread();        //实例化MyThread对象
        Thread t = new Thread(my,"线程-A");
        System.out.println("线程开始执行之前  --> " + t.isAlive());
        t.start();
        System.out.println("线程开始执行之后  --> " + t.isAlive());
        for (int i = 0; i < 2; i++){
            System.out.println("main线程运行 --> " + i);
        }
        <span style="font-size:14px;color:#ff0000;">System.out.println("代码执行之后  --> " + t.isAlive());</span>
    }
}

上面这段程序的运行结果,在代码执行之后的线程可能仍在执行,也可能已经执行完毕。

-------------------------------------------------
线程开始执行之前  --> false
线程开始执行之后  --> true
main线程运行 --> 0
main线程运行 --> 1
<span style="font-size:14px;color:#ff0000;">代码执行之后  --> true</span>
线程-A运行, i = 0
线程-A运行, i = 1

-------------------------------------------------

-------------------------------------------------
线程开始执行之前  --> false
线程开始执行之后  --> true
...
<span style="font-size:14px;color:#ff0000;">代码执行之后  --> false</span>

-------------------------------------------------

       以上代码红色部分输出结果是不确定的,有可能到最后线程已经不存活;但也有可能继续存活。 这主要看哪个线程先执行完!!!

  • 因为线程操作的不确定性,所以主线程有可能最先执行完,那么此时其他线程不会受到任何影响,并不会随着主线程的结束而结束!!!

      9.4.3 线程的强制运行

           在线程操作中,可以使用join()方法让一个线程强制运行,线程强制运行期间,其他线程无法运行,必须等待此线程完成之后才可以继续执行。

  范例: 线程的强制运行

class MyThread implements Runnable{
    public void run(){
    	for (int i = 0; i < 2; i++){
        	System.out.println(Thread.currentThread().getName() + "运行, --> " + i);
    	}
    }
}
public class ThreadJoinDemo{
    public static void main(String args[]){
        MyThread mt = new MyThread();
        Thread t = new Thread(mt, "线程-A");
        t.start();
        for (int i = 0; i < 5; i++){
            if(i > 1){
                try{
                    <span style="font-size:14px;color:#ff0000;">t.join();</span>
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
            System.out.println("Main线程运行  --> " + i);
        }
    }
}

运行结果:  主线程main必须等待线程-A完成之后才能继续执行!!

-------------------------------------------------
Main线程运行  --> 0
Main线程运行  --> 1
线程-A运行, --> 0
线程-A运行, --> 1
Main线程运行  --> 2
Main线程运行  --> 3
Main线程运行  --> 4

-------------------------------------------------
当注释掉上面程序中的红色部分,就是不强制运行“线程-A”的时候的输出结果是:
-------------------------------------------------
Main线程运行  --> 0
Main线程运行  --> 1
Main线程运行  --> 2
Main线程运行  --> 3
Main线程运行  --> 4
线程-A运行, --> 0
线程-A运行, --> 1

-------------------------------------------------


      9.4.4 线程的休眠

              可以直接使用Thread.sleep()方法来实现“让一个线程在运行时暂时的休眠”

 范例:线程的休眠

class MyThread implements Runnable{
    public void run(){
        for(int i = 0; i < 3; i++){
            try{
                Thread.sleep(5000);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行,i = " + i);
        }
    }
}
public class ThreadSleepDemo{
    public static void main(String args[]){
        MyThread mt = new MyThread();
        new Thread(mt, "线程-A").start();
    }
}

运行结果为:

-------------------------------------------------
线程-A运行,i = 0

线程-A运行,i = 1

线程-A运行,i = 2

-------------------------------------------------

   上面程序的每次输出都会间隔5000ms, 达到了延迟操作的效果。 

      9.4.5 中断线程

              当一个线程运行时,另外一个线程可以直接通过interrupt()方法中断其运行状态。 线程一旦被中断,会产生一个一个异常。。。

范例:线程的中断

class MyThread implements Runnable{
    public void run(){
        System.out.println("1、 进入run方法了");
        try {
            Thread.sleep(10000);
            System.out.println("2、 已经完成休眠");
        }catch(InterruptedException e){
            System.out.println("3、 休眠被终止了");
            return;
        }
        System.out.println("4、 run方法正常结束");
    }
}
public class ThreadInterruptDemo{
    public static void main(String args[]){
        MyThread my = new MyThread();
        Thread t = new Thread(my, "线程-A");
        t.start();
        try{
            Thread.sleep(2000);
        }catch(InterruptedException e){}
        t.interrupt();
    }
}

运行结果:

-------------------------------------------------
1、 进入run方法了

3、 休眠被终止了

-------------------------------------------------

    上面程序,一个线程启动之后进入了休眠状态,原来是要休眠10s之后再继续执行,但是主方法在线程启动之后的两秒就中断了该线程。休眠一旦中断后将执行catch中的代码。

      9.4.6 后台线程

              在Java程序中,只要前台有一个线程在运行,则整个java进程都不会消失,所以此时可以设置一个后台线程,这样即使java进程结束了,此后台线程依然会继续执行。 要想实现这样的操作,直接使用setDaemon()方法即可。

范例: 后台线程设置

import java.io.*;
class MyThread implements Runnable{
    public void run(){
        while(true){
            System.out.println(Thread.currentThread().getName() + "在运行。");  //输出线程名称
        }
    }
}
public class ThreadDaemonDemo{
    public static void main(String args[]){
        MyThread my = new MyThread();
        Thread t = new Thread(my, "线程-A");
        t.setDaemon(true);           //此线程在后台运行
        t.start();
        try{
            System.in.read();   //接收输入,使程序在此停顿,一旦接收到用户输入,main线程终止,自动结束
        }catch(IOException e){
            
        }
    }
}

运行结果:

若主线程main没有结束(用户没有输入操作),那么后台线程(守护线程)会一直运行,不断输出;如果用户输入后,主线程main会结束,由于上面程序中除了主线程main一个前台线程(用户线程)之外,没有其他的用户线程。所以JVM判断进程当中没有用户线程在运行,会结束掉所有守护线程的运行(包括垃圾收集线程GC),并且结束掉该进程!!!

   在线程类MyThread中,尽管run()方法中是死循环的方式,但是程序依然可以执行完,因为方法中的死循环已经设置成后台运行了。。。。

补充:关于用户线程和守护线程

  • 所谓守护线程就是运行在程序后台的线程,程序的主线程main(程序已开始启动该线程)不会是守护线程。
  • java虚拟机JVM在运行一个进程时,可能会同时有多个线程在运行,只有当所有的非守护线程都结束的时候,虚拟机的进程才会结束,不管在运行的线程是不是main()线程。
  • main主线程结束了(Non-Daemon thread),如果此时在运行的其他线程是daemon thread,JVM会使得这个线程停止,JVM也停下。如果此时正在运行的其他线程有非守护线程,那么必须等到所有的非守护线程结束了,JVM才会停下来。
  • 总值,必须等所有的非守护线程都运行结束了,只剩下守护线程的时候,JVM会停止所有的非守护线程,然后JVM也会自己停止。
  • 主线程main是非守护线程。
  • 后台线程(守护线程)表示的是当前任务中不是必不可少的线程,例如在main线程中启动了一个后台线程,那么当main线程执行完之后,不管后台线程是否执行完毕都会被杀死。。可以参考垃圾收集机制来理解GC也是一个守护线程!!!

      9.4.7 线程的优先级

              在java的线程操作中,所有的线程在运行前都会保持在就绪状态,那么此时,哪个线程的优先级高,哪个线程的就有可能会先被执行,如下图所示: 

                       

         在java的线程中使用setPriority()方法可以设置一个线程的优先级,在java的线程中一共有3种优先级,如下表所示:

         

范例:测试线程优先级

class MyThread implements Runnable{
    public void run(){
        for (int i = 0; i < 2; i++){
            try{
                Thread.sleep(20);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行,i = " + i);
        }
    }
}
public class ThreadPriorityDemo{
    public static void main(String args[]){
        Thread t1 = new Thread(new MyThread(), "线程-A");
        Thread t2 = new Thread(new MyThread(), "线程-B");
        Thread t3 = new Thread(new MyThread(), "线程-C");
        t1.setPriority(Thread.MIN_PRIORITY);
        t2.setPriority(Thread.MAX_PRIORITY);
        t3.setPriority(Thread.NORM_PRIORITY);
        t1.start();
        t2.start();
        t3.start();
    }
}

运行结果:

-------------------------------------------------
线程-A运行,i = 0
线程-B运行,i = 0
线程-C运行,i = 0
线程-A运行,i = 1
线程-B运行,i = 1
线程-C运行,i = 1

-------------------------------------------------


并不是线程的优先级越高就一定会先执行,哪个线程先执行将由CPU的调度决定。上面的输出结果就说了此情况。

  • 主方法的优先级是NORM_PRIORITY。可以通过getPriority()方法来获取线程的优先等级。


      9.4.8 线程的礼让

              在线程操作中,可以使用yield()方法将一个线程的操作暂时让他其他线程执行。

范例:线程的礼让

class MyThread implements Runnable{
    public void run(){
        for (int i = 0; i < 5; i++){
            System.out.println(Thread.currentThread().getName() + "运行, i = " + i);
            if (i == 3){
                System.out.println("线程礼让:");
                Thread.currentThread().yield();    //线程礼让
            }
        }
    }
}
public class ThreadYieldDemo{
    public static void main(String args[]){
        MyThread my = new MyThread();
        Thread t1 = new Thread(my, "线程-A");
        Thread t2 = new Thread(my, "线程-B");
        t1.start();
        t2.start();
    }
}

运行结果:

-------------------------------------------------
线程-A运行, i = 0
线程-A运行, i = 1
线程-A运行, i = 2
线程-A运行, i = 3
线程礼让:
线程-B运行, i = 0
线程-B运行, i = 1
线程-B运行, i = 2
线程-B运行, i = 3
线程礼让:
线程-A运行, i = 4
线程-B运行, i = 4

-------------------------------------------------

每当线程满足条件(I == 3),就会将本线程暂停,而让其他线程执行。


9.5 线程操作范例

    设计一个线程操作类,可以产生3个线程对象,并分别设置三个线程的休眠时间,具体如下所示:

  • 线程A,休眠10秒
  • 线程B,休眠20秒
  • 线程C,休眠30秒

      9.5.1 实现一    继承Thread类

class MyThread extends Thread{
    private int time;
    public MyThread(String name, int time){
        super(name);
        this.time = time;
    }
    public void run(){
        try{
            Thread.sleep(this.time);
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "线程, 休眠" + this.time + "毫秒");
    }
}
public class ExecDemo01{
    public static void main(String args[]){
        MyThread my1 = new MyThread("线程-A", 10000);
        MyThread my2 = new MyThread("线程-B", 10000);
        MyThread my3 = new MyThread("线程-C", 10000);
        my1.start();
        my2.start();
        my3.start();
    }
}

运行结果:

-------------------------------------------------
线程-A线程, 休眠10000毫秒
线程-B线程, 休眠10000毫秒
线程-C线程, 休眠10000毫秒

-------------------------------------------------

      9.5.2 实现二    实现Runnable接口

class MyThread implements Runnable{
    //private String name;
    private int time;
    /*public MyThread(String name, int time){
        this.name = name;
        this.time = time;
    }*/
    public MyThread(int time){
        this.time = time;
    }
    public void run(){
        try{
            Thread.sleep(this.time);
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        //System.out.println(this.name + "线程,休眠" + this.time + "毫秒");    //此时根本就没有调用线程的真正线程名。
        System.out.println(Thread.currentThread().getName() + "线程,休眠" + this.time +"毫秒");
    }
}
public class ExecDemo02{
    public static void main(String args[]){
        MyThread my1 = new MyThread(10000);
        MyThread my2 = new MyThread(20000);
        MyThread my3 = new MyThread(30000);
        Thread t1 = new Thread(my1, "线程-A");
        Thread t2 = new Thread(my2, "线程-B");
        Thread t3 = new Thread(my3, "线程-C");
        t1.start();
        t2.start();
        t3.start();
    }
}

运行结果:

-------------------------------------------------
线程-A线程,休眠10000毫秒

线程-B线程,休眠20000毫秒

线程-C线程,休眠30000毫秒

------------------------------------------------
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章