线程的五种状态
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创建线程
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: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相减十次。
2、Thread实现
java.lang.Thread是一个类,实现了Runnable接口。如果要实现多线程,需要继承Thread类,然后通过创建实现类对象来启动线程。
public class Thread implements Runnable {
...
}
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次相减。
3、Runnable和Thread区别
- 通过实现Runnable接口能够解决单继承问题,如果实现Thread类则不能继承其它类了。
- 通过Runnable接口方式实现多线程,能够起到资源共享的目的,因为多个线程共用一个Runnable实现类,而Thread是继承Runnable接口,每个Thread都会实现各自的Runnable接口。
4、Thread中start()和run()方法
start()和run()都是Thread中的方法,start()会启动一个新线程,新线程会去执行相应的run()方法,start()不能重复调用。run()和普通成员方法一样,单独调用run()方法,不会启动新线程,而是使用当前的线程执行run()方法,这个run()和普通方法一样,可以被重复调用。
run()源代码:
@Override
public void run() {
if (target != null) {
target.run();
}
}
target是一个Runnable对象,run()方法直接调用Thread线程的Runnable的run()方法,并不会新建一个线程。
start()源代码:
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)//如果线程不是处于就绪状态,抛出异常
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
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()方法。
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(),而真实等待的是正在执行的父进程。
线程中断
Thread中自带的stop()和suspend()由于不安全(从JDK2,开始不建议使用),所以已经过时不在建议使用了。线程中断可以从阻塞状体和运行状态说起。
阻塞状态
当线程通过wait()、sleep()、join()等进入了阻塞状态,若此时调用线程的interrupt()会将线程中断标记为true,由于处于阻塞状态,中断标记会被清除,同时产生一个InterruptedException异常。所以可以将InterruptedException放在适当的位置就能终止进程。
public void run(){
try {
while (true){
....
}
}catch (InterruptedException ie){
//产生InterruptedException,退出while(true)循环,线程终止
}
}
如果将异常处理放在while()中,这样while(true)不会被停止,还需要在catch手动break一下。
运行状态
public void run(){
while (!thread.isInterrupted()){
...
}
}
isInterrupted()是判断线程中断标记是否为true。当线程处于运行状态时候,通过调用interrupted()方法将线程中断标记设置为true,这样判读isInterrupted()就可以退出线程了。
interrupted()并不会终止处于运行状态的线程,只是将中断标记设置为true。
综合线程处于阻塞状态和运行状态,可以使用通用方式:
public void run(){
try {
//1、通过中断标记终止线程
while (!thread.isInterrupted()){
}
}catch (InterruptedException ie){
//2、通过InterruptedException产生异常,终止线程
}
}
interrupted()和isInterrupted()区别
二者都能检测对象的中断标记,但是interrupted()除了返回中断状态外,如果线程处于阻塞状态还会清除中断标记(设置为false),而isInterrupted()只是返回中断标记。
线程优先级
java中线程优先级从1~10,默认是5,值越大优先级越高,高优先级与优先低优先级线程执行。每个线程都会标记为一个守护线程或非守护线程。主线程创建的子线程与其具有相同的优先级。当且仅当父线程是守护线程,子线程才是守护线程。java虚拟机启动时候就是启动一个非守护线程(通过main方法启动),它会监听:
- 是否调用exit(),并且exit()被执行;
- 所有的非守护线程都死了(jvm中只有守护线程)
java中线程有两种:守护线程(用户线程)和守护线程。可通过isDaemon()方法来区别。用户线程一般用户执行用户任务,守护线程也称为后台进程,一般用来执行后台任务。java虚拟机在用户线程都结束后会退出。如果要设置为守护线程可以通过setDaemon(true)来操作。
网友评论