线程入门
线程的几个属性
线程的属性包括线程的编号(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方法
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中的函数式编程可以将方式二精简成方式三;
注意:
- 是调用Thread的start方法才是启动线程,直接调用run方法只是方法调用,并不会新开一个线程;
- start方法不可多次调用,在调用start方法后再次调用会抛出
IllegalThreadStateException
异常;
Runnable
实现Runnable的run方法
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
- Future
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";
}
}
- FutureTask
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接口
参考:
- 《Java多线程编程实战指南-核心篇》
- RedSpider社区