Java Thread类的基本用法

        上一篇文章我们讲了Java关于线程的基本知识和如何创建一个简单的线程。Java使用Thread类来创建线程。多线程编程对于以前没有相关经验的人来说,最难理解的地方在于操作系统调度的执行过程。对于这样的代码:

    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("thread");
        });
        thread.start();

        System.out.println("main");
    }

        我们知道,java在启动的时候,已经自动创建了主线程,主线程调用main方法,main方法中又创建了一个线程(命名为”thread线程“),这个线程的任务是打印”thread“,同时,main方法中在创建一个新线程后的任务是打印”main“。当创建了thread线程并且start后,主线程和thread线程就是广泛上定义的并发(并发+并行)的过程,这个时候先打印”main“还是先打印”thread“是不确定的,是随机的,要看操作系统的随机调度。 同样着,谁先结束也是不确定的,也可能是主线程先结束,也可能是thread线程先结束。所以,为了让主线程等待thread线程结束,需要使用join方法。

创建线程是看start的顺序,但是具体的线程对应的任务什么时候执行,要看系统调度器。

Thread常见构造方法

        比如使用lambda表达式创建一个线程并且起名字。线程在操作系统内部是没有名字的,只有一个身份标志。但是在java中为了更加明白线程是谁,在jvm中给对应的Thread对象加了一个和内核中线程一一对应关系的名字,就像姓名和身份证号之间的关系。这个名字对于程序的执行没有关系。如果不手动创建,也会有默认的名字。

    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("thread");
        }, "thread线程");
    }

Thread常见的几个属性

        ID表示线程的身份。ID有好几个,这里的方法获取到的是线程在JVM中的身份标识。线程的标识有好几个,在内核的PCB上有标识,在用户态线程库中也有标识(操作系统系统的线程库)。

        getName()方法是获取到在Thread构造方法中传入的名字。

        getState()方法是一个比较关键的属性。在PCB里面有几个状态,在这里得到的状态是JVM设定的状态,比操作系统内置的状态更加丰富。

        isDaemon()方法,daemon称为”守护线程“,也可以称为后台线程。比如手机上的app,打开app,这个app就来到前台,当我们切换应用的时候,这个app就到后台了。线程也分成前台线程和后台线程,也可以设置线程的前后台。

        一个线程创建出现默认是前台线程,前台线程会阻止进程的退出,进程会保证所有的前台线程都执行结束,才会退出。对于后台线程,不会阻止进程的结束,进程退出的时候,不管线程是否执行结束。main线程是一个前台线程。

        使用setDaemon(布尔值)方法设置一个线程为后台,true表示后台线程。

    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "thread线程");

        thread.start();

        System.out.println(thread.getId());
        System.out.println(thread.getName());
        System.out.println(thread.getState());
        System.out.println(thread.getPriority());
        System.out.println(thread.isDaemon());
        System.out.println(thread.isAlive());
    }

上述操作都是获取到的一瞬间的状态,不是持续的状态。

start方法

再次强调,创建Thread实例并没有真正在操作系统内核中创建出线程(通过run方法,或Runnable,lambda中的内容安排任务),只有调用start,才是真正的在系统中创建出线程,才是真正的开始执行任务。

中断线程

        想让线程结束,只要让线程的入口方法执行结束,线程就随之结束了。线程的入口方法,对于主线程来说,main方法就是它的入口方法;对于其他线程来说,run()方法中要执行的任务或者Runnable,lambda中的内容就是它的入口方法。

中断线程,就是让线程尽快的把入口方法执行结束。有2种方法。

(1)直接手动创建标志位来区分线程是否要结束

