多线程系列(一)--线程基础

文章目录
(一)线程的定义
(二)实现多线程的方式

  • 继承Thread类
  • 实现Runnable接口

(三)Thread常用方法介绍

  • 中断线程方法
    1. interrupt()方法
  • 静态方法{#3.2}
    1. currentThread()方法
    2. sleep()方法
    3. yield()方法
  • 对象方法
    1. isAlive()方法
    2. join()方法
    3. join(long)方法与sleep(long)方法的区别

(四)停止线程

  • 安全的终止线程
    1. 中断法+boolean变量法
    2. 抛出异常法+return法

(五)线程优先级
(六)守护线程(Daemon线程)
(七)线程的状态(线程的生命周期)

线程的定义

线程可以理解成是在进程中独立运行的子任务。

实现多线程的方式

  1. 继承Thread类;
  2. 实现Runnable接口;

继承Thread类

public class Thread implements Runnable

Thread类实现了Runnable接口,他们之间具有多态关系。

下面是一个创建线程实例,继承Thread类,并且重写run方法。

public class MyThread extends Thread {

    @Override

    public void run(){

        super.run();

        System.out.println("MyThread");

    }

    public static void main(String[]args){

        MyThread myThread=new MyThread();

        myThread.start();

        System.out.println("main running end!");

    }

}

运行结果:

main running end!

MyThread

从运行结果可以看出,myThread.run()方法执行的时间比较晚,说明在使用多线程技术时,代码运行的结果与代码执行的顺序无关。

实现Runnable接口

首先,我们来查看一下Thread.java的构造函数:


多线程系列(一)--线程基础_第1张图片
Image.png

通过实现Runnable接口,也可以创建线程,将Runnable对象通过Thread类的构造函数传进去,下面展示实例:

public class MyRunnable implements Runnable {

    @Override

    public void run() {

        System.out.println("running!");

    }

    public static void main(String[]args){

        Runnable runnable=new MyRunnable();

        Thread thread = new Thread(runnable);

        thread.start();

        System.out.println("main running end!");

    }

}

运行结果:

main running end!

running!

因为Java只支持单继承,为了避免这一局限性,可以使用实现Runnable接口的方式来实现多线程技术。

从构造函数Thread(Runnable target)可以看出,我们不光可以传入实现Runnable接口的对象,也可以传入一个Thread类的对象,因为Thread类也实现了Runnable接口,这样做可以将Thraed对象中的run()方法完全交由其他的线程来进行调用。

Thread常用方法介绍

方法名称 方法说明
public synchronized void start() 启动一个线程,Java虚拟机JVM调用run()方法
public final native boolean isAlive(); isAlive()方法是判断当前线程是否处于活动状态。活动状态是线程已经启动且尚未终止。
public void interrupt() 中断线程
public boolean isInterrupted() 测试线程Thread对象是否中断,但是不会清除中断状态,即再次调用this.isInterrupted()方法,返回true.
public final synchronized void join(long millis);
public final void join();
public final synchronized void join(long millis, int nanos);
如果线程A执行了thread.join()语句,则当前线程A等待thread线程终止之后才从thread.join()返回。两个超时方法表示,如果线程thread在给定的超时时间里没有终止,那么将会从超时方法中返回。
public static boolean interrupted() 测试当前线程是否中断,执行后会清除中断状态(中断状态会置为false),即第一次调用返回true,再次调用Thread.interrupted()方法,返回false.
public static native Thread currentThread(); 返回对当前正在执行的线程对象的引用。
public static native void sleep(long millis);
public static void sleep(long millis, int nanos)
sleep()方法是让当前线程“正在执行的线程”休眠(暂停)指定的毫秒数。
public static native void yield(); 让当前线程放弃当前的CPU资源,将CPU让给其他的任务去占用CPU执行时间。由running状态变为ready状态

中断线程方法

方法名称 方法说明
public void interrupt() 中断线程
public boolean isInterrupted() 测试线程Thread对象是否中断,但是不会清除中断状态,即再次调用this.isInterrupted()方法,返回true.
public static boolean interrupted() 测试当前线程是否中断,执行后会清除中断状态(中断状态会置为false),即第一次调用返回true,再次调用Thread.interrupted()方法,返回false.

中断可以理解为线程的一个标识位属性,它表示一个运行中线程是否被其他线程进行了中断操作。其他线程通过调用该线程的interrupt()方法来对它进行中断操作。

interrupt()方法

interrupt()方法的作用是中断线程。对象方法。

方法说明:

1.如果被中断的线程正在调用Object.wait()/Object.wait(long)/Object.wait(long,int),Thread.join()/Thread.join(long)/Thread.join(long,int),Thread.sleep(long)/Thread.sleep(long,int),那么中断状态将会被清除(中断状态置为false),并且抛出InterruptedException异常。

举例说明:

public class InterruptThread{

    public static void main(String[]args){

        try{

            SleepThread sleepThread=new SleepThread();

            BusyThread busyThread=new BusyThread();

            sleepThread.start();

            busyThread.start();

            Thread.sleep(1000);

            sleepThread.interrupt();

            busyThread.interrupt();

            System.out.println("busyThread isInterrupted="+busyThread.isInterrupted());

            System.out.println("busyThread isInterrupted="+busyThread.isInterrupted());

            System.out.println("sleepThread isInterrupted="+sleepThread.isInterrupted());

            System.out.println("sleepThread isInterrupted="+sleepThread.isInterrupted());

            Thread.currentThread().interrupt();

            System.out.println("main interrupted="+Thread.interrupted());

            System.out.println("main interrupted="+Thread.interrupted());

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

    }

    public static class SleepThread extends Thread{

        @Override

        public void run(){

            try {

                Thread.sleep(5000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        }

    }

    public static class BusyThread extends Thread{

        @Override

        public void run(){

            while (true){

            }

        }

    }

}

运行结果:

busyThread isInterrupted=true

busyThread isInterrupted=true

sleepThread isInterrupted=false

sleepThread isInterrupted=false

main interrupted=true

main interrupted=false

java.lang.InterruptedException: sleep interrupted

    at java.lang.Thread.sleep(Native Method)

    at JavaMultiThreadProgramming.InterruptThread$SleepThread.run(InterruptThread.java:31)

结果分析:

busyThread线程与main线程结果对比验证:

busyThread线程中断之后,调用Thread的对象方法isInterrupted()获取busyThread线程的中断状态为true,再次调用还是为true,说明isInterrupted()方法不会重置中断标志位。

main线程中断之后,调用Thread.interrupted()方法,第一次返回true,第二次返回false,验证第一次调用Thread.interrupted()方法之后会重置该线程的中断标志位,因此第二次调用的时候返回结果为false.

sleepThread线程与busyThread线程结果对比:

busyThread线程一直在运行,中断之后,线程正常中断,而sleepThread线程一直在睡眠,中断之后会抛出InterruptedException异常;

sleepThread线程中断之后调用interrupted()方法,两次都返回false,验证在该线程正在sleep()的时候中断线程,在抛出异常之前会先清除中断标志位,即中断标识置为false。

静态方法

currentThread()方法

currentThread()方法返回代码段正在被哪个线程调用。静态方法。
JDK中实现:

public static native Thread currentThread();

举例说明:

public class Run1 {

    public static void main(String[]args){

        System.out.println(Thread.currentThread().getName());

    }

}

运行结果:

main

结果说明:main()方法被名为main的线程调用。

再次举例说明:

public class Run1 extends Thread{

    public Run1(){

        System.out.println("Run1 printed by "+Thread.currentThread().getName());

    }

    @Override

    public void run(){

        System.out.println("run printed by "+Thread.currentThread().getName());

    }

    public static void main(String[]args){

        System.out.println("main printed by "+Thread.currentThread().getName());

        Run1 run1=new Run1();

        run1.start();

        //run1.run();

    }

}

运行结果:

main printed by main

Run1 printed by main

run printed by Thread-0

结果说明:

Run1的构造方法被main线程调用,run方法被thread-0的线程调用,说明run1.start()方法会新启一个线程。将上述代码中的run1.run()的注释取消,再次执行,得到的结果如下:

main printed by main

Run1 printed by main

run printed by main

run printed by Thread-0

从上面的结果可以看出,直接调用线程的run方法run1.run(),该方法是由main线程来执行的,并没有新启一个线程。

sleep()方法

sleep()方法是让当前线程“正在执行的线程”休眠(暂停)指定的毫秒数。“正在执行的线程”指的是this.currentThread()返回的线程。

JDK实现:

public static native void sleep(long millis) throws InterruptedException;

方法说明:

  1. 暂停线程,线程进入到waiting状态,会让出CPU。
  2. 不会放弃对象锁。
  3. 静态方法,作用在当前线程上。

举例说明:

public class Run3 extends Thread {

    @Override

    public void run(){

        try {

            System.out.println("run threadName="+this.currentThread().getName()+" begin "+System.currentTimeMillis());

            Thread.sleep(2000);

            System.out.println("run threadName="+this.currentThread().getName()+" end "+System.currentTimeMillis());

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

    }

    public static void main(String[]args) throws InterruptedException {

        Run3 run3=new Run3();

        run3.start();

        System.out.println("main "+" begin "+System.currentTimeMillis());

        System.out.println("main "+" end "+System.currentTimeMillis());

    }

}

运行结果:

main  begin 1515381264226
main  end 1515381264226

run threadName=Thread-0 begin 1515381264226
run threadName=Thread-0 end 1515381266219

结果说明:因为main线程与Run3线程是异步执行的,所以main线程可能先执行完,打印了main的begin和end,Run3线程随后执行,打印run begin和run end.

yield()方法

yield()方法让当前线程放弃当前的CPU资源,将CPU让给其他的任务去占用CPU执行时间。由running状态变为ready状态。静态方法。但是放弃的时间不确定,有可能刚刚放弃,马上又获得了CPU时间片。

JDK实现:

public static native void yield();

举例说明:

public class YieldThread extends Thread{

    @Override

    public void run(){

        long begin=System.currentTimeMillis();

        long sum=0;

        for(int i=0;i<50000000;i++){

            //Thread.yield();

            sum+=i;

        }

        long end=System.currentTimeMillis();

        System.out.println("sum =" +sum);

        System.out.println("sum cost time " +(end-begin)+" ms.");

    }

    public static void main(String[]args){

        YieldThread thread=new YieldThread();

        thread.start();

    }

}

运行结果:

sum =1249999975000000
sum cost time 47 ms.

取消掉上述代码注释后运行结果:

sum =1249999975000000
sum cost time 10332 ms

对象方法

isAlive()方法

isAlive()方法是判断当前线程是否处于活动状态。活动状态是线程已经启动且尚未终止。
在JDK中实现如下:

public final native boolean isAlive();

举例说明:

public class Run2 extends Thread {

    @Override

    public void run(){

        System.out.println("run = "+this.isAlive());

    }

    public static void main(String[]args) throws InterruptedException {

        Run2 run2=new Run2();

        System.out.println("begin == "+run2.isAlive());

        run2.start();

        System.out.println("end == "+run2.isAlive());

        Thread.sleep(1000);

        System.out.println("Thread end == "+run2.isAlive());

    }

}

运行结果:

begin == false
end == true

run = true
Thread end == false

上述结果说明:在线程run2没有执行start()方法的时候,线程还未运行,isAlive()=false,在执行start()之后,这里的结果其实是不确定的,end == true说明该线程还没有运行完成,因此isAlive=true,在睡眠1s之后,Thread end == false说明线程已经运行完成,所以isAlive==false.

join()方法

方法名称 方法说明
public final synchronized void join(long millis);
public final void join();
public final synchronized void join(long millis, int nanos);
如果线程A执行了thread.join()语句,则当前线程A等待thread线程终止之后才从thread.join()返回。两个超时方法表示,如果线程thread在给定的超时时间里没有终止,那么将会从超时方法中返回

join()方法的实现调用了wait()方法,当线程终止时,会调用线程自身的notifyAll()方法,会通知所有等待在该线程对象上的线程。JDK实现如下:

///此处A timeout of 0 means to wait forever 字面意思是永远等待,其实是等到t结束后。  
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;

            }

        }

    }

从源码可以看出:如果线程被生成了,但还未被起动,isAlive()将返回false,调用它的join()方法是没有作用的。将直接继续向下执行。

当main线程调用t.join时候,main线程会获得线程对象t的锁(wait 意味着拿到该对象的锁),调用该对象的wait(等待时间),直到该对象唤醒main线程 ,比如退出后。这就意味着main 线程调用t.join时,必须能够拿到线程t对象的锁。

举例说明:

public class JoinThread {

    public static void main(String[]args){

        Thread previous=Thread.currentThread();

        for(int i=0;i<10;i++){

            Thread thread=new Thread(new Domino(previous),String.valueOf(i));

            thread.start();

            previous=thread;

        }

    }

     public static class Domino implements Runnable{

         private Thread thread;

         public Domino(Thread thread){

             this.thread=thread;

         }

         @Override

         public void run() {

             try{

                 thread.join();

             } catch (InterruptedException e) {

             }

             System.out.println(Thread.currentThread().getName()+" terminate.");

         }

     }

}

运行结果:

0 terminate.

1 terminate.

2 terminate.

3 terminate.

4 terminate.

5 terminate.

6 terminate.

7 terminate.

8 terminate.

9 terminate.

join(long)方法与sleep(long)方法的区别

两个方法的区别主要是在对同步的处理上。

方法join(long)的功能在内部是使用wait(long)方法来实现的,所以join(long)具有释放对象锁的特点。从源码中可以看出,当执行wait(long)方法后,当前线程的锁被释放,那么其他线程就可以调用此线程的同步方法了。而Thread.sleep(long)方法却不释放锁。

停止线程

在介绍如何安全的停止线程之前,先介绍3个不好的方法,在这里只说明不好的方法的缺点,不详细举例说明。
stop()方法-已作废,强制停止线程

stop()方法在终结一个线程时不会保证线程的资源正常释放,没有给予线程完成资源释放工作的机会,可能会导致程序工作在不确定的状态下。

suspend()方法-已作废,暂停线程
resume()方法-已作废,恢复线程

suspend()/resume()方法在调用后,线程不会释放已经占有的资源(比如锁),而是占着资源进入睡眠状态,这样荣引起死锁问题。还容易出现因为线程的暂停而导致数据不同步的情况。

安全的终止线程

中断法+boolean变量法

通过这两种方式能够使线程咋终止时有机会去清理资源,而不是武断 地将线程停止,这两种方式使得线程的终止显得更加安全和优雅。

实例如下:

public class Shutdown {

    public static void main(String[]args) throws InterruptedException {

//中断法

        Runner1 one=new Runner1();

        Thread countThread=new Thread(one,"CountThread");

        countThread.start();

        TimeUnit.SECONDS.sleep(1);

        countThread.interrupt();

////boolean变量法

        Runner2 two=new Runner2();

        countThread=new Thread(two,"CountThread");

        countThread.start();

        TimeUnit.SECONDS.sleep(1);

        two.cancel();

    }

    private static class Runner1 implements Runnable{

        private long i;

        @Override

        public void run() {

            while (!Thread.currentThread().isInterrupted()){

                i++;

            }

            System.out.println("count="+i);

        }

    }

    private static class Runner2 implements Runnable{

        private long i;

        private volatile boolean on=true;

        @Override

        public void run() {

            while (on){

                i++;

            }

            System.out.println("count="+i);

        }

        public void cancel(){

            on=false;

        }

    }

}

运行结果:

count=24098615
count=511335299

从上面的结果看出,run()方法里面,while循环之外,还是会继续运行,打印count还是能够打印出来。
为了解决上述问题,还有一种方式来终止线程。

抛出异常法+return法

public class Shutdown2 {

    public static void main(String[]args) throws InterruptedException {

        Runner1 one=new Runner1();

        Thread countThread=new Thread(one,"CountThread");

        countThread.start();

        TimeUnit.SECONDS.sleep(1);

        countThread.interrupt();

    }

    private static class Runner1 implements Runnable{

        private long i=0;

        @Override

        public void run() {

            try {

                while (!Thread.currentThread().isInterrupted()){

                    System.out.println("i="+(i++));

                }

                if(Thread.currentThread().isInterrupted()){

                    System.out.println("Thread end!!!");

                   // throw new InterruptedException();

                   //return;

                }

                System.out.println("below for");

            } catch (InterruptedException e) {

                System.out.println("in catch");

                e.printStackTrace();

            }

        }

    }

}

运行结果(注释 // throw new InterruptedException();):

..................

i=174485

i=174486

i=174487

i=174488

i=174489

i=174490

Thread end!!!

below for

运行结果(去掉注释 // throw new InterruptedException()):


i=168998

i=168999

i=169000

i=169001

Thread end!!!

in catch

java.lang.InterruptedException

    at Thread.Shutdown2$Runner1.run(Shutdown2.java:23)

    at java.lang.Thread.run(Thread.java:748)

Process finished with exit code 0

结果分析:用抛出异常的方式,没有打印出"below for".

return法

上述代码,注释掉// throw new InterruptedException();取消注释return;

运行结果:

i=164759

i=164760

i=164761

i=164762

Thread end!!!

结果分析:同样没有"below for".

线程优先级

在操作系统中,线程可以划分优先级,优先级较高的线程得到的CPU资源较多,也就是CPU优先执行优先级较高的线程对象中的任务。设置线程优先级有助于帮“线程规划器”确定下一次选择哪个线程来优先执行。

设置线程的优先级使用setPriority(int)方法,默认优先级是5.在Java中,线程的优先级分为1~10这10个等级,如果小于1或者大于10,则JDK抛出异常throw new IllegalArgumentException()。设置优先级时,针对频繁阻塞(休眠或者I/O操作)的线程需要设置较高优先级,而偏重计算(需要较多CPU时间或者偏运算)的线程则设置较低的优先级,确保处理器不会被独占。在不同的JVM以及操作系统上,线程规划会存在差异,有些操作系统甚至会忽略对线程优先级的设定。

JDK中使用3个常量来预置定义优先级的值。如下:

      /**

     * The minimum priority that a thread can have.

     */

    public final static int MIN_PRIORITY = 1;

   /**

     * The default priority that is assigned to a thread.

     */

    public final static int NORM_PRIORITY = 5;

    /**

     * The maximum priority that a thread can have.

     */

    public final static int MAX_PRIORITY = 10;

线程优先级的特性:

  1. 继承性
    在Java中,线程的优先级具有继承性,比如A线程启动B线程,则B线程的优先级与A是一样的。

  2. 规则性
    线程的优先级具有一定的规则性,也就是CPU尽量将执行资源让给优先级比较高的线程。

  3. 随机性
    线程的优先级具有随机性,也就是优先级较高的线程不一定每次都先执行完。当有大量优先级高的线程和大量优先级低的线程一起执行的时候,呈现出来的是大多数的优先级高的线程先执行完,但是不是每一个高优先级的线程都比低优先级的线程先执行完。

守护线程(Daemon线程)

在Java线程中有两种线程,一种是用户线程,一种是守护线程。

Daemon线程是一种支持性线程,因为他主要被用作程序中后台调度以及支持性工作。这意味着,当一个Java虚拟机中不存在非Daemon线程的时候,Java虚拟机将会退出。可以通过调用Thread.setDaemon(true)将线程设置为Daemon线程。Daemon属性需要在启动线程之前设置,不能在启动之后设置。

典型的守护线程就是垃圾回收线程,当进程中没有非守护线程了,则垃圾回收线程也就没有存在的必要了,自动销毁。Daemon线程的作用是为其他线程的运行提供便利服务。

Daemon线程被用作完成支持性工作,但是在Java虚拟机退出时Daemon线程中的finally块并不一定会执行,实例如下:

public class DaemonThread extends Thread{

    public void run(){

        try {

            Thread.sleep(10);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }finally {

            System.out.println("DaemonThread finally run.");

        }

    }

    public static void main(String[]args){

        DaemonThread thread=new DaemonThread();

        thread.setName("DaemonThread");

        thread.setDaemon(true);

        thread.start();

    }

}

运行结果:无
结果说明:
运行上述程序,可以看到在中断或者命令提示符上没有任何输出。main线程(非Daemon线程)在启动了DaemonThread之后随着main方法执行完毕而终止,而此时Java虚拟机中已经没有非Daemon线程,虚拟机需要退出。Java虚拟机中的所有Daemon线程都需要立即终止,因此DaemonThread立即终止,但是DaemonThread中的finally块并没有执行。在构建Daemon线程时,不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑。

线程的状态(线程的生命周期)

状态名称 说明
NEW 初始状态,线程被构建,但是还没有调用start()方法
RUNNABLE 运行状态,Java线程将操作系统中的就绪和运行两种状态笼统地称为“运行中”
BLOCKED 阻塞状态,表示线程阻塞于锁
WAITING 等待状态,标识线程进入等待状态,进入该状态标识当前线程需要等待其他线程做出一些特定动作(通知或中断)
TIME_WAITING 超时等待状态,该状态不同于WAITING,它可以在指定的时间自行返回的
TERMINATED 终止状态,标识当前线程已经执行完毕

Java线程在运行的生命周期中有6种状态,在给定一个时刻,线程只能处于其中的一个状态。

状态名称 说明
NEW 初始状态,线程被构建,但是还没有调用start()方法
RUNNABLE 运行状态,Java线程将操作系统中的就绪和运行两种状态笼统地称为“运行中”
BLOCKED 阻塞状态,表示线程阻塞于锁
WAITING 等待状态,标识线程进入等待状态,进入该状态标识当前线程需要等待其他线程做出一些特定动作(通知或中断)
TIME_WAITING 超时等待状态,该状态不同于WAITING,它可以在指定的时间自行返回的
TERMINATED 终止状态,标识当前线程已经执行完毕

在Java中线程的状态分为6种

  1. 初始状态:new
    创建一个Thread对象,但还未调用start()启动线程时,线程处于初始态。
  2. 运行态:runnable
    在java中,运行态包含就绪态和运行态。
    • 就绪态

      • 该状态下的线程已经获得执行所需的所有资源,只要CPU分配执行权就可以运行。
      • 所有就绪状态的线程存放在就绪队列中。
    • 运行态

      • 获得CPU执行权,正在执行的线程。
      • 由于一个CPU同一时刻只能执行一条线程,因此每个CPU每个时刻只有一条运行态的线程。
  3. 阻塞态:blocked
    • 当一条正在执行的线程请求你某一资源失败时,就会进入阻塞态。
    • 在Java中,阻塞态专指请求锁失败时进入的状态。(进入synchronized关键字修饰的方法或代码块时,获取锁失败)
    • 由一个阻塞队列存放所有阻塞态的线程。
    • 处于阻塞态的线程会不断请求资源,一旦请求成功,就会进入就绪队列,等待 执行。
  4. 等待态:waiting
    • 当前线程调用wait/join/park函数时,当前线程就会进入等待态。
    • 也有一个等待队列存放所有等待态的线程。
    • 线程处于等待态标识它需要等待其他线程的指示才能继续运行。
    • 进入等待态的线程会释放CPU执行权,并释放资源(如锁)。
  5. 超时等待态:timed_waiting
    • 当运行中的线程调用sleep(time)/wait/join/parkNanos/parkUntil时,就会进入该状态。
    • 它和等待态一样,并不是因为请求不到资源,而是主动进入,并且进入后需要其他线程唤醒。
    • 进入该状态后会释放CPU执行权和占有的资源。
    • 与等待态的区别:到了超时时间后自动进入阻塞队列,开始竞争锁。
  6. 终止态:terminated
    • 线程执行结束后的状态。

阻塞在java.concurrent包中的Lock接口的线程是等待状态,因为java.concurrent包中的Lock接口对于阻塞的实现均使用了LockSupport类中的相关方法。

Java线程各状态之间的转换如下图所示:


多线程系列(一)--线程基础_第2张图片
Image.png

参考:
Java多线程编程核心技术
Java并发编程的的艺术

该文是我的读书笔记,对知识进行总结,帮助自己以后回顾。

你可能感兴趣的:(多线程系列(一)--线程基础)