线程入门

该图片由DreamyArt在Pixabay上发布

线程的几个属性

线程的属性包括线程的编号(ID),名称(Name),线程类别(Daemon),和优先级(Priority);

属性 属性类型及用途 只读属性 重要注意事项
编号(ID) 类型:long。用于标识不同的线程,不同线程有不同编号 某个编号的线程运行结束后,该编号可能被后续创建的线程使用,不同线程拥有的编号虽然不同,但是这种编号的唯一性只在Java虚拟机的一次运行有效。也就是说重启一个Java虚拟机后,某些线程的编号可能与上次Java虚拟机运行的某个线程的编号一样,因此该属性的值不适合用作唯一标识。
名称(Name) 类型:String,区分不同线程,默认格式:“Thread-线程编号”,如“Thread-0” Java并不禁止同名,设置线程名称属性有助于代码调试和问题定位。
线程类别(Daemon) 类型:boolean,值为true标识相应的线程为守护线程,否则为用户线程,该属性的默认值与相应线程的父线程的该属性值相同。 该属性必须在该线程启动之前设置,即对setDaemon方法的调用必须在start方法的调用之前;否则会抛出IllegalThreadStateException异常,负责一些关键任务处理的线程不适合设置为守护线程。
优先级(Priority) 类型:int。该属性本质上是给线程调度器的提示,用于表示应用程序希望哪个线程得到优先运行。Java定义了1-10的10个优先级。默认值一般为5; 一般使用默认优先级即可。不恰当的设置该属性可能导致严重的问题(线程饥饿)

线程创建的几种方式

Thread

继承Thread,重写run方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

public class ThreadDemo extends Thread {

    @Override
    public void run() {
        System.out.println("Thread Demo One");
    }

    public static void main(String[] args) {
        //方式一
        ThreadDemo threadDemo=new ThreadDemo();
        threadDemo.start();
        //方式二
        new Thread(){
            @Override
            public void run() {
                System.out.println("Thread Demo Two");
            }
        }.start();
        //方式三
        new Thread(() -> System.out.println("Thread Demo Three")).start();
    }
}

说明: 暂时列了这三种方式,其中方式二,三是一样的代码,Java8中的函数式编程可以将方式二精简成方式三;

注意:

  1. 是调用Thread的start方法才是启动线程,直接调用run方法只是方法调用,并不会新开一个线程;
  2. start方法不可多次调用,在调用start方法后再次调用会抛出IllegalThreadStateException异常;

Runnable

实现Runnable的run方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

public class RunnableDemo {
    public static void main(String[] args) {
        new Thread(new RunnableDemoRun()).start();
    }

}
class RunnableDemoRun implements Runnable{

    @Override
    public void run() {
        System.out.println("Runnable Demo");
    }
}

Callable

  1. Future
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

public class CallableDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //构造线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        //创建实例
        CallableDemoRun callableDemoRun = new CallableDemoRun();
        //执行
        Future<String> submit = executorService.submit(callableDemoRun);
        // 注意调用get方法会阻塞当前线程,直到得到结果。
        // 所以实际编码中建议使用可以设置超时时间的重载get方法。
        System.out.println(submit.get());
    }
}
class CallableDemoRun implements Callable<String> {
    @Override
    public String call() throws Exception {
        // 沉睡一秒
        Thread.sleep(1000);
        System.out.println("Callable Demo Run");
        return "SUCCESS";
    }
}

  1. FutureTask
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

public class CallableDemoOne {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        //实例化线程
        CallableDemoRunOne callableDemoRunOne = new CallableDemoRunOne();
        //封装进FutureTask
        FutureTask<String> stringFutureTask = new FutureTask<>(callableDemoRunOne);
        //执行
        executorService.submit(stringFutureTask);
        System.out.println(stringFutureTask.get());
    }
}
class CallableDemoRunOne implements Callable<String>{
    @Override
    public String call() throws Exception {
        // 沉睡一秒
        Thread.sleep(1000);
        System.out.println("Callable Demo Run");
        return "SUCCESS";
    }
}

问题

Thread类的几个常用方法

  • currentThread():静态方法,获取当前正在执行的线程对象的引用

  • start():开始执行线程的方法,Java虚拟机会执行线程内的run方法

  • yield():yield在英语里有放弃的意思,同样,这里的yield()指的是当前线程愿意让出对当前处理器的占用。这里需要注意的是,就算当前线程调用了yield()方法,程序在调度的时候,也还有可能继续运行这个线程的;

  • sleep():静态方法,使当前线程睡眠一段时间;

  • join():使当前线程等待另一个线程执行完毕之后再继续执行,内部调用的是Object类的wait方法实现的;

sleep与wait有什么区别

  • sleep方法是Thread的一个静态类方法,wait方法是object的实例

  • 调用sleep方法过程中,线程不会释放当前持有的锁资源;而调用wait方法,会使线程处于等待(阻塞)状态,并且释放所持有的对象的锁;

  • 调用sleep()方法需要指定时间,到期后线程会自动唤醒;而调用wait方法的线程需要手动唤醒;

Thread类与Runnable接口的比较

  • 由于Java‘单继承,多实现’的特性,Runnable接口使用起来比Thread更灵活;

  • Runnable接口出现更符合面向对象,将线程单独进行对象的封装。

  • Runnable接口出现,降低了线程对象和线程任务的耦合性。

  • 如果使用线程时不需要使用Thread类的诸多方法,显然使用Runnable接口更为轻量。

Callable,Runnable和Thread区别

  • Runnable与Callable是一个接口,Thread是Runnable的实现类

  • Runnable和Thread均无返回值,Callable有返回值

  • Runnable和Thread线程内不能抛出异常,需内部处理异常,Callable可抛出异常

Future与FutureTask

  • Future是接口类,FutureTask是实现的RunnableFuture接口的,而RunnableFuture接口同时继承了Runnable接口和Future接口

参考:

  1. 《Java多线程编程实战指南-核心篇》
  2. RedSpider社区
坚持原创技术分享,您的支持将鼓励我继续创作!