Java笔记——线程
线程
线程介绍
进程:运行的程序 线程:线程由进程创建,是进程的一个实体,一个进程可以拥有多个线程
单线程:同一时刻,只允许执行一个线程 多线程:同一时刻,可以执行多个线程
并发:同一时刻,多个任务交替执行,造成一种”貌似同时“的错觉。单核 cpu 实现的多任务就是并发。 并行:同一时刻,多个任务同时执行。多核 cpu 可以实现并行。
1 | |
线程使用
- 创建线程的两种方式
- 继承
Thread类,重写run方法 - 实现
Runnable接口,重写run方法
1 | |
java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时再用继承
Thread 类方法来创建线程显然不可能。此时可以通过实现 Runable
接口来创建线程。 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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61public class threadUse02 {
public static void main(String[] args) {
Dog dog = new Dog();
// dog.start(); 无法调用
Thread thread = new Thread(dog); // 设计模式【静态代理模式】
thread.start();
// 模拟Thread
Tiger tiger = new Tiger(); // 实现Runnable接口
new ThreadProxy(tiger).start();
}
}
class Animal {}
class Tiger extends Animal implements Runnable {
@Override
public void run() {
System.out.println("Tiger~~~")
}
}
// 线程代理类,模拟Thread
class ThreadProxy implements Runnable {
private Runnable target = null; // 属性, 类型是 Runnable
@Override
public void run() {
if (target != null) {
target.run(); // 动态绑定(运行类型Tiger)
}
}
public ThreadProxy(Runnable target) { // 构造器接收Runnable接口类型
this.target = target;
}
public void start() {
start0();
}
public void start0() {
run();
}
}
class Dog implements Runnable {
int cnt = 0;
@Override
public void run() { // 重写run方法,写具体的业务逻辑
while (true) {
System.out.println("dog ~~~ " + (++cnt));
try {
Thread.sleep(1000); // 休眠1s
} catch (InterruptedException e) { // ctrl + alt + t
e.printStackTrace();
}
if (cnt == 10) break;
}
}
}
当main线程启动一个子程序 Thread-0,
主线程不会阻塞,会继续执行。 所有线程结束后,进程才会挂掉。
Thread.currentThread().getName()
使用命令 jconsole监控线程执行情况
Java 中实现真正的多线程是start()中的
start0()方法(JVM调用),run()
方法只是一个普通方法 
继承Thread vs 实现Runnable的区别
- 从java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本质上没有区别,从jdk帮助文档可以看到Thread类本身就实现了Runnable接口
- 实现Runnable接口方式更加适合多个线程共享一个资源的情况并且避免了单继承的限制
1 | |
线程终止
- 当线程完成任务后,自动退出;
- 通过使用变量来控制run方法退出的方式停止线程,即通知方式
setLoop = false;
线程方法
1 | |
1 | |
用户线程和守护线程
- 用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
- 守护线程:一般是为工作线程服务的,当所有的用户线程执行结束,守护线程自动结束
- 常用的守护线程:垃圾回收机制
- 在
start()前执行线程名.setDaemen(true);
线程生命周期
- JDK 中用
Thread.State枚举表示了线程的几种状态
1 | |

Synchronized
线程同步机制
- 在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性。
- 也可以这里理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作。
1 | |
互斥锁

- Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。
- 每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
- 关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问
- 同步的局限性:导致程序的执行效率要降低
- 同步方法(非静态的)的锁可以是
this,也可以是其他对象(要求是同一个对象) - 同步方法(static静态的)的锁为当前类本身
类名.class。
死锁
多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程时一定要避免死锁的发生。
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
29
30
31
32
33
34
35
36
37
38
39public class DeadLock_ {
public static void main(String[] args) {
// 模拟死锁
DeadlockDemo A = new DeadlockDemo(true);
DeadlockDemo B = new DeadlockDemo(false);
A.start();
B.start();
}
}
class DeadlockDemo extends Thread {
static Object o1 = new Object(); // 保证多线程共享一个对象,这里使用static
static Object o2 = new Object();
boolean flag;
public DeadlockDemo(boolean flag) { // 构造器
this.flag = flag;
}
@Override
public void run() {
if (flag) {
synchronized (o1) { // 对象互斥锁
System.out.println(Thread.currenyThread().getName() + " 进入1");
synchronized(o2) {
System.out.println(Thread.currenyThread().getName() + " 进入2");
}
}
} else {
synchronized (o2) {
System.out.println(Thread.currenyThread().getName() + " 进入3");
synchronized(o1) {
System.out.println(Thread.currenyThread().getName() + " 进入4");
}
}
}
}
}
释放锁
- 下面操作会释放锁
- 当前线程的同步方法、同步代码块执行结束
- 当前线程在同步代码块、同步方法中遇到
break、return。 - 当前线程在同步代码块、同步方法中出现了未处理的
Error或Exception,导致异常结束 - 当前线程在同步代码块、同步方法中执行了线程对象的
wait()方法,当前线程暂停,并释放锁。
- 下面操作不会释放锁
- 线程执行同步代码块或同步方法时,程序调用
Thread.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁 - 线程执行同步代码块时,其他线程调用该线程的
suspend()方法将该线程挂起,该线程不会释放锁。