什么是线程

在计算机中,一个任务就是一个进程。打开任务管理器,我们可以看见多个进程。如浏览器,QQ。线程是进程的再划分,是进程的一部分。一个进程可以只有一个线程(主线程),也可以有多个线程。拿浏览器举例,打开浏览器的多个网页,一个网页在播放音乐,一个网页在播放视频,一个网页在浏览博客。每个网页有不同的分工,这些浏览器的每个子任务就是一个线程。

线程是更轻量的进程,创建一个线程的消耗要低于创建一个进程,且同一进程的不同线程之间可以共享资源,效率更高。通过多线程可以更好地实现并发编程。

进程vs线程

  • 进程包含线程,进程是线程的一部分,一个进程中至少包含一个线程。
  • 进程与进程之间不能共享资源,同一个进程的线程之间可以共享资源。
  • 进程是系统分配资源的最小单位,线程是系统调度执行的最小单位。
  • 多进程稳定性高于多线程,一个进程的崩溃不会影响其他线程;多线程下一个线程的崩溃可能影响该进程。

创建线程

一个线程至少包含一个进程,main()就是一个线程,我们还可以在里面创建多个线程。

1.继承Thread类,重写run()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Main {
public static void main(String[] args) {
Thread t = new MyThread();
t.start(); // 启动新线程
}
}

class MyThread extends Thread {
@Override
public void run() {
System.out.println("start new thread!");
}
}

2.创建Thread实例,实现Runnable接口

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Main {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start(); // 启动新线程
}
}

class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("start new thread!");
}
}

3.其他变形

  • 匿名内部类创建Thread子类对象
1
2
3
4
5
6
Thread t1 = new Thread() {
@Override
public void run() {
System.out.println("使用匿名类创建 Thread 子类对象");
}
};
  • 匿名内部类创建Runnable子类对象
1
2
3
4
5
6
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("使用匿名类创建 Runnable 子类对象");
}
});
  • lambda表达式创建Thread子类对象
1
2
3
Thread t4 = new Thread(() -> {
System.out.println("使用匿名类创建 Thread 子类对象");
});

Thread方法

常见构造方法

构造方法
Thread() 分配一个新的 Thread对象。
Thread(Runnable target) 分配一个新的 Thread对象。
Thread(Runnable target, String name) 分配一个新的 Thread对象,name为新线程的名字。
Thread(String name) 分配一个新的 Thread对象,name为新线程的名字。

启动一个线程.start()

Thread对象对创建后,并没有启动线程,执行该指令后才会真正创建一个线程。即内核中创建了一个内存控制块PCB。

中断一个线程

方法1:自定义变量作为标志位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//自定义变量flag作为标志位
static volatile boolean flag = false;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(){
@Override
public void run() {
while(!flag){
System.out.println("线程正在运行");
}
}
};
t.start();
Thread.sleep(1000); //使线程进入休眠状态
flag = true;
}

方法2:interrupted()方法中断线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ThreadDemo3 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(){
@Override
public void run() {
while(!Thread.currentThread().isInterrupted()){
System.out.println("线程运行中");
}
}
};
t.start();
Thread.sleep(1000);
t.interrupt();
}
}

Thread.currentThread().isInterrupted()获取当前线程是否被中断;interrupted()向线程发出中断请求。

Thread收到通知方式有两种:①当线程因wait/sleep/join等方法处于堵塞状态时,以 InterruptedException 异常的形式通知。②isInterrupted()标志位置为true.

等待一个线程.join()

执行该指令,会使该线程执行结束后再执行下一线程,即将并发编程变成了串行。比如用某软件下载一部电影,可以边下载边观看,当执行该指令后,就要等下载完后再看了~

休眠一个线程sleep()

让线程进入休眠状态,进入休眠状态的指令,他的属性isAlive()为Timed Waiting。线程状态会稍后介绍到。

线程状态

注意,这里我们说的是java线程的六种状态。利用isAlive属性可以观察线程的状态,线程有以下6种状态:

  • New:新创建的线程,但未执行start(),线程没有存活;
  • Runnable:运行中的线程,正在执行run()方法的Java代码;
  • Blocked:运行中的线程,因为某些操作被阻塞而挂起;
  • Waiting:运行中的线程,因为某些操作在等待中,如执行wait()方法;
  • Timed Waiting:运行中的线程,因为执行sleep()方法正在计时等待;
  • Terminated:线程结束,线程没有存活。

线程状态转换

线程被创建出来,处于New状态。调用start方法后,处于Runnable状态。当线程获取不到锁时,处于Blocked状态,直到获取到锁后进入Runnable状态。运行的线程调用wait或join方法后会进入Waiting状态,直到唤醒通知后进入Runnable状态。调用带有超时参数的方法后会进入Timed Waiting状态,直到时间到后进入Runnable状态。运行的线程执行完run方法或出现异常后进入Terminated状态。

与操作系统中的进程状态不同,java没有就绪态。操作系统中,一个进程调用start后并没有直接运行,而是进入就绪态,当获取到cpu时间片后进入运行态。我们可以认为在java中,就绪态和运行态同属于Runnable。