白话java锁--线程状态

本文详细介绍了Java线程的概念、创建方式、线程状态及转换,包括初始、运行、阻塞、等待、超时等待和终止状态。探讨了不同状态间的转换机制,如通过调用start()、sleep()、wait()等方法实现状态变化,以及如何通过设置标记位、使用interrupt()方法安全地终止线程。

线程概念

线程就是进程(进程就是在运行过程中的程序)的执行单元。过多内容不再赘述。这里注意一下java的线程是映射到操作系统的线程的,在执行线程操作的时候实际上是需要调用操作系统的方法的

创建线程的四种方式

方式一

继承Thread类

public class MyThread extends Thread

注意:

  1. 一个线程调用两次start()方法会抛出java.lang.IllegalThreadStateException的异常,也就是start()只可以被调用一次
    在这里插入图片描述
    因为线程在启动的时候会有几种状态,每种状态改变的时候都会修改threadStatus的值,所以第二次启动的时候发现threadStatus的值已经改变了不是0了,就会抛出IllegalThreadStateException的异常
    在这里插入图片描述
  2. 看start0()方法的时候,发现没有方法体,只有方法名,且是native的,说明是本地方法,是调用c语言的方法
    在这里插入图片描述
  3. 不可以手动调用run()方法

方式二

实现Runnable()接口

public class MyThread implements Runnable

注意:

  1. 接口方式避免单继承的局限性
  2. 子类通过实现Runnable中的接口,实现自己的逻辑,而Thread则负责资源的调度与辅助业务(类似于代理模式)

方式三

实现Callable接口

public class MyThread implements Callable<String> {

    @Override
    public String call() {
        // do something...
        return "";
    }

    public static void main(String[] args) {
        Callable<String> callable  =new MyThread();
        FutureTask <String>futureTask=new FutureTask<>(callable);
        Thread mThread=new Thread(futureTask);
        mThread.start();
    }

}

这种方式在java 5中新增的方式,实现Callable接口可以有返回值,同时可以抛出异常,但是前两种方式就不具备这种特点,(虽然说不可以抛出异常但是可以通过UncaughtExceptionHandler获取到异常),Callable接口就好比Runable接口的升级版,call()方法作为线程执行的具体逻辑可以有返回值。

但是Callable对象不能直接构造线程,需要通过FutureTask实现类将Callable包装后才能开启线程,因为FutureTask同时实现了Future接口和Runnable接口,而Future接口可以接收Callable。

方式四

线程池方式,这种方式在这里就简单的说一下,后面会专门开篇文章详细介绍线程池

public static void main(String[] args) {
    ExecutorService ex = Executors.newFixedThreadPool(5);

    for(int i=0;i<5;i++) {
        ex.submit(new Runnable() {

            @Override
            public void run() {
                for(int j=0;j<10;j++) {
                    System.out.println(Thread.currentThread().getName()+j);
                }

            }
        });
    }
    ex.shutdown();
}

线程的六种状态

在java.lang.Thread.State中可以详细看到一共有六种状态
在这里插入图片描述
直接就在网上找了张图,然后说明一下每种状态都代表什么意思
在这里插入图片描述

  • 初始(NEW):单纯的创建了一个线程,还没有调用start()方法
  • 运行(RUNNABLE):就绪(READY)和运行中(RUNNING)两种状态笼统的称为“运行”。线程在创建之后调用start()方法时,可能不会立即就获得CPU执行权力,而是出于等待获得CPU执行权力的状态,此时线程位于可运行线程池中,等待被线程调度选中,获得CPU执行权力,这时就是就绪状态(READY)。处于就绪状态的线程在获得CPU执行权力之后就会变为运行中状态(RUNNING)
  • 阻塞(BLOCKED):表示因为存在锁的原因而阻塞
  • 等待(WAITING):因为调用wait()方法,导致线程处于等待状态,这种状态下需要其他线程调用notify()/notifyAll()方法来结束
  • 超时等待(TIMED_WAITING):类似于WAITING的状态,但是可以设定超时时间,在超过超时时间后可以自行结束等待状态
  • 终止(TERMINATED):表示线程已经执行完毕

线程的六种状态–详细说明

初始状态

通过上面四种方式创建线程后,还没有调用start()方法,线程就进入了初始状态。

运行状态–就绪状态

