多线程基础

多线程的概念

进程是程序运行的实例。启动一个java程序其实就是启动了一个jvm的进程。进程是程序向操作系统申请资源(内存空间、文件句柄等)的基本单位。

线程是进程中可以独立执行的最小单位。

进程和线程的关系,可以比喻成一个项目组和组员的关系。项目组完成一个项目需要需求,开发,测试。这些往往都是并行的。需要需求,开发和测试人员协作完成。他们共享项目组的资源,如需求文档,功能代码等。

为什么需要线程

  1. 提高cpu的利用率。因为cpu的执行速率是远远高于io操作的。如果单线程运行,遇到io操作,系统会一直等待io完成再往下执行。这段io操作的时间内浪费了cpu的性能。
  2. 提高响应速率。为了用户的体验,在使用GUI软件时,一个慢的IO操作不至于会使软件”冻住”。
  3. 充分利用多核。对应已经普及的多核计算机,多线程可以重复利用其性能。

java中的线程

java中的线程是通过java.lang.Thread类来实现。一个Thread或其子类就是一个线程。

创建线程

创建执行线程的方式有三种:

Thread类的构造函数大概可以分为要Runnable和不需要Runnable两种。 Thread()和 Thread(Runnable)。所以可以根据这两种方式来创建一个线程。

继承Thread
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
   //1.继承Thread类
class ThreadDemo extends Thread{

//2.重写run方法。
public void run() {
// 业务逻辑
}
}

public static void main(String[] args) {
//new 一个Thread对象
Thread thread = new ThreadDemo();
//调用start方法启动一个线程。线程启动后会执行run方法。
thread.start();
}
实现Runnable接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
   //1.实现Runnable类
class RunnableDemo implements Runnable{
//2.实现run方法
public void run() {
// 业务逻辑
}

}

public static void main(String[] args) {
//new 一Runnable实现类
Runnable run = new RunnableDemo();
//通过Runnable构造Thread
Thread thread2 = new Thread(run);
//启动线程
thread2.start();
}
实现callable接口

上面两种线程都没有返回值,jdk1.5后出现了callable能创建具有返回值的线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
   //实现Callable接口
class CallableDemo implements Callable<String>{
//实现call方法
public String call() throws Exception {
return "callable";
}

}
public static void main(String[] args) {

//callable有两种使用方式:

//1.通过线程池
//创建线程池
ExecutorService threadService = Executors.newSingleThreadExecutor();
//提交callable任务
Future<String> submit = threadService.submit(new CallableDemo());
//阻塞获取返回值
String object = submit.get();
System.out.println(object);

//2. 通过FutureTask
FutureTask<String> task1 = new FutureTask<String>(new CallableDemo() {
});
Thread thread1 = new Thread(task1);
thread1.start();
System.out.println(task1.get());
}
三种创建线程方式的差异

callable和runnable除了callable可以有返回值,其他的都一样。实际上callable最终会被构造成一个实现Runnable接口的类。例如上面的FutureTask。

主要分析Runnable方式和继承Thread这两种方式。

  1. 继承Thread是通过继承的方式,实现Runnable是一种组合的方式。组合相对于继承更加低耦合和灵活。《Effictive java》中也提到复合优先于继承的观点。

  2. 实现Runnable的方式,容易线程间实现资源的共享。因为一个Runnable对象可以用来创建多个线程。对象的成员变量被多个线程共享。这也使得我们要注意线程安全问题。

线程的属性

属性 作用
编号(ID) 用于标识不同线程
名称(Name) 用于区分不同线程 默认:”thread-0” .可以设置。
线程类别(Daemon) 可以设置守护线程。主线程结束,守护线程也会结束
优先级(Priority) 可以设置线程优先级,1-10.默认5.

线程的状态

image

状态 说明
创建(New) 线程被创建未启动。java中就是还没调用start方法之前
可运行(Runnable) 该状态下有两种情况,一种是获取到cpu的时间片
阻塞(BLOCKED) 发起阻塞IO或者争取独占锁资源。该状态不占用cpu
等待(Waiting) 执行特定的方法会,等待其他线程唤醒
有时限等待(Timed Waiting) 和Waiting状态类似,但是Timed Waiting是有时限的,指定时间到会结束等待状态
消亡(Terminated) run方法执行完毕或者发生异常。