美文网首页
java线程

java线程

作者: 傻明蚕豆 | 来源:发表于2020-10-18 19:15 被阅读0次

    大家好,我是傻明蚕豆,今天为大家带来java线程的基础知识。

    我在CSDN也发布了,编辑比这里美观:https://blog.csdn.net/weixin_44668634/article/details/109147514

    一、线程的概念;

    进程:进程就是一个在内存中运行的应用程序,比如你电脑在运行的一个QQ,如果你再打开个哭狗,那就是另一个进程,每个进程都有自己的独立内存空间,一个进程中可以有多个线程。

    线程:线程是进程里面的一个执行流程,是CPU调度和分派的基本单位,一个进程中可以有多个线程,线程与进程内的其他线程一起共享所有该进程的资源,每个线程有自己的堆栈和局部变量。java程序中最少有两个线程,一个main线程,一个是垃圾回收线程。

    二、线程的生命周期;

    NEW状态:

    一个已创建而未启动(还没调用Thread.start()方法)的Java线程,就是处于NEW状态。

    RUNNABLE状态:

    当线程调用了start()之后,就会进入RUNNABLE状态,RUNNABLE状态包含两个子状态:READY和RUNNING。

    READY:准备状态,当被线程调度器进行调度,会变为RUNNING状态。

    RUNNING:运行状态,线程正在运行,run()方法中的代码在执行。

    当Java线程调用yield()方法时或者由于线程调度器的调度,线程的状态有可能由RUNNING转变为READY,Thread.getState()可以获取到状态。

    WAITING状态:

    无限期等待状态,需要唤醒,进入该状态是由于调用了下面方法之一:不带超时的Object.wait(),不带超时的Thread.join(),LockSupport.park() 。

    TIMED WAITING状态:

    时间等待状态,无需唤醒,进入该状态是由于调用了下面方法之一:Thread.sleep(time),Object.wait(time),Thread.join(time),LockSupport.parkNanos(time),LockSupport.parkUntil(time)

    BLOCKED状态:

    阻塞状态,遇到阻塞的I0操作或者在等待某个锁资源都会进入阻塞状态。

    TERMINATED状态:

    死亡状态,线程的最终状态,在该状态的线程不会再切换到其它任何状态,意味着线程的整个生命周期都结束了。

    三、线程的创建方式;

    1、继承Thread类

    public class Thread1 extends Thread{

        @Override

        public void run() {

            System.out.println(Thread.currentThread().getName());

        }

        public static void main(String[] args) {

            Thread t=new Thread1();

            t.start();

            //t.start();

        }

    }

    2、实现Runnable接口

    public class Thread2 implements Runnable{

        @Override

        public void run() {

            System.out.println(Thread.currentThread().getName());

        }

        public static void main(String[] args) {

            new Thread(new Thread2()).start();

        }

    }

    3、实现Callable接口

    public class Thread3 implements Callable{

        public static void main(String[] args) {

            ExecutorService executorService=Executors.newFixedThreadPool(1);

            Thread3 t=new Thread3();

            Future future=executorService.submit(t);

            System.out.println("result:"+future);

        }

        @Override

        public Object call() throws Exception {

            String threadName=Thread.currentThread().getName();

            System.out.println("name:"+threadName);

            return threadName;

        }

    }

    四、线程的启动和停止;

    怎么启动线程?

    Thread类实现了Runnable接口,所以创建线程继承Thread类或者实现Runnable接口都是一样的,因为java是单继承,所以如果要继承其他类,那就只有实现Runnable接口了。

    线程启动必须调用Thread.start方法,run()方法只是一个类中的普通方法,调用run方法跟调用其他普通方法一样,而start()是会创建新线程,然后新线程调用run方法。

    我们来看看Thread类的源码(JDK1.8):

    private volatile int threadStatus = 0;

    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();

    public enum State {

    NEW,

    RUNNABLE,

    BLOCKED,

    WAITING,

    TIMED_WAITING,

    TERMINATED;

    }

    threadStatus和枚举类State就是用来记录Thread的状态,Thread类中并没有对threadStatus进行过赋值,那么threadStatus应该是在start0方法里面改变了。

    start0方法是native修饰的,所以是调用了C++实现的方法,而threadStatus是volatile修饰,保证可见性,

    因此native方法进行变量threadStatus修改的同时Thread类中就能同时获取到该变量的最新值。

    同时start方法是synchronized修饰的,所以即使多线程同时调用start方法,也能保证原子性。

    线程初始化后状态为0,所以start方法只可以调用一次,因为线程调用过一次start方法后,状态就会改变,那么threadStatus就不等于0了,

    由于threadStatus是在c++里面修改,所以看不到ThreadStatus等于多少,但肯定不等于0,所以再调用start方法就会抛出IllegalThreadStateException。

    如何中断线程?

    不要问我为什么不用stop方法。

    在Thread线程类里面有个isInterrupted方法,

    private native boolean isInterrupted(boolean ClearInterrupted);

    是一个native方法,Thread类有两个public方法调用了该native方法,

    public boolean isInterrupted() {

        return isInterrupted(false);

    }

    这个方法只返回线程的中断状态,并没有去改变线程的中断状态。

    public static boolean interrupted() {

        return currentThread().isInterrupted(true);

    }

    这个方法是个静态方法,所以只作用于当前正在执行的线程,方法里面调用了currentThread()获取当前线程,该方法返回当前线程的中断状态,并且会重置线程的中断状态。

    这两个方法都只是查看线程的中断状态,并没有中断线程,还有一个方法interrupt():

    public void interrupt() {

        if (this != Thread.currentThread())

            checkAccess();

        synchronized (blockerLock) {

            Interruptible b = blocker;

            if (b != null) {

                interrupt0();          // Just to set the interrupt flag

                b.interrupt(this);

                return;

            }

        }

        interrupt0();

    }

    这个就是中断线程的方法,首先判断是否是当前线程,如果不是调用checkAccess()方法,大概就是查询权限吧,也就是说一个线程只允许自己中断。

    我们再看看这个方法的注释,调用interrupt方法时,如果线程因为以下方法的调用而处于阻塞中,线程的中断标志会被清除,并抛出一个InterruptedException。

    以下方法包括:Object类的wait()、wait(long)、wait(long, int)和Thread类的join()、join(long)、join(long, int)、sleep(long)、sleep(long, int)。

    如果线程没有因为以上方法调用而进入阻塞状态的话,那么中断这个线程仅仅会设置它的中断标志位,而不会抛出InterruptedException。

    总结:调用interrupt方法,并不会中断一个正在运行的线程,而是将线程的中断标志设为true,在某些情况下抛出一个InterruptedException罢了。

    也就是说,无论是设置中断状态,还是抛出InterruptedException,那都是给当前线程的建议,至于在收到这些中断的建议后,当前线程要怎么处理,完全取决于当前线程。

    那么到底怎么中断线程呢?

    设置中断标记法

    public static void test1(){

        Thread thread=new Thread(()->{

            System.out.println("线程启动了");

            while(!Thread.currentThread().isInterrupted()){ //默认情况下isInterrupted() 返回 false

                System.out.println(Thread.currentThread().isInterrupted());

            }

            System.out.println("线程结束了");

        },"test1");

        thread.start();

        try {

            Thread.sleep(2);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        //通过调用interrupt(),把while里面的isInterrupted()返回变为true

        thread.interrupt();

    }

    当main线程运行到最后一行代码thread.interrupt();,那么Thread.currentThread().isInterrupted()就等于true,

    然后循环不成立,接着执行输出"线程结束了",那岂不是没法中断?所以逻辑代码都写在循环里面才能使用中断标记法。

    捕获InterruptedException法

    public static void test2(){

        Thread thread=new Thread(()->{

            System.out.println("线程启动了");

            while(!Thread.currentThread().isInterrupted()){

                try {

                    Thread.sleep(1100);

                    System.out.println(Thread.currentThread().isInterrupted());//逻辑处理代码

                } catch (InterruptedException e) {

                    System.out.println("出异常了");

                    //Thread.currentThread().interrupt();//调用中断方法

                    //break;//跳出循环

                    //return ;//跳出方法,不再执行输出"线程结束了"的逻辑

                }

            }

            System.out.println("线程结束了");

        },"test2");

        thread.start();

        try {

            Thread.sleep(1000 * 5);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        //线程执行了一段时间(上面睡眠5秒),我突然想要线程停止,于是调用interrupt方法

        thread.interrupt();

    }

    这里如果线程test2正在sleep,main线程这时候执行thread.interrupt(),那么会抛出异常,并且线程的中断标志会被清除,所以要做什么操作,就必须在catch里面搞了。

    或者这样写:

    public static void test3(){

        Thread thread=new Thread(()->{

            System.out.println("线程启动了");

            try {

                while(!Thread.currentThread().isInterrupted()){

                    Thread.sleep(1100);

                    System.out.println(Thread.currentThread().isInterrupted());//逻辑处理代码

                }

            } catch (InterruptedException e) {

                System.out.println("出异常了");

                //return ;//跳出方法,不再执行输出"线程结束了"的逻辑

            }

            System.out.println("线程结束了");

        },"test3");

        thread.start();

        try {

            Thread.sleep(1000 * 5);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        thread.interrupt();

    }

    五、线程的其他方法;

    sleep方法:

    public static native void sleep(long millis) throws InterruptedException;

    sleep是一个静态的native方法,它作用于当前线程,当调用sleep方法后,当前线程会让出CPU资源,但不会释放锁。

    Thread.sleep() 与 Thread.currentThread().sleep() 是一样的,只不过一个是用类直接调用静态方法, 一个是用类的实例调用静态方法。

    yield方法:

    public static native void yield();

    yield是一个静态的native方法,当调用yield方法后,当前线程表示会让出CPU,但到底让不让是随机的,或者是CPU决定的。

    join方法:

    调用join方法后,会让当前线程先执行,如果指定时间,则先执行指定时间,如果时间为0,则是让当前线程执行完为止。

    比如有线程T1和线程T2,线程T1里面有这样代码:T2.jion(0);

    那么T1会立刻停止让线程T2执行,一直到T2执行完,T1才会继续执行,即使T2里面调用了sleep方法让出CPU资源,T1也会等T2执行完才能获得CPU资源。

    我们来看一下源码:

    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");

        }

        //当参数为0,while循环会一直检查T2是否存活,存活的话T1就一直wait(0)。

        //注意:如果T1在执行T2.jion(0)时,T2还没start,那么T2就不存活,那么T2.jion(0)相当于白写了。

        if (millis == 0) {

            while (isAlive()) {

                wait(0);

            }

        } else {

            while (isAlive()) {

                long delay = millis - now;

                if (delay <= 0) {

                    break;

                }

                wait(delay);

                now = System.currentTimeMillis() - base;

            }

        }

    }

    好了,java线程就介绍到这里,如有问题请留言一起探讨。

    谢谢观看!

    相关文章

      网友评论

          本文标题:java线程

          本文链接:https://www.haomeiwen.com/subject/aoicmktx.html