就绪状态只是说线程有资格运行,但是调度程序没有挑选这个线程,那么就一直是就绪状态(有一点类似缓冲区,在实际执行前的一个缓冲区,可能是为了防止多个线程同时执行,而不知道应该执行哪个线程)

运行状态–运行中状态

调度程序从就绪状态的线程池中选择一个线程去获得CPU执行权限,执行具体的线程逻辑。这个是线程进入到运行中状态的唯一方式

阻塞状态

线程因为等待获取锁的时候而处于阻塞状态。具体详细说明可以看我的这篇文章白话Java锁–synchronized关键字

等待状态

处于等待状态的线程不会被分配CPU执行时间,需要去被显式的唤醒,否则会处于无限等待的状态

超时等待状态

处于这种的等待状态的线程同样不会被分配CPU执行时间,不过不需要无限期的去等待其他线程显示的唤醒,而是在达到一定时间后会被自动唤醒

终止状态

当线程run()方法执行完毕之后,或者主线程执行完毕时,就认为终止了。而且执行完毕了之后不能再次start(),不能复生,否则会抛出java.lang.IllegalThreadStateException异常

线程的六种状态–如何进行转换

初始状态–>运行状态

调用线程的start()方法即可

public synchronized void start()

运行状态–>超时等待状态

  1. 调用Thread.sleep(long millis)方法,让线程暂缓执行,等到预计设定好的时间之后再恢复执行。主要作用就是给其他线程执行的机会。
public static native void sleep(long millis) throws InterruptedException;
  • 休眠会交出CPU执行权限,让CPU去执行其他任务
  • 进入休眠状态的线程不会释放锁,即当前线程持有某个对象锁的时候,调用sleep后,其他线程无法获取这个对象锁(这个需要与wait方法做区分,因为wait方法会释放持有的对象锁)
  1. 调用Object.wait(long millis)方法,让获得到CPU执行权限的线程放弃权限,进入到超时等待状态,进行等待其他线程的唤醒或者等待预先设定好的时间
public final native void wait(long timeout) throws InterruptedException;
  • 进入超时等待的线程会释放锁
  1. 调用Thread.join(long millis)方法,join方法是指如果在主线程中调用这个方法就会让主线程休眠,让调用join()方法的线程先执行完毕后再开始执行主线程。
public final synchronized void join(long millis) throws InterruptedException
  • 实际上就是调用join方法的线程等待被调用的线程执行完毕之后才开始执行,可以设定超时时间,从而进入超时等待的状态
  • 同样的因为join进入超时等待的线程不会释放锁,其他线程无法获取这个锁对象(与sleep类似)
  1. 调用JUC包中的方法,在本篇文章里先不对JUC包中的内容进行讲解。

运行状态<–超时等待状态

  1. 调用Object.notify()/notifyAll()方法,notify()方法会唤醒某个因为调用wait()方法而处于等待队列的线程,是随机的,notifyAll()方法可以唤醒所有处于等待队列中的线程
public final native void notify();

public final native void notifyAll();
  • 唤醒的线程可以处于运行状态去重新竞争对象锁
  1. 超时时间到同样会将线程转化为运行状态
  2. 调用JUC包中的方法

运行状态–>等待状态

与超时等待类似

  1. 调用Object.wait()方法,让获得到CPU执行权限的线程放弃权限,进入到等待状态,进行等待其他线程的唤醒
    public final void wait() throws InterruptedException {
        wait(0);
    }
  • 进入等待的线程会释放锁
  1. 调用Thread.join()方法,join方法是指如果在主线程中调用这个方法就会让主线程休眠,让调用join()方法的线程先执行完毕后再开始执行主线程。
    public final void join() throws InterruptedException {
        join(0);
    }
  • 实际上就是调用join方法的线程等待被调用的线程执行完毕之后才开始执行,调用线程因为等待从而进入等待状态
  • 同样的因为join进入等待的线程不会释放锁,其他线程无法获取这个锁对象(与sleep类似)
  1. 调用JUC包中的方法,在本篇文章里先不对JUC包中的内容进行讲解。

运行状态<–等待状态

  1. 调用Object.notify()/notifyAll()方法,notify()方法会唤醒某个因为调用wait()方法而处于等待队列的线程,是随机的,notifyAll()方法可以唤醒所有处于等待队列中的线程
public final native void notify();

