多线程

多线程的概念

多线程是一种用于实现并发执行的计算机编程技术,这允许单个程序创建和管理多个线程,以同时执行多个任务或执行块。

  • 并发:在同一时刻,有多个指令在单个CPU上交替执行
  • 并行:在同一时刻,有多个指令在CPU上同时执行

线程的生命周期

  • 新建(New):创建后尚未启动的线程处于这个状态
  • 就绪(Runnable):调用start()方法后,线程进入就绪状态,此时线程只是开辟了内存空间,正在等待被线程调度器选中获得对CPU的使用权
  • 运行(Running):线程获得CPU权限进行执行
  • 阻塞(Blocked):线程在等待监视器锁(即等待 synchronized 锁)时,会进入阻塞状态
  • 等待(WAITING):线程等待另一个线程通知或中断时,会进入等待状态。进入这个状态的线程需要等待其他线程调用notify()notifyAll()方法
  • 超时等待(TIME_WAITING):线程在指定的等待时间内等待另一个线程的通知时,会进入超时等待状态。这通常发生在调用了带有指定等待时间的wait(long timeout)join(long millis)sleep(long millis)LockSupport.parkNanos()等方法时
  • 死亡(Terminated):线程执行结束或者因为异常退出的状态

多线程的实现方式

  • 继承Thread类的方式进行实现
    • 自己定义一个类继承Thread
    • 重写run方法
    • 创建子类对象,并启动线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//定义一个类继承Thread
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName());
}
}
}
//开启线程
public class Main {
public static void main(String[] args) {
//创建MyThread对象
MyThread t1 = new MyThread();
//设置线程名字
t1.setName("线程1");
//可以合并写:
//MyThread t1 = new MyThread("线程1")
//开启线程
t1.start();
}
}
  • 实现Runnable接口的方式进行实现
    • 自己定义一个类实现Runnable接口
    • 重写run方法
    • 创建自己定义的类的对象
    • 创建一个Thread类的对象,并开启线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//定义一个类实现Runnable接口
public class MyRun implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//获取到当前线程的对象
Thread t = Thread.currentThread();
System.out.println(t.getName());
}
}
}
//开启线程
public class Main {
public static void main(String[] args) {
//创建MyRun对象
MyRun mr = new MyRun();
//创建线程对象
Thread t1 = new Thread(mr);
//给线程设置名字
t1.setName("线程1");
//开启线程
t1.start();
}
}
  • 利用Callable接口和Future接口方式实现
    • 特点:可以获取多线程运行的结果
    • 自己定义一个类实现Callable接口
    • 重写call(有返回值,表示多线程运行的结果)
    • 创建自己定义的类的对象(表示多线程要执行的任务)
    • 创建FutureTask的对象(管理多线程运行的结果)
    • 创建Thread类的对象,并启动(表示线程)
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
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
}
}

public class Main {
public static void main(String[] args) {
//创建MyCallable对象(表示多线程要执行的任务)
MyCallable mc = new MyCallable();
//创建FutureTask对象(管理多线程运行的结果)
FutureTask<Integer> ft = new FutureTask<> (mc);
//创建线程的对象
Thread t1 = new Thread(ft);
//启动线程
t1.start();
//获取多线程运行的结果
Integer result = ft.get();
System.out.println(result);
}
}

三种实现方式的比较

优点 缺点
继承Thread类 编程比较简单,可以直接使用Thread类中的方法 可以扩展性较差,不能再继承其他类
实现Runnable接口 扩展性强,实现该接口的同时还可以继承其他的类 编程相对复杂,不能直接使用Thread类中的方法
实现Callable接口 扩展性强,实现该接口的同时还可以继承其他的类 编程相对复杂,不能直接使用Thread类中的方法

多线程中常用的成员方法

  • 返回此线程的名称

String getName()

  • 设置线程的名字(构造方法也可以设置名字)

void setName(String name)

  • 获取当前线程的对象

当JVM虚拟机启动之后,会自动的启动多条线程,其中有一条线程就叫做main线程,它的作用就是去调用main方法,并执行里面的代码

static Thread currentThread()

  • 让线程休眠指定的时间,单位为毫秒

static void sleep(long time)

  • 设置线程的优先级

1 -> 最小

10 -> 最大

5 -> 默认

setPriority(int newPriority)

  • 获取线程的优先级

final int getPriority()

  • 设置守护线程

当其他的非守护线程完毕之后,守护线程回陆续结束

final void setDaemon(boolean on)

  • 出让线程/礼让线程(礼让不一定会成功)

public static void yield()

  • 插入线程/插队线程(插队一定会成功)

public static void join()

  • 中断线程

public static void interrupt()

通知线程退出

可以通过使用变量来控制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
public class MyThread extends Thread {
private boolean loop = true;
@Override
public void run() {
while (loop) {
System.out.println("Running...");
}
}

//修改loop值的方法
public void setLoop(boolean loop) {
this.loop = loop;
}
}

public class Main {
public static void main(String[] args) {
MyThread t1 = new MyThread();

t1.setName("线程1");
t1.start();

Thread.sleep(1000);
//通过修改loop来控制线程的终止
t1.setLoop(false);
}
}

等待唤醒机制

  • 当前线程等待,直到被其他线程唤醒

void wait()

  • 随即唤醒单个线程

void notify()

  • 唤醒所有线程

void notifyAll()

线程同步机制

在多线程编程中,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性。

同步具体方法-Synchronized

  1. 同步代码块
1
2
3
synchronized(对象) {//得到对象的锁,才能操作同步代码
//需要被同步的代码
}
  1. 同步方法
1
2
3
public synchronized void method(String name) {
//需要被同步的代码
}

互斥锁

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

实现步骤

  1. 先分析上锁的代码
  2. 选择同步代码块或同步方法
  3. 要求多个线程的锁对象为同一个即可

死锁

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

释放锁

下列操作会释放锁:

  1. 当前线程的同步方法,同步代码块执行结束
  2. 当前线程在同步代码块,同步方法中遇到break,return
  3. 当前线程在同步代码块,同步方法中出现了未处理的Error或Exception,导致异常结束
  4. 当前线程在同步代码块,同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁

下列操作不会释放锁:

  1. 线程执行同步代码块或同步方法时,程序调用了Thread.sleep(),Thread.yield()方法暂停当前线程的执行,不会释放锁

线程池

线程池的主要核心原理:

  1. 创建一个池子,池子中是空的

  2. 提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子,下回再次提交任务时,不需要创建新的线程,直接复用已有的线程即可

  3. 但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待

  • 创建一个没有上限的线程池
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MyRun implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//获取到当前线程的对象
Thread t = Thread.currentThread();
System.out.println(t.getName());
}
}
}
public class Main {
public static void main(String[] args) {
//创建没有上限的线程池
ExecutorService pool = Executors.newCachedTreadPool();
//提交任务
pool.submit(new MyRunnable());

//销毁线程池
pool.shutdown();
}
}
  • 创建有上线的线程池
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MyRun implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//获取到当前线程的对象
Thread t = Thread.currentThread();
System.out.println(t.getName());
}
}
}
public class Main {
public static void main(String[] args) {
//创建没有上限的线程池
ExecutorService pool = Executors.newFixedThreadPool(3);
//
pool.submit(new MyRunnable());

//销毁线程池
pool.shutdown();
}
}