//boolean表示线程标志位
    private static boolean isQuit = false;

    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            while (!isQuit) {
                System.out.println("线程运行中...");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("新线程执行结束");
        });
        thread.start();

        try {
            Thread.sleep(5000);
            System.out.println("控制新线程退出");
            isQuit = true;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

        能控制线程结束,主要是这个线程有一个循环,这个循环执行结束,就算结束了这个线程。这样的情况是非常常见的,很多时候创建线程都hi让线程完成一些比较复杂的任务,往往都有一些循环。如果线程本身执行很快,一下子就结束了,也就没有提前控制的必要了。

Thread内置了标志位,不需要手动创建标志位。

(2)Thread内置的标志位

    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            //静态方法(获取到当前线程的实例)
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("线程运行中...");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();

        try {
            Thread.sleep(5000);
            System.out.println("控制新线程退出!");
            thread.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

        其中,Thread.currentThread()方法是一个静态的方法,通过这个方法获取当前线程的实例(Thread对象),这个方法总会有一个线程会调用它。哪一个线程调用这个方法,就返回哪一个线程的Thread对象。

isInterrupted()方法是一个判定内置的标志位,默认是false,true表示线程要中断。现在运行,查看结果。

调用interrupt,产生异常,但是线程还是在继续运行。

interrupt方法的行为主要有两种:

(1)如果thread线程没有处于阻塞状态,这个时候interrupt就会修改内置的标志位,将isInterrupted的状态改为true;

(2)如果thread线程正在处于阻塞状态,这个时候interrupt就让线程内部能产生阻塞的方法,比如让sleep方法抛出InterruptedE xception异常。

        阻塞的意思就是程序停下来,不走了。更形象的例子是让你下载视频的时候,进度条在99%之前都是顺利的,但是到了99%进度条就停了下来,不走了。99%之前就是未阻塞状态,99%的时候就是阻塞状态,过了好长的时间,进度条才继续从99%开始走。

        能产生阻塞的方法比如sleep方法(实际中能产生阻塞的方法很多,这里只是用sleep方法模拟一下)抛出的异常正好被新创建的线程给捕获,然后执行了thread线程中的printStackTrace()方法。

        下面创建的线程大部分时间是在sleep阻塞状态,但是始终反复在阻塞状态和正常运行状态之间切换。

        这样捕获后,我们就可以自己控制线程的退出的行为了,可以立马退出(break),也可以等一会退出(收尾操作后break),也可以不退出。主线程发出“退出”的信息后,新线程自己决定如何处理退出的行为。

        需要注意的是,这里的InterruptedException不是让interrupt方法抛出异常,而是interrupt方法让线程中的sleep方法抛出异常。sleep方法抛出的异常被捕获,打印了信息。第二个sleep方法对应的是下面的捕获异常。

        还可以使用Thread.interrupted()方法来创建标志位。这个方法的标志位会自动清除,控制中断,标志位会先设为true,读取的时候就会读取到true,但是读取结束后这个标志位就会自动恢复成false,这个一般很少用。而代码中的方法状态不会恢复。

        有的同学可能奇怪,明明interrupt方法是在创建的线程后面,为啥还能被创建的线程捕获异常呢,实际上,前面我们说过,main线程调用main方法,main方法中创建新的线程(命名为thread线程)后,main线程和thread方法就是并发执行的,它们各自的任务什么时候被调度是不确定的,总有那么一个时间main线程中的interrupt方法被thread线程捕获,这个时候thread线程是什么状态, interrupt方法就执行什么样的操作,多线程的调度是随机的,这种随机调度是抢占式执行的。

join方法

        线程之间的执行顺序是完全随机的,要看系统的调度。写代码的时候,是比较讨厌这种随机性的,更需要手段能让顺序确定下来。join方法就是一种确定线程执行顺序的手段。不能确定两个线程开始的执行顺序,但是可以通过join来控制两个线程的结束顺序。

注意一下代码,当我们不注释join方法和注释join方法后运行结果有什么不同。

    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        thread.start();

//        try {
//            thread.join();
//        } catch (InterruptedException e) {
//           e.printStackTrace();
//        }

        for (int i = 0; i < 5; i++) {
            System.out.println("main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

        加了join方法后的程序会先让thread线程执行完了再执行main,而没有加join方法的则是并发执行。我们是无法干预调度器的行为,但是可以让main线程主动阻塞进行等待,等待thread执行结束了,main解除阻塞,才能继续往下执行。有多个join方法的时候,方法的先后顺序是没有关系的。

        join方法自身也会阻塞,Java线程但凡是阻塞的方法都可能抛出这个异常。

        现在的情况是我创建了两个线程,分别命名为thread1,thread2。想要thread1先执行结束后,再执行thread2,最后执行main线程的后序任务。这样,虽然调度器仍然是随机的,但是在形式上就是顺序执行的代码。当我们的代码需要先执行一组线程后,在执行另一组的线程,就可以这样写了。

public class Main {

    static Thread thread1 = null;
    static Thread thread2 = null;

    public static void main(String[] args) {

        thread1 = new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                System.out.println("thread1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread1.start();

        thread2 = new Thread(() -> {
            try {
                thread1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for (int i = 0; i < 3; i++) {
                System.out.println("thread2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        );
        thread2.start();

        try {
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        for (int i = 0; i < 3; i++) {
            System.out.println("main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

join有两种行为:

(1)如果被等待的线程还没有执行结束,那么直接阻塞等待;

(2)如果被等待的线程已经执行结束了,就直接返回。

        join方法不带参数的时候,就是死等,一直等待下去。方法带参数的时候,如果超过了等待的最大时间,就开始执行下一步的代码。一般写带参数的join方法。

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值