跳至主要內容

Java 多线程

Zenghr大约 5 分钟Java

多线程理论基础

提示

本文用于理解学习多线程的理论基础以及代码实现

带着BAT大厂的面试问题去理解

  • 描述进程和线程的区别
  • 如何理解并发和并行的区别
  • 多线程的出现是要解决什么问题?
  • 线程不安全是指什么?举例说明
  • 实现线程安全有哪些方法?

进程的发明

最初的计算机只能执行一些特定的指令,用户输入一个指令,计算机做出一个操作,显然计算机大部分时间都在等待用户的指令,效率低下。

批处理操作系统:用户将一系列指令写下来,形成一个清单,然后交给计算机去读取并依次执行指令,这样批处理系统就出现了

多任务操作系统:虽然批处理系统的诞生提高了电脑的处理效率,但是一个操作执行完,才能执行另一个操作,当一个操作需要大量的IO操作,cpu 只能等待IO操作完成才能执行其他操作,很浪费 cpu 的资源。于是人们发明了进程,每个进程对应一定的内存空间,且只能使用自己内存,每个进程之间互不干扰。同时进程也保存了程序的运行状态,当程序暂停时,在下一次切换会根据暂停时保存的状态来恢复,并接着执行。

可以说 进程的出现提高了 cpu 的利用率

并发和并行的概念

并发是能够让操作系统从宏观上看起来同一时间段执行多个任务。 换句话说,进程让操作体统的并发成
为了可能,至此出现多任务操作系统
虽然并发从宏观上看有多个任务在执行,但在事实上,对于单核CPU来说,任意具体时刻都只有一个任
务在占用CPU资源。
操作系统一般通过CPU时间片轮转实现并发

  • 并发:在一段时间内多个进程轮流使用 cpu,多个进程形成并发
  • 并行:在同一时刻多个进程使用各自的 cpu ,多个进程形成并行,并行需要多个 cpu 支持

线程的出现

出现进程后,计算机性能 cpu 得到了巨大的提升,但是人们对实时性有了要求,因为一个进程在一个时间段内只能做一个事情,如果一个进程有多个子任务时,只能逐个得执行这些子任务,很影响效率。

e.g:当手机在获取网络图片时,此时用户点击了相机,因为 cpu 正在渲染图片,不能响应点击相机的操作,就出现了卡顿现象

所以人们为了解决实时性,发明了 线程,让一个线程执行一个子任务,这样一个进程就包含了多个线程;当用户点击相机时,暂停渲染图片,响应相机操作,响应完成再切换,就满足了对实时性的要求。

进程让操作系统的并发性成为了可能,而线程让进程的内部并发成为了可能

提示

注意:一个进程包含多个线程,但是这些线程共享进程占有的内存地址空间和资源。进程是操作系统进
行资源分配的基本单位(进程之间互不干扰),而线程是操作系统进行CPU调度的基本单位(线程间互
相切换)

进程和线程的区别

根本区别:进程是操作系统资源分配的基本单位,而线程是CPU调度和执行的基本单位

  • 开销:进程有独立的内存空间,程序之间切换开销比较大;线程可以看出是轻量级的进程,共享进程的资源,每个线程都有自己独立的栈空间,切换开销小
  • 内存分配方面:系统在运行的时候会为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系
    统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源
  • 包含关系:线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务

线程不安全实例

模拟买票过程,共有 5 张票,多线程模拟卖票的过程,以下代码模拟买票过程,操作结束后输出的数据错乱,有可能出现 票数为负数的结果

public class TicketRunnable implements Runnable {
    private int count = 5;
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if (count > 0) {
                count--;
                System.out.println(Thread.currentThread().getName() + "=> " + count);
            }
        }
    }
}

模拟买票

public class TicketRunnableExample {
    public static void main(String[] args) {
        TicketRunnable tr = new TicketRunnable();

        Thread th1 = new Thread(tr);
        Thread th2 = new Thread(tr);
        Thread th3 = new Thread(tr);


        th1.start();
        th2.start();
        th3.start();
        // 输出:
        // Thread-0=> 4
		// Thread-0=> 1
		// Thread-0=> 0
		// Thread-2=> 2
		// Thread-1=> 3
    }
}

实现线程安全

1. 互斥同步

  • synchronized: Java的关键字,同步机制由 Jvm 维护
  • ReentrantLock: Java 并发包中的互斥锁

如需要进一步了解线程互斥同步可以看:

Java 并发 - 多线程基础