基础

线程相关概念

程序(program):是为完成特定任务,用某种语言编写的一组指令的集合(也就是我们写的代码)。

线程是由进程/另一个线程创建的,是进程的一个实体。
一个进程可以拥有多个线程。

单线程

多线程

并发

并行

线程使用

  • 创建线程的方式:
  1. 继承Thread接口
  2. 实现Runnable接口
  3. 使用Callable (用得很少)

继承Thread接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Cat extends Thread {
int times = 0;
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("meow~" + (++times));

if (times == 10) {
break;
}
}
}
}
  • 多线程的情况下,一个进程的所有线程都结束了的时候,这个进程才会挂掉。

  • Java中真正实现多线程的是start()中的start0(),而不是run()。开启线程的操作是start(),调用run()就只是简单地调用这个方法而已,相当于是串行化地执行,并没有开启一个线程。

  • (源码底层)在start()方法中会调用一个start0()方法,就是它这个方法真正实现了多线程。start0()是一个native方法,由JVM直接调用,底层由C/C++实现。start()调用完start0()后,该线程并不一定会立即执行,只是将线程变成了可运行状态。具体什么时候执行取决于CPU,由CPU统一调度。

实现Runnable接口

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
Class Test {
public static void main(String[] args) {
Cat cat = new Cat();
Thread thread = new Thread(Cat);
thread.start();
}
}

class Cat implements Runnable {
int times = 0;
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Meow~" + (++times));

if (times == 10) {
break;
}
}
}
}

底层使用了(静态)代理模式。为了方便理解,我们来写一个“极简的”Thread类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class ThreadProxy implements Runnable {
private Runnable target = null; // 定义属性,类型是Runnable

@Override
public void run() {
if (target != null) {
target.run();
}
}

public ThreadProxy(Runnable target) {
this.target = target;
}

public void start() {
start0(); // 真正实现多线程的方法
}
}

[继承Thread]与[实现Runnable]的区别

  1. 从Java的设计来看,通过继承Thread和实现Runnable接口这两种创建线程的方式在本质上没有区别,我们从JDK帮助文档中可以看到Thread类本身就实现了Runnable接口。底层都是在start()中调用了start0()。
  2. 如果没有特殊要求,建议采用实现Runnable接口的方式,这种方式适合多个线程共享一个资源的情况,而且避免了单继承的限制。

线程方法

线程生命周期

image-20240207225752554

synchronized

互斥锁

  1. Java中引入了对象互斥锁的概念来保证共享数据操作的完整性。
  2. 每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
  3. 关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问。
  4. 同步的局限性:导致程序的执行效率降低。
  5. 同步方法(非静态的)的锁对象可以是this,也可以是其他对象(要求是同一个对象)。默认是this。
  6. 同步方法(静态的)的锁对象默认是当前类.class。

死锁

多个线程都占用了对方的锁资源,但不肯相让,导致死锁。

下面的操作不会释放锁:

  1. 线程执行同步代码块/同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁。
  2. 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁。
    提示:应尽量避免使用suspend()和resume()来控制线程,方法不再推荐使用(已过时)。

进阶:

并发基础

线程

线程池

并发容器

JUC

executor

collections

locks

atomic(原子类)

tools(CountDownLatch, Exchanger, ThreadLocal, CyclicBarrier)