public final native void notifyAll();
  • 唤醒的线程可以处于运行状态去重新竞争对象锁
  1. 调用JUC包中的方法

运行状态–>阻塞状态

线程在进入synchronized方法或者synchronized块的时候,因为其他线程以及获得到对象锁,导致这个线程无法获得到对象锁,从而为了等待对象锁而处于阻塞状态。

运行状态<–阻塞状态

处于阻塞状态的线程,因为获得到了对象锁,重新去竞争CPU执行权,处于运行状态。

就绪状态–>运行中状态

通过系统的调度线程调度,让处于就绪状态的线程去获取CPU执行权限,处于运行中状态

就绪状态<–运行中状态

  1. 处于就绪状态的线程,因为CPU时间片使用完了导致进入到就绪状态,重新去竞争CPU执行权
  2. 调用yield()方法让出执行权而进入到就绪状态。作用:让相同优先级的线程轮流执行,但不保证一定会轮流执行。实际上无法保证达到让步的目的,因为让步的线程可能还会被调度线程选中。
public static native void yield();
  • yield()方法和sleep()方法类似,不会释放锁,但yield()方法不能控制具体交出CPU的时间
  • yield()方法只能让拥有相同优先级的线程获得CPU的执行机会
  • yield()方法不会让线程进入到阻塞状态

线程终止

线程终止有三种方式:

方式一

设置标记位,让线程正常停止

class MyThread implements Runnable{
    //设置标记位
    private boolean flag=true;
    public void setFlag(boolean flag) {
        this.flag = flag;
    }
    @Override
    public void run() {
        while(flag)
        {
            // do somthing...
        }
    }
}
public class Test {
    public static void main(String[] args) throws InterruptedException{
        MyThread myThread=new MyThread();
        Thread thread1=new Thread(myThread);
        thread1.start();
        myThread.setFlag(false);
        System.out.println("代码结束");
    }
}

方式二

使用stop()方法强制使线程退出,但是该方法不安全,已经废弃了

class MyThread implements Runnable{
    //设置标记位
    private boolean flag=true;
    public void setFlag(boolean flag) {
        this.flag = flag;
    }
    @Override
    public void run() {
        while(flag)
        {
            // do somthing...
        }
    }
}
public class Test {
    public static void main(String[] args) throws InterruptedException{
        MyThread myThread=new MyThread();
        Thread thread1=new Thread(myThread);
        thread1.start();
        thread1.stop();
        System.out.println("代码结束");
    }
}

那么为什么stop()方法不安全呢?

因为stop()方法会释放有线程获得的所有锁,当一个线程在执行stop()方法的时候,线程会立即停止,假如在执行同步方法的时候,执行了一半,线程就被stop()了,那么就会造成数据的不完整性,所以stop()方法不安全,已经废弃,不建议使用。

方式三

使用Thread的interrupt()方法中断线程

class MyThread implements Runnable{
    //设置标记位
    private boolean flag=true;
    public void setFlag(boolean flag) {
        this.flag = flag;
    }
    @Override
    public void run() {
        while(flag)
        {
            // do somthing...
        }
    }
}
public class Test {
    public static void main(String[] args) throws InterruptedException{
        MyThread myThread=new MyThread();
        Thread thread1=new Thread(myThread);
        thread1.start();
        thread1.interrupt();
        System.out.println("代码结束");
    }
}

interrupt()方法其实类似于第一种方法,也是改变中断状态,不会立即终止一个正在运行的线程,在调用interrupt()方法的时候会给线程设置一个为true的中断标识,设置之后,会根据线程的状态执行后续的操作

  • 如果线程当前状态处于非阻塞状态,仅仅将线程的中断标识设置为true
  • 如果当前线程处于阻塞状态,在设置中断标识为true后,又调用了wait()、sleep()、join()方法而进入等待状态,那么线程中断标志位会被重新设置为false,并抛出InterruptedException异常
  • 如果在中断时,线程处于非阻塞状态,则将中断标识设置为true,但是又调用了wait()、sleep()、join()方法而进入等待状态,那么线程中断标志位会被重新设置为false,并抛出InterruptedException异常

调用interrupted()方法的时候实际上只是设置了线程中断的标识,并根据线程后续的执行状态来决定是否抛出异常。根据中断标识的具体值来决定线程如何退出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值