多线程系列(一)--线程基础
文章目录
(一)线程的定义
(二)实现多线程的方式
- 继承Thread类
- 实现Runnable接口
(三)Thread常用方法介绍
- 中断线程方法
- interrupt()方法
- 静态方法{#3.2}
- 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的构造函数:
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;
方法说明:
- 暂停线程,线程进入到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块中的内容来确保执行关闭或清理资源的逻辑。
线程的状态(线程的生命周期)
状态名称 | 说明 |
---|---|
NEW | 初始状态,线程被构建,但是还没有调用start()方法 |
RUNNABLE | 运行状态,Java线程将操作系统中的就绪和运行两种状态笼统地称为“运行中” |
BLOCKED | 阻塞状态,表示线程阻塞于锁 |
WAITING | 等待状态,标识线程进入等待状态,进入该状态标识当前线程需要等待其他线程做出一些特定动作(通知或中断) |
TIME_WAITING | 超时等待状态,该状态不同于WAITING,它可以在指定的时间自行返回的 |
TERMINATED | 终止状态,标识当前线程已经执行完毕 |
Java线程在运行的生命周期中有6种状态,在给定一个时刻,线程只能处于其中的一个状态。
状态名称 | 说明 |
---|---|
NEW | 初始状态,线程被构建,但是还没有调用start()方法 |
RUNNABLE | 运行状态,Java线程将操作系统中的就绪和运行两种状态笼统地称为“运行中” |
BLOCKED | 阻塞状态,表示线程阻塞于锁 |
WAITING | 等待状态,标识线程进入等待状态,进入该状态标识当前线程需要等待其他线程做出一些特定动作(通知或中断) |
TIME_WAITING | 超时等待状态,该状态不同于WAITING,它可以在指定的时间自行返回的 |
TERMINATED | 终止状态,标识当前线程已经执行完毕 |
在Java中线程的状态分为6种
- 初始状态:new
创建一个Thread对象,但还未调用start()启动线程时,线程处于初始态。 - 运行态:runnable
在java中,运行态包含就绪态和运行态。-
就绪态
- 该状态下的线程已经获得执行所需的所有资源,只要CPU分配执行权就可以运行。
- 所有就绪状态的线程存放在就绪队列中。
-
运行态
- 获得CPU执行权,正在执行的线程。
- 由于一个CPU同一时刻只能执行一条线程,因此每个CPU每个时刻只有一条运行态的线程。
-
- 阻塞态:blocked
- 当一条正在执行的线程请求你某一资源失败时,就会进入阻塞态。
- 在Java中,阻塞态专指请求锁失败时进入的状态。(进入synchronized关键字修饰的方法或代码块时,获取锁失败)
- 由一个阻塞队列存放所有阻塞态的线程。
- 处于阻塞态的线程会不断请求资源,一旦请求成功,就会进入就绪队列,等待 执行。
- 等待态:waiting
- 当前线程调用wait/join/park函数时,当前线程就会进入等待态。
- 也有一个等待队列存放所有等待态的线程。
- 线程处于等待态标识它需要等待其他线程的指示才能继续运行。
- 进入等待态的线程会释放CPU执行权,并释放资源(如锁)。
- 超时等待态:timed_waiting
- 当运行中的线程调用sleep(time)/wait/join/parkNanos/parkUntil时,就会进入该状态。
- 它和等待态一样,并不是因为请求不到资源,而是主动进入,并且进入后需要其他线程唤醒。
- 进入该状态后会释放CPU执行权和占有的资源。
- 与等待态的区别:到了超时时间后自动进入阻塞队列,开始竞争锁。
- 终止态:terminated
- 线程执行结束后的状态。
阻塞在java.concurrent包中的Lock接口的线程是等待状态,因为java.concurrent包中的Lock接口对于阻塞的实现均使用了LockSupport类中的相关方法。
Java线程各状态之间的转换如下图所示:
Image.png
参考:
Java多线程编程核心技术
Java并发编程的的艺术
该文是我的读书笔记,对知识进行总结,帮助自己以后回顾。