您的位置:首页 > 其它

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

2018-01-09 15:49 155 查看
文章目录

(一)线程的定义

(二)实现多线程的方式

继承Thread类

实现Runnable接口

(三)Thread常用方法介绍

中断线程方法

interrupt()方法

静态方法

currentThread()方法

sleep()方法

yield()方法

对象方法

isAlive()方法

join()方法

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

(四)停止线程

安全的终止线程

中断法+boolean变量法

抛出异常法+return法

(五)线程优先级

(六)守护线程(Daemon线程)

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

线程的定义

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

实现多线程的方式

继承Thread类;

实现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的构造函数:



通过实现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
1389d
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;


方法说明:

暂停线程,线程进入到waiting状态,会让出CPU。

不会放弃对象锁。

静态方法,作用在当前线程上。

举例说明:

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;


线程优先级的特性:

继承性

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

规则性

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

随机性

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

守护线程(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块中的内容来确保执行关闭或清理资源的逻辑。

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

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线程各状态之间的转换如下图所示:



参考:

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

该文是我的读书笔记,对知识进行总结,帮助自己以后回顾。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: