为什么使用多线程
可以最大限度地利用CPU的空闲时间来处理其它任务。异步处理不同的任务,提高任务处理效率。
线程的五种状态
1、新建状态(New):线程对象创建后,就进入新建状态。
2、就绪状态(Runnable):就绪状态又称可执行状态,线程被创建后通过调用start()方法,从而启动线程。就绪状态的线程,随时有可能被CPU调度运行。
3、运行状态(Running):线程获取CPU权限进行执行。只有就绪状态的线程才能进入运行状态。
4、阻塞状态(Blocked):线程因为某种原因放弃CPU使用权,停止运行。直到线程进入就绪状态,才可以再到运行状态。
阻塞状态三种情况:
(1)、等待阻塞:通过调用线程的wait()方法,让线程等待某工作完成
(2)、同步阻塞:线程获取同步锁synchronized同步锁失败(因为锁正在被其它线程使用),进入同步阻塞。
(3)、其它阻塞:通过调用线程的sleep()、join()或发出I/O请求,线程进入阻塞状态。当sleep()状态超时、join()等待终止或超时、或者I/O处理完毕时,线程重新进入就休状态。
5、死亡状态(Dead):线程正常直行完成或者因为异常原因退出run()方法,该线程生命周期结束。
通过Thread和Runnable创建线程
Java的JDK开发包中,已经自带了对多线程技术的支持,我们可以很方便的进行多线程编程。
实现多线程编程的方式主要有两种,一种是继承Thread类,另一种就是实现Runnable接口。而Thread和Runnable的关系就是Thread类实现了Runnable接口:
public class Thread implements Runnable {}
1、Runnable实现
java.lang.Runnable是一个接口,里面只定义了run()抽象方法。如果要实现多线程,可以实现Runnable接口,然后通过Thread thread = new Thread(new Xxx()),其中Xxx是实现Runnable接口的类。
public interface Runnable {
public abstract void run();
}
Runnable方式实现多线程
public class RunnableTest implements Runnable{
int num = 10;
@Override
public void run() {
for(int i=0;i<20;i++){
if(this.num > 0){
System.out.println(Thread.currentThread().getName()+" num:" +this.num-- );
}
}
}
}
class Test{
public static void main(String[] args){
RunnableTest runnable = new RunnableTest();
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
Thread thread3 = new Thread(runnable);
thread1.start();//只有start()后才进入就绪状态
thread2.start();
thread3.start();
}
}
运行结果
- Thread-0 num:10
- Thread-0 num:7
- Thread-2 num:8
- Thread-1 num:10
- Thread-1 num:9
- Thread-1 num:4
- Thread-1 num:3
- Thread-1 num:2
- Thread-1 num:1
- Thread-2 num:5
- Thread-0 num:6
结论:三个线程共享num变量,共同对num相减十次。这种情况也是出现“非线程安全的”原因,两个线程可能同时获取了同一变量值,同时进行操作,比如上面的线程0和线程1都打印了10。
2、Thread实现
java.lang.Thread是一个类,实现了Runnable接口。如果要实现多线程,需要继承Thread类,然后通过创建实现类对象来启动线程。
Thread方式实现多线程
class ThreadTest extends Thread{
int num = 10;
@Override
public void run() {
for(int i=0;i<20;i++){
if(this.num > 0){
System.out.println(this.getName() + " num:" + this.num--);
}
}
}
}
class Test{
public static void main(String[] args){
ThreadTest threadTest1 = new ThreadTest();
ThreadTest threadTest2 = new ThreadTest();
ThreadTest threadTest3 = new ThreadTest();
threadTest1.start();
threadTest2.start();
threadTest3.start();
}
}
运行结果
- Thread-0 num:10
- Thread-0 num:9
- Thread-0 num:8
- Thread-0 num:7
- Thread-0 num:6
- Thread-2 num:10
- Thread-2 num:9
- Thread-1 num:10
- Thread-1 num:9
- Thread-0 num:1
- ....
结论:通过继承Thread类创建的线程,每个线程直接不会共享变量。每个线程都会各自对num进行10次相减。
实际上因为Thread实现了Runnable接口,所以我们也可以使用new Thead(theadTest)形式来启动线程,这样得到结果和实现Runnable接口结果时一样的,因为所有线程都共享了同一个theadTest的num变量。
还有一个需要注意的点,就是如果多次调用start()方法,则会抛出异常:Exception in thread "main" java.lang.IllegalThreadStateException。
Thread对象交由其它线程执行
Thread的构造方法可以接收实现了Runnable接口的线程,而Thread类是Runnable接口的实现类,所以我们可以将一个Thread对象交由另一个线程执行,也就是另一个线程指向这个Thread实现类的run方法。
在说明这个问题前我们先介绍下Thread类的介个方法:
- currentThread:返回当前代码块被哪个线程执行。
- getName:返回当前线程名称。
- isAlive():判断当前线程是否处于活动状态,活动状态是指线程已经启动并且还没有执行完成(就绪状态、运行状态和阻塞状态)。
下面是将线程1直接交由线程2来执行:
public class ThreadDemo {
public static void main(String[] args) {
//创建线程1
ThreadTest thread1 = new ThreadTest();
thread1.setName("rename thread1");
//创建线程2,并将线程1传递给线程2
Thread thread2 = new Thread(thread1);
thread2.setName("rename thread2");
thread2.start();
}
}
class ThreadTest extends Thread {
public ThreadTest() {
System.out.println("====1:" + Thread.currentThread().getName());
System.out.println("====1:" + Thread.currentThread().isAlive());
System.out.println("====2:" + this.getName());
System.out.println("====2:" + this.isAlive());
}
@Override
public void run() {
System.out.println("====3:" + Thread.currentThread().getName());
System.out.println("====3:" + Thread.currentThread().isAlive());
System.out.println("====4:" + this.getName());
System.out.println("====4:" + this.isAlive());
}
}
上面程序执行结果:
====1:main
====1:true
====2:Thread-0
====2:false
====3:rename thread2
====3:true
====4:rename thread1
====4:false
以====1开头的打印内容是在ThreadTest构造方法中打印的,而调用ThreadTest构造方法是在main进程中的main方法中调用的,所以Thread.currentThread()表示的是main进程(因为ThreadTest代码块当前被main进程执行)。那====2的打印结果又说明什么呢,我们先看一下this.getName()好Thread.currentThread().getName()的区别。
Thread.currentThread()是指执行当前代码块的线程,所以Thread.currentThread().getName()给出的是执行当前代码块的线程名称。而this是指当前线程对象,上面的例子也就是ThreadTest的实例,所以this.getName()给出的是当前对象的线程名称。
通过上面的说明我们就知道了====2实际打印的是ThreadTest的一个实例,Thread-0是当我们创建一个Thread实例时,Java为我们提供的默认线程名称,并且Thread-0此时并没有运行(当前是main进程在执行)。
====3和=====4是在ThreadTest的run方法定义的,当启动thread2线程时候会执行该run方法。====3同样指的是运行当前代码块的线程,也就是"rename thread2"(因为我们在启动thread2时为其重新定义名称),并且thread2处于运行状态(在执行当前代码块)。而====4的this此时还是值得ThreadTest的实例,也就是thread1线程,thread1也进行了重命名"rename thread1"(调用构造方法时还没有进行重命名,所以当时的名称是默认名称Thread-0),并且当前thread1线程并没有执行,thread1中的run方法是交由thread2线程执行的。
注意当前执行线程(Thread.currentThread())和当前线程对象(this)的区别。但是要清楚this.currentThread()和Thread.currentThread()是一样。
上面的实例就说明:线程被创建后可以不执行,而是交由其它线程执行。其它线程启动后,就会调用这个被创建的线程的run方法。
3、Runnable和Thread区别
- 通过实现Runnable接口能够解决单继承问题,如果实现Thread类则不能继承其它类了。
- 通过Runnable接口方式实现多线程,能够起到资源共享的目的,因为多个线程共用一个Runnable实现类,而Thread是继承Runnable接口,每个Thread都会实现各自的Runnable接口。这种说法也不绝对,因为我们也可以将同一个Thread的实现类交由
new Thread(Runnable target)
来达到共享变量的目的。
4、Thread中start()和run()方法
start()和run()都是Thread中的方法,start()会启动一个新线程,新线程会去执行相应的run()方法,start()不能重复调用。run()和普通成员方法一样,单独调用run()方法,不会启动新线程,而是使用当前的线程执行run()方法(哪个线程调用的run方法,就由哪个线程来执行),这个run()和普通方法一样,可以被重复调用。
run()源代码:
@Override
public void run() {
if (target != null) {
//交由指定的线程来执行定义的run方法
target.run();
}
}
target是一个Runnable对象,run()方法直接调用Thread线程的Runnable的run()方法,并不会新建一个线程。
start()源代码:
public synchronized void start() {
if (threadStatus != 0)//如果线程不是处于就绪状态,抛出异常
throw new IllegalThreadStateException();
group.add(this);//将当前线程添加到线程组里
boolean started = false;
try {
start0();//启动线程
started = true;//启动标志位
} finally {
try {
if (!started) {
group.threadStartFailed(this);//启动失败,添加到失败队列里
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0(); //启动线程的本地方法
start()通过调用本地start0()方法,启动一个线程,新线程会调用run()方法。
线程常用方法
sleep()
Thread.sleep()的作用是“当前正在执行的线程”休眠指定时间,这个当前正在执行的线程指的是Thread.currentThread()。
getId()
this.getId()用于获取线程的唯一标识。
yield()方法
yield()方法的作用是让当前线程放弃CPU的使用权,让其它任务执行。但是放弃的时间不固定,有可能马上又获取到CPU时间片。
测试下面代码,查看注释Thread.yield()前后的执行耗时。
class YieldThread extends Thread {
@Override
public void run() {
int count = 0;
long beginTime = System.currentTimeMillis();
for(int i= 0; i < 1000000; i++){
//Thread.yield();
count = count + i + 1;
}
long endTime = System.currentTimeMillis();
System.out.println("耗时" + (endTime - beginTime) + "毫秒!");
}
}
//注释Thread.yield()打印结果:
耗时6毫秒!
//打开注释
耗时820毫秒!
停止线程
Java中有以下三种方式来停止正在运行的线程。
- 使用退出标志,让线程正常退出,也就是执行完了run方法后自动停止。
- 使用Thread类提供的stop()、suspend()或resume(),但是这三种方法是不建议使用的,因为它们可能产生不可预期的结果(可能一些清理工作得不到完成;对一些锁定对象进行了“解锁”,导致得不到同步处理,从而出现数据不一致的问题),并且这三个方法已经被标记为废弃了。
- 使用interrupt方法来中断线程,进而手动实现停止当前线程。
对于第一点没什么说的,通过判断退出标志位来退出run方法。第二点中Thread中自带的stop()和suspend()由于不安全(从JDK2,开始不建议使用),所以已经过时不在建议使用了。线程中断是目前停止线程的通用方式,也是我们这里要说的方式。
线程中断方法
使用interrupt()方法并不会像使用for-break那样立即退出循环,调用interrupt()方法只是将当前线程打了一个标记,也就是中断标记。我们还需要加入一个判断逻辑,来手动停止当线程。
Thread类提供了两个方法来判断当前线程中断标记:
- Thread.interrupted():判断当前线程是否已经中断,当前线程指的是当前正在运行的线程(如果使用threadInstance.intrrupted()方式则指的是当前线程对象,有可能不是正在运行的线程)。Thread.interrupted()除了具有判断线程是否中断的功能,它还会清除线程中断标记。也就是说如果当前线程中断状态为true,那么调用过interrupted后线程中断状态重新恢复到false。
- isInterrupted():判断线程对象(有可能不是正在运行的线程)是否已经中断,该方法不是静态方法,所以不能通过Thread.currentThread调用。
注意isInterrupted和interrupted的区别:所以只能被线程对象调用,这时候线程对象有可能并没有运行;另一个重要的区别在于isInterrupted方法不会清楚中断状态。
停止运行状态的线程
停止运行中的线程,我们可以通过:判断线程中断标记+手动抛出异常的方式来停止线程。
class ThreadDemo extends Thread {
@Override
public void run() {
try {
while (true){
if(this.isInterrupted())
throw new InterruptedException();
//执行业务逻辑
}
}catch (InterruptedException e) {
System.out.println("进入Catch代码块,run方法执行完成,退出异常");
}
}
}
public class ThreadTest {
public static void main(String[] args) {
ThreadDemo threadDemo = new ThreadDemo();
threadDemo.start();
//中断线程标记
threadDemo.interrupt();
}
}
注意try-catch需要在while循环外部,否则不会退出run方法。
停止阻塞状态的线程
停止阻塞状态的线程比较容易,因为它不需要我们判断中断标记。当对阻塞状态中的线程调用interrupt()方法时,会自动抛出InterrupterdException异常,并且会清除中断标记状态值,也就是变为false。
public class ThreadTest {
public static void main(String[] args) {
ThreadDemo threadDemo = new ThreadDemo();
threadDemo.start();
threadDemo.interrupt();
}
}
class ThreadDemo extends Thread {
@Override
public void run() {
try {
while (true){
//业务执行逻辑
Thread.sleep(10000);
}
}catch (InterruptedException e) {
System.out.println("进入Catch代码块,run方法执行完成,退出异常");
}
}
}
需要注意try-catch在代码中的维值,如果将异常处理放在while()中,这样while(true)不会被停止。
暂停线程
暂停线程是指此线程能够暂时停止,之后还能恢复运行。我们可以使用Thread类中的suspend()方法来暂停线程,使用resume()方法来恢复线程执行。
需要注意:suspend()、resume()方法和 stop()方法都已经废弃了,因为它们有可能产生不可预知的后果
suspend()和resume()使用实例:
class MyThread extends Thread {
private long i = 0;
public long getI() {
return i;
}
@Override
public void run() {
while (true)
i++;
}
}
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
Thread.sleep(3000);
myThread.suspend();
System.out.println("i:" + myThread.getI());
Thread.sleep(3000);
System.out.println("i:" + myThread.getI());
myThread.resume();
Thread.sleep(3000);
myThread.suspend();
System.out.println("i:" + myThread.getI());
Thread.sleep(3000);
System.out.println("i:" + myThread.getI());
}
}
打印结果:
i:1629776063
i:1629776063
i:3296163993
i:3296163993
从上面的打印结果可以看到,线程确实暂停了,之后又重新恢复了。之所以将它们废弃,主要是使用suspend()和resume()极易造成公共同步对象独占,导致其它线程无法访问公共对象。
synchronized void printString(String str) {
if(Thread.currentThread().getName().equals("TheadA")) {
System.out.println("被线程A独占");
Thread.currentThread().suspend();
}
}
除了上面问题,suspend()和resume()方法如果使用不当,也会造成数据不同步,所以我们应该避免使用这两个方法。
synchronized同步锁
原理:每个对象都有且只有一个同步锁,同步锁依赖于对象存在。当调用某个对象的synchronized方法时,就获取该对象的同步锁。例如synchronized(obj)就是获取了obj的同步锁,不同线程对同步锁访问是互斥的。就是说一个时间点,对象的同步锁只能被一个线程调用,其它线程如果要使用,需要等待正在使用同步锁的线程释放掉后才能使用。
synchronized规则:
- 当一个线程访问某对象的synchronized方法或synchronized代码块时,其它线程对该对象的该synchronized方法或者synchronized代码块访问将被受阻;
- 当一个线程访问某个对象的synchronized方法或synchronized代码块时,其它线程可以访问该对象的非synchronized方法或synchronized代码块;
- 当一个线程访问某个对象的synchronized方法或synchronized代码块时,当其它对象访问该对象的synchronized方法或synchronized代码块时,其它对象的线程将被受阻。
public class RunnableTest implements Runnable{
@Override
public void run() {
synchronized(this){
for(int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName()+" num:" + i );
}
}
}
}
class Test{
public static void main(String[] args){
RunnableTest runnable = new RunnableTest();
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
Thread thread3 = new Thread(runnable);
thread1.start();//只有start()后才进入就绪状态
thread2.start();
thread3.start();
}
}
运行结果
- Thread-0 num:0
- Thread-0 num:1
- Thread-0 num:2
- Thread-2 num:0
- Thread-2 num:1
- Thread-2 num:2
- Thread-1 num:0
- Thread-1 num:1
- Thread-1 num:2
结论:
当一个线程访问对象的synchronized方法或者代码块,其它线程将会被阻塞。Thread0、Thread1、Thread2共用RunnableTest实现Runnable接口的同步锁,当一个线程运行synchronized()代码块时候,其它线程需要等待正在运行的线程释放同步锁后才能运行。
再来看下使用Thread方式实现多线程的获取同步锁的执行流程
class ThreadTest extends Thread{
int num = 10;
@Override
public void run() {
synchronized(this){
for(int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName()+" num:" + i );
}
}
}
}
class Test{
public static void main(String[] args){
ThreadTest threadTest1 = new ThreadTest();
ThreadTest threadTest2 = new ThreadTest();
ThreadTest threadTest3 = new ThreadTest();
threadTest1.start();
threadTest2.start();
threadTest3.start();
}
}
运行结果
- Thread-0 num:0
- Thread-0 num:1
- Thread-0 num:2
- Thread-2 num:0
- Thread-1 num:0
- Thread-2 num:1
- Thread-1 num:1
- Thread-2 num:2
- Thread-1 num:2
结论:
发现并没有我们之前说的Thread0、Thread1、Thread2阻塞顺序执行,这个主要是和Thread形式创建多线程有关,trhreadTest1、trhreadTest2、trhreadTest3是三个不同的对象,它们是通过new ThreadTest()创建的三个对象,这里synchronized(this)是指的ThreadTest对象,所以threadTest1、threadTest2、threadTest3是获取的三个不同的同步锁。而上面使用RunnableTest方式实现的多线程,this是指的RunnableTest,这样三个线程使用的是同一个对象的同步锁。
当一个进程访问对象的同步锁时,其它线程可以访问这个对象的非synchronize代码块
class ThreadTest2{
public void synMethod(){
synchronized (this){
for(int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName() + " num: " + i);
}
}
}
public void nonSynMethod(){
for(int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName() + " num:" + i);
}
}
}
class Test{
public static void main(String[] args){
final ThreadTest2 threadTest = new ThreadTest2();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
threadTest.synMethod();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
threadTest.nonSynMethod();
}
});
thread1.start();
thread2.start();
}
}
返回结果
- Thread-0 num:0
- Thread-0 num:1
- Thread-1 num:0
- Thread-0 num:2
- Thread-1 num:1
- Thread-1 num:2
结论:
thread1访问对象的synchronize代码块,thread2访问非synchronized代码块。thread2并没有因为thread1受阻。
当一个线程访问一个对象的synchronized方法或代码块,其它线程访问这个对象的其它synchronized也是受阻的。
class ThreadTest2{
public void synMethod1(){
synchronized (this){
for(int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName() + " num: " + i);
}
}
}
public void synMethod2(){
synchronized (this){
for(int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName() + " num:" + i);
}
}
}
}
class Test{
public static void main(String[] args){
final ThreadTest2 threadTest = new ThreadTest2();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
threadTest.synMethod1();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
threadTest.synMethod2();
}
});
thread1.start();
thread2.start();
}
}
返回结果
- Thread-0 num:0
- Thread-0 num:1
- Thread-0 num:2
- Thread-1 num:0
- Thread-1 num:1
- Thread-1 num:2
结论:thread1、thread2都会调用ThreadTest2的synchronized(this)代码块,而这个this都是ThreadTest2,所以线程2需要等到线程1执行完synchronized才能执行。
synchronized方法和synchronized代码块
synchronized方法是用synchronized修饰类方法,synchronized代码块是用synchronized修饰代码块的。synchronized代码块可以更精准的控制限制区域,有时效率也是比synchronized方法高的。
class ThreadTest2{
public synchronized void synMethod1(){
for(int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName() + " num: " + i);
}
}
public void synMethod2(){
//this获取当前对象的同步锁,如果修改成xxx,则获取xxx的同步锁
synchronized (this){
for(int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName() + " num:" + i);
}
}
}
}
实例锁和全局锁
- 实例锁:如果锁在某一个实例上面,那么该锁就是实例锁。如果这个类是单例,那么这个锁也具有全局锁的概念。实例锁使用synchronized关键字。
- 全局锁:如果锁针对的是一个类上面,无论多少个实例共享这个锁。全局锁使用static synchronized,或者锁在该类的class或classloader上面。
class SomeLock{
public synchronized void intanceLockA(){}
public synchronized void instanceLockB(){};
public static synchronized void globalLockA(){};
public static synchronized void globalLockB(){};
}
- x.instaceLockA()和x.instanceLockB(),二者不能同时被访问,因为二者都是访问的都是x的实例锁。
- x.instaceLockA()和y.instaceLockA(),二者可以同时被访问,因为二者访问的不是同一个对象的锁。
- x.globalLocckA()和y.globalLockB(),二者不能同时访问,因为y.globalLockB()相当于SomeLock.globalLockB(),x.globalLockA()相当于SomeLock.globalLockA(),二者使用的是同一个同步锁,所以不能同时被访问。
- x.instaceLocakA()和b.globalLockA(),二者可以同时被访问,因为一个是示例的锁,一个是类的锁。
线程的等待与唤醒
线程的等待与唤醒使用了Object类中的wait()、wait(long timeout)、wait(long timeout,int nanos)、notify()、notifyAll()
- wait():使线程进入等待状态(等待阻塞),直到其它线程调用该对象的 notify()或notifyAll(),当前线程会被唤醒(进入就绪状态)。
- wait(long timeout):使线程进入等待状态(等待阻塞),直到其它线程调用该对象的notify()或notifyAll()或超过了指定时间,当前线程会被唤醒(进入就绪状态)。
- wait(long timeout,int nanos):使线程进入等待状态(等待阻塞),直到其它线程调用该对象的notify()或notifyAll()或超过了指定时间或被其它线程中断,当前线程会被唤醒(进入就绪状态)。
- notify():唤醒在此对象监视器(同步锁的实现原理)上等待的单个线程。
- notifyAll():唤醒在此对象监视器上等待的多个线程。
注意:
wait()的作用是让当前线程等待,当前线程指的是正在cpu运行的线程,而不是调用wait()方法的线程。wait()、notify()、notifyAll()都是属于Object类下边的方法,之所以在Object下面而没有在Thread类下面,主要原因就是同步锁。
wait()和notify()都是对对象的同步锁进行操作,同步锁是对象持有的,并且每个对象有且仅有一个。
线程让步yield()
yield()的作用是让步。让当前线程由运行状态进入就绪状态,从而让其他具有高优先级的线程获取cpu执行。但是并不会保证当前线程调用yield()后,其它同等级线程一定获取到cpu执行权。也有可能当前线程又进入到运行状态。
yield()与wait()的区别
- wait()会由运行状态进入等待状态(阻塞状态),而yield()会从运行状态进入就绪状态。
- wait()会释放对象的同步锁,而yield()是不会释放对象的同步锁的。
线程休眠sleep()
sleep()在Thread.class类中定义,让当前线程由运行状态进入休眠状态(阻塞状态)。sleep()需要指定休眠时间,线程休眠时间会大于等于该休眠时间;线程被重新唤醒时会由阻塞状体进入就绪状态。
sleep()与wait()区别
sleep()和wait()都会让线程由运行状态进入阻塞状态,但是wait()会释放对象同步锁,而sleep()不会释放同步锁。
线程join()
join()在Thread.class类中定义,让主线程等待子线程结束后才能继续运行。
// 主线程
public class Father extends Thread {
public void run() {
Son s = new Son();
s.start();
s.join();
...
}
}
// 子线程
public class Son extends Thread {
public void run() {
...
}
}
Son线程是在Father线程中创建的,并且调用了s.join(),这样Father线程要等到Son线程执行完成后,才会执行。可以查看下join()的源代码:
public final void join() throws InterruptedException {
join(0);
}
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;
}
}
}
join()中通过wait()进行等待,所以即使是子线程调用的join(),而真实等待的是正在执行的父进程。
线程优先级
在操作系统中,线程可以划分优先级,优先级较高的线程被CPU优先执行。设置线程优先级就是帮助“线程规划器”确定下次选哪一个线程来优先执行。
java中线程优先级从1~10,默认是5。Thead类提供了3个常量预定义优先级的值:
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
线程优先级具有继承性,这里的继承性是指比如在A线程中启动B线程,那么B线程就有与A线程同样的优先级。
需要注意的是,高优先级的线程总是大部分都会先执行完成,但是并不代表高优先级的线程执行完成后,再去执行低优先级的线程。高优先级,只说明CPU尽量将执行资源给优先级比较高的线程。
守护线程
在Java线程有两种线程,非守护线程(又称为用户线程)和守护线程(Daemon)。
守护线程是一种特殊的线程,它的特性就是陪伴,当进程中不存在非守护线程了,则守护线程自动销毁。比如GC线程就是典型的守护线程,当进程中没有非守护线程了,则垃圾回收线程自动销毁。
比如下面测试用例:
class DaemonThread extends Thread {
@Override
public void run() {
int i =0;
try {
while (true) {
System.out.println(i++);
Thread.sleep(1000);
}
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
DaemonThread daemonThread = new DaemonThread();
daemonThread.setDaemon(true);
daemonThread.start();
Thread.sleep(5000);
System.out.println("main线程退出,daemonThread也不会执行了");
}
}
打印结果:
0
1
2
3
4
main线程退出,daemonThread也不会执行了
因为DaemonThread是守护线程,而main线程为非守护线程,当main线程退出后,daemonThread也会退出。
关注我
欢迎关注我的公众号,会定期推送优质技术文章,让我们一起进步、一起成长!
公众号搜索:data_tc
或直接扫码:🔽