Java多线程之线程状态转换、控制线程和线程同步

Java多线程之线程状态转换、控制线程和线程同步

  • 前言:我是一名android,实际上我是大连理工大学的一名大四小白!Java多线程系列博客是我自己从众多博客以及一些书总结出来的文章。内容均为总结性内容,用于自己复习研究以及广大同仁们交流!如果大家想要看这篇博客,请务必先看看它系列博客的前一篇:Java多线程之多线程概述和三种概述方式。

(一).线程状态转换

  1. 新建状态(New):新创建了一个线程对象。Java虚拟机为其分配内存,并初始化成员变量。
  2. 就绪状态(Runnable):线程对象调用了start()方法之后,线程处于就绪状态,Java虚拟机会为其创建方法调用栈和程序计数器。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
  3. 运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
  4. 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃了CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行态。
    阻塞的情况分三种:
    一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
    二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
    (三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)

5.死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。当线程结束时,其他线程不会受到任何影响,并不会随之结束,一旦子线程启动后,它就拥有和主线程相同的地位。

Java多线程之线程状态转换、控制线程和线程同步_第1张图片

(二).控制线程

(一).join线程:Thread提供了让一个线程等待另一个线程完成的方法–join()方法。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。

package com.multithread.join;  
class Thread1 extends Thread{  
    private String name;  
    public Thread1(String name) {  
        super(name);  
       this.name=name;  
    }  
    public void run() {  
        System.out.println(Thread.currentThread().getName() + " 线程运行开始!");  
        for (int i = 0; i < 5; i++) {  
            System.out.println("子线程"+name + "运行 : " + i);  
            try {  
                sleep((int) Math.random() * 10);  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }  
        System.out.println(Thread.currentThread().getName() + " 线程运行结束!");  
    }  
}  

public class Main {  

    public static void main(String[] args) {  
        System.out.println(Thread.currentThread().getName()+"主线程运行开始!");  
        Thread1 mTh1=new Thread1("A");  
        Thread1 mTh2=new Thread1("B");  
        mTh1.start();  
        mTh2.start();  
        System.out.println(Thread.currentThread().getName()+ "主线程运行结束!");  

    }  

}  

输出结果:
main主线程运行开始!
main主线程运行结束!
B 线程运行开始!
子线程B运行 : 0
A 线程运行开始!
子线程A运行 : 0
子线程B运行 : 1
子线程A运行 : 1
子线程A运行 : 2
子线程A运行 : 3
子线程A运行 : 4
A 线程运行结束!
子线程B运行 : 2
子线程B运行 : 3
子线程B运行 : 4
B 线程运行结束!
发现主线程比子线程早结束

加join

public class Main {  

    public static void main(String[] args) {  
        System.out.println(Thread.currentThread().getName()+"主线程运行开始!");  
        Thread1 mTh1=new Thread1("A");  
        Thread1 mTh2=new Thread1("B");  
        mTh1.start();  
        mTh2.start();  
        try {  
            mTh1.join();  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        try {  
            mTh2.join();  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        System.out.println(Thread.currentThread().getName()+ "主线程运行结束!");  

    }  

}  

运行结果:
main主线程运行开始!
A 线程运行开始!
子线程A运行 : 0
B 线程运行开始!
子线程B运行 : 0
子线程A运行 : 1
子线程B运行 : 1
子线程A运行 : 2
子线程B运行 : 2
子线程A运行 : 3
子线程B运行 : 3
子线程A运行 : 4
子线程B运行 : 4
A 线程运行结束!
主线程一定会等子线程都结束了才结束

(二).后台线程:有一种线程它是在后台运行的,任务是为其他的线程提供服务,这种线程被称为“后台线程”。JVM的垃圾回收线程就是典型的后台线程。
  后台线程有一个典型特征,前台线程都死亡了,后台线程会自动死亡。调用Thread对象的setDaemon(true)方法可以将指定线程设定为后台线程。

(三).线程睡眠 Sleep:Thread.sleep(long millis)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()平台移植性好。

(四).线程让步 yield:暂停当前正在执行的线程对象,并执行其他线程。 Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。 yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

package com.multithread.yield;  
class ThreadYield extends Thread{  
    public ThreadYield(String name) {  
        super(name);  
    }  

    @Override  
    public void run() {  
        for (int i = 1; i <= 50; i++) {  
            System.out.println("" + this.getName() + "-----" + i);  
            // 当i为30时,该线程就会把CPU时间让掉,让其他或者自己的线程执行(也就是谁先抢到谁执行) 
            if (i ==30) {  
                this.yield();  
            }  
        }  

}  
}  

public class Main {  

    public static void main(String[] args) {  

        ThreadYield yt1 = new ThreadYield("张三");  
        ThreadYield yt2 = new ThreadYield("李四");  
        yt1.start();  
        yt2.start();  
    }  

} 

第一种情况:李四(线程)当执行到30时会CPU时间让掉,这时张三(线程)抢到CPU时间并执行。
第二种情况:李四(线程)当执行到30时会CPU时间让掉,这时李四(线程)抢到CPU时间并执行。

【sleep()和yield()的区别】sleep()使当前线程处于阻塞状态,所以执行sleep()的线程在指定时间内肯定不会被执行;yield()方法只是使当前程序重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。sleep()方法允许较低优先权的线程获得运行机会,但yield()方法只会让相同或较高优先权的线程获得运行机会。

(五).改变线程优先级

  • Thread类提供了setPriority(int newPriority)来设置和返回线程的优先级,其中setPriority()方法的参数可以是一个整数,范围是1—10之间。还有下面三个常量:
  • MIN_PRIORITY = 1
    NORM_PRIORITY = 5
    MAX_PRIORITY = 10

(三).常见线程名词解释和常用方法:

主线程:JVM调用程序main()所产生的线程。
当前线程:这个是容易混淆的概念。一般指通过Thread.currentThread()来获取的进程。
后台线程:指为其他线程提供服务的线程,也称为守护线程。JVM的垃圾回收线程就是一个后台线程。用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束
前台线程:是指接受后台线程服务的线程,其实前台后台线程是联系在一起,就像傀儡和幕后操纵者一样的关系。傀儡是前台线程、幕后操纵者是后台线程。由前台线程创建的线程默认也是前台线程。可以通过isDaemon()和setDaemon()方法来判断和设置一个线程是否为后台线程。

  • sleep(): 强迫一个线程睡眠N毫秒。
      isAlive(): 判断一个线程是否存活。
      join(): 等待线程终止。
      activeCount(): 程序中活跃的线程数。
      enumerate(): 枚举程序中的线程。
    currentThread(): 得到当前线程。
      isDaemon(): 一个线程是否为守护线程。
      setDaemon(): 设置一个线程为守护线程。(用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束)
      setName(): 为线程设置一个名称。
      wait(): 强迫一个线程等待。
      notify(): 通知一个线程继续运行。
      setPriority(): 设置一个线程的优先级。

你可能感兴趣的:(多线程,线程同步,线程状态)