Java笔记——线程

线程

线程介绍

进程:运行的程序 线程:线程由进程创建,是进程的一个实体,一个进程可以拥有多个线程

单线程:同一时刻,只允许执行一个线程 多线程:同一时刻,可以执行多个线程

并发:同一时刻,多个任务交替执行,造成一种”貌似同时“的错觉。单核 cpu 实现的多任务就是并发。 并行:同一时刻,多个任务同时执行。多核 cpu 可以实现并行。

1
2
3
Runtime runtime = Runtime.getRuntime();
// 获取当前电脑的cpu数量
int cpuNums = runtime.availableProcessors();

线程使用

  • 创建线程的两种方式
  1. 继承 Thread 类,重写 run 方法
  2. 实现 Runnable 接口,重写 run 方法
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
package com.company.thread_;

/**
* @author nessaj
* @date 2022/6/28 10:14
*/
public class threadUse01 {
public static void main(String[] args) {
Cat cat = new Cat();
cat.start(); // 启动线程-> 最终执行cat.run()
}
}

class Cat extends Thread {
int cnt = 0;

@Override
public void run() { // 重写run方法,写具体的业务逻辑
while (true) {
System.out.println("cat ~~~ " + (++cnt));
try {
Thread.sleep(1000);
} catch (InterruptedException e) { // ctrl + alt + t
e.printStackTrace();
}
if (cnt == 10) break;
}
}
}

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
61
public 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的区别

  1. 从java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本质上没有区别,从jdk帮助文档可以看到Thread类本身就实现了Runnable接口
  2. 实现Runnable接口方式更加适合多个线程共享一个资源的情况并且避免了单继承的限制
1
2
3
4
5
T t = new T("xxx"); // 一个资源被两个线程共享
Thread thread1 = new Thread(t);
Thread thread2 = new Thread(t);
thread1.start();
thread2.start();

线程终止

  1. 当线程完成任务后,自动退出;
  2. 通过使用变量来控制run方法退出的方式停止线程,即通知方式 setLoop = false;

线程方法

1
2
3
4
5
6
7
8
setName //设置线程名称,使之与参数name相同
getName //返回该线程的名称
start //使该线程开始执行;Java虚拟机底层调用该线程的start0方法
run //调用线程对象run方法
setPriority //更改线程的优先级 [10, 5, 1]
getPriority //获取线程的优先级
sleep //在指定的毫秒数内让当前正在执行的线程休眠(暂停执行
interrupt //中断线程, 一般用于唤醒正在休眠的线程
1
2
yield: 线程的礼让。让出cpu,让其他线程执行,但礼让的时间不确定, 所以也不一定礼让成功
join: 线程的插队。插队的线程一旦插队成功, 则肯定先执行完插入的线程所有的任务

用户线程和守护线程

  1. 用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
  2. 守护线程:一般是为工作线程服务的,当所有的用户线程执行结束,守护线程自动结束
  • 常用的守护线程:垃圾回收机制
  • start() 前执行 线程名.setDaemen(true);

线程生命周期

  • JDK 中用 Thread.State 枚举表示了线程的几种状态
1
2
3
4
5
6
7
8
9
10
public static enum Thread.State
extends Enum<Thread.state>

线程状态。线程可以处于以下状态之一:
NEW 尚未启动的线程处于此状态。
RUNNABLE 在Java虚拟机中执行的线程处于此状态。
BLOCKED 被阻塞等待监视器锁定的线程处于此状态。
WAITING 正在等待另一个线程执行特定动作的线程处于此状态。
TIMED_WAITING 正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
TERMINATED 已退出的线程处于此状态。

Synchronized

线程同步机制

  1. 在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性。
  2. 也可以这里理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作。
1
2
3
4
5
6
7
8
9
1. 同步代码块
synchronized (对象) { // 得到对象的锁,才能操作同步代码
// 需要被同步的代码;
}

2. synchronized 还可以放在方法声明中,表示整个方法为-同步方法
public synchronized void m (String name) {
// 需要被同步的代码
}

互斥锁

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

释放锁

  • 下面操作会释放锁
  1. 当前线程的同步方法、同步代码块执行结束
  2. 当前线程在同步代码块、同步方法中遇到 breakreturn
  3. 当前线程在同步代码块、同步方法中出现了未处理的 ErrorException ,导致异常结束
  4. 当前线程在同步代码块、同步方法中执行了线程对象的 wait() 方法,当前线程暂停,并释放锁。
  • 下面操作不会释放锁
  1. 线程执行同步代码块或同步方法时,程序调用 Thread.sleep()Thread.yield() 方法暂停当前线程的执行,不会释放锁
  2. 线程执行同步代码块时,其他线程调用该线程的 suspend() 方法将该线程挂起,该线程不会释放锁。

Java笔记——线程
https://nessaj7.github.io/2022/07/06/b708aef3b714.html
作者
kuhn
发布于
2022年7月6日
许可协议