Java多线程基础(二)
前言
上一篇文章《Java多线程基础(一)》整理了线程的基本概念和常用实现,本篇继续整理一下线程常用API。
线程的命名与获取
多线程的运行状态是不确定的,线程的命名可以让我们在开发中通过获取线程名字进行相关操作。Thread
类中命名方式有以下两种:
- 通过
Thread
类的构造函数
,示例:
Runnable mr = new Runnable() {
@Override
public void run() {}
};
//将自定义线程名"myRunnable"作为构造参数传入
Thread myRunnable = new Thread(mr,"myRunnable");
- 通过
Thread
类的setName()
方法,示例:
Thread t1 = new Thread(mr);
//调用线程的setName方法
t1.setName("myThread-1");
获取线程的名字可以使用线程的getName()
方法,我们可以搭配currentThread()
方法,获取当前执行线程的名字,比如:
Thread.currentThread().getName();
join
join()
方法,作用是把指定线程加入到当前线程,将原本交替执行的两个线程合并为顺序执行,当前线程会在指定线程执行结束后再执行。如在A线程
的线程执行体中调用B线程
的join()
方法,直到B线程
执行完毕,A线程
才能继续执行。
join()
方法有三种重载方式:
- join():等待加入的线程执行完成。
- join(long millis):等待加入的线程指定毫秒时间,若在此时间内,加入的线程还未执行结束,则不会继续等待。
- join(long millis , int nanos):等待加入的线程指定毫秒加微秒时间,若在此时间内,加入的线程还未执行结束,则不会继续等待。
示例:
public class ThreadDemo {
public static void main(String[] args) {
for (int i=0;i<=5;i++){
System.out.println(Thread.currentThread().getName()+"---"+i);
if(i==3){
//通过匿名内部类实现Runnable接口
Runnable myRunnable = new Runnable() {
@Override
public void run() {
for (int i=0;i<=3;i++ ){
System.out.println(Thread.currentThread().getName()+"--"+i);
}
}
};
//使用myRunnable创建线程并调用start
Thread mr = new Thread(myRunnable);
Thread mr2 = new Thread(myRunnable,"mr2");
mr.start();
mr2.start();
try {
//在main线程执行体中调用mr线程的join()方法,main停止执行,需要等待mr线程执行完后才能继续执行
mr.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
输出结果:
main--0
main--1
main--2
main--3 //i=3时,调用mr线程的join方法,main线程停止
mr--0
mr2--0 //mian线程停止,但是mr2线程还是正常交替执行
mr--1
mr2--1
mr--2
mr2--2
mr--3
mr2--3
main--4 //mr线程执行完后,main线程恢复
main--5
sleep
sleep()
方法,作用是让当前正在执行的线程暂停指定的时间,并进入阻塞状态
。在其睡眠的时间段内,由于并未处于就绪状态
,即使此时系统中没有任何其他可执行的线程,其也不会得到执行的机会;睡眠时间结束后则进入就绪状态
,等待CPU
调度执行。
sleep()
方法有两种重载方式:
- static void sleep(long millis):让当前正在执行的线程暂停
millis
毫秒,并进入阻塞状态
。 - static void sleep(long millis , int nanos):让当前正在执行的线程暂停
millis
毫秒加nanos
微秒,并进入阻塞状态
。
sleep()
方法常用来暂停线程执行:
public static void main(String[] args) {
//内部匿名类实现Runnable接口
Runnable mr = new Runnable() {
@Override
public void run() {
for (int i=0;i<=5;i++){
if(i==3){
try {//循环到i=3时,阻塞1秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"--"+i);
}
}
};
//创建线程进入就绪状态
Thread myRunnable = new Thread(mr,"myRunnable");
myRunnable.start();
for (int i=0;i<=10;i++){
System.out.println(Thread.currentThread().getName()+"--"+i);
}
}
输出结果:
main--0
myRunnable--0
main--1
myRunnable--1
main--2
myRunnable--2 //可以看到main线程i=3之前正常交叉执行,i=3时进入阻塞状态
main--3
main--4
main--5
main--6
main--7
main--8
main--9
main--10
myRunnable--3 //睡眠时间结束,main就绪,CPU调度继续执行
myRunnable--4
myRunnable--5
守护线程(Daemon Thread)
在Java
中线程可分为两类:User Thread
(用户线程)、Daemon Thread
(守护线程)。其中守护线程
的存在主要是为用户线程
提供服务,如JVM
中的垃圾回收线程(GC
),两者在构造和使用上几乎没有区别,不同处在于生命周期,当所有用户线程都进入死亡状态
后,守护进程
就会随着JVM
一起停止运行(毕竟服务的对象都没有了,守护线程
也没必要存在了)。
设置守护线程示例:
Thread myDaemon = new Thread();
// 设置 myDaemon 为 守护线程
myDaemon.setDaemon(true);
// 验证当前线程是否为守护线程,返回 true 则为守护线程
myDaemon.isDaemon();
需要注意的是setDaemon()
必须在线程的start()
方法之前执行,否则会抛出IllegalThreadStateException
异常,你不能把正在运行的常规线程设置为守护线程;另外,在守护线程中产生的新线程也会是守护线程。
守护线程死亡示例:
class UserThread implements Runnable{//用户线程
@Override
public void run() {
for (int i=0;i<=5;i++){
System.out.println("用户线程开始执行---"+i);
}
}
}
class DaemonThread implements Runnable{//守护线程
@Override
public void run() {
for (int i=0;i<=5;i++){
System.out.println("守护线程开始执行---"+i);
try {
//这里让守护线程暂停1毫秒,让用户线程可以先执行完成
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class DaemonDemo {
public static void main(String[] args) {
Runnable userThread = new UserThread();
Runnable daemonThread = new DaemonThread();
Thread t1 = new Thread(userThread);
Thread t2 = new Thread(daemonThread);
t1.start();
//将t2设置为守护线程
t2.setDaemon(true);
t2.start();
}
}
输出结果:
用户线程开始执行---0
守护线程开始执行---0 //守护线程执行了一次
用户线程开始执行---1
用户线程开始执行---2
用户线程开始执行---3
用户线程开始执行---4
用户线程开始执行---5 //用户线程执行完成
Process finished with exit code 0
从上面输出结果我们可以看出,当所有用户线程都执行完成后,JVM会直接停止运行,不会判断是否还有守护线程未执行完成。
线程优先级
每个线程在执行时都具有一定的优先级,优先级高的线程具有较多的执行机会。线程默认的优先级继承自创建它的线程,main
线程默认具有普通优先级(5)。
设置线程优先级:setPriority(int priorityLevel)
,参数priorityLevel
范围在1-10之间,常用的有如下三个静态常量值:
MAX_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5
获取线程优先级:getPriority()
使用示例:
public class PriorityDemo {
public static void main(String[] args) {
Runnable myRunnable = new Runnable() {
@Override
public void run() {
for (int i=0;i<=6;i++){
System.out.println(Thread.currentThread().getName()+"执行--"+i);
}
}
};
Thread t1 = new Thread(myRunnable,"线程1");
Thread t2 = new Thread(myRunnable,"线程2");
//这里给线程1设置优先级10,线程2继承main线程的默认优先级5
t1.setPriority(Thread.MAX_PRIORITY);
//输出2个线程的优先级
System.out.println("线程1优先级:"+t1.getPriority());
System.out.println("线程2优先级:"+t2.getPriority());
t1.start();
t2.start();
}
}
输出结果:
线程1优先级:10
线程2优先级:5
线程1执行--0
线程2执行--0
线程1执行--1
线程2执行--1
线程1执行--2
线程1执行--3
线程1执行--4
线程1执行--5
线程1执行--6
线程2执行--2
线程2执行--3
线程2执行--4
线程2执行--5
线程2执行--6
通过以上结果可以验证,具有较高优先级的线程对象仅表示此线程具有较多的执行机会,而非优先执行。
yeild
yield()
方法,使当前执行的线程让出CPU
资源,进入就绪状态
,让其他拥有相同优先级的线程获取CPU
执行的机会;因为调用方法的线程是就绪状态
,所以并不能控制让出CPU
的时间,可能下一刻被CPU
调用的线程还会是它。示例:
class Thread1 implements Runnable{
@Override
public void run() {
for (int i=0;i<=5;i++){
//调用yield(),尝试让出cpu
Thread.yield();
System.out.println(Thread.currentThread().getName()+"--"+i);
}
}
}
class Thread2 implements Runnable{
@Override
public void run() {
for (int i=0;i<=5;i++){
System.out.println(Thread.currentThread().getName()+"--"+i);
}
}
}
public class YieldDemo {
public static void main(String[] args) {
Runnable mr1 = new Thread1();
Runnable mr2 = new Thread2();
Thread t1 = new Thread(mr1);
Thread t2 = new Thread(mr2);
Thread t3 = new Thread(mr2);
t1.start();
t2.start();
t3.start();
}
}
输出结果: //多次运行,结果都还是交替执行
Thread-0--0
Thread-1--0
Thread-1--1
Thread-1--2
Thread-1--3
Thread-1--4
Thread-2--0
Thread-1--5
Thread-0--1
Thread-2--1
Thread-0--2
Thread-2--2
Thread-0--3
Thread-2--3
Thread-0--4
Thread-2--4
Thread-0--5
Thread-2--5
通过以上结果可以验证,yield()
方法有时并不能达到让步的效果,因为让步的线程还有可能被线程调度程序再次选中。