关于 java 多线程,你需要知道的一些基础知识

你好,我是 shengjk1,多年大厂经验,努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注!你会有如下收益:

  1. 了解大厂经验
  2. 拥有和大厂相匹配的技术等

希望看什么,评论或者私信告诉我!


一、前言

俗话说的好,学好数理化,走遍天下都不怕。也就是说总有一些基础中的基础,学好了这些就可以起到事半功倍的效果

二、线程基础

2.1 CPU时间片

CPU时间片是操作系统中一个重要概念,它指的是操作系统分配给每个进程在CPU上执行的时间长度。

在多任务处理系统中,CPU时间片用于实现多进程之间的轮转调度,确保每个进程都有机会执行并且不会长时间占用CPU。

CPU分配给各个线程/进程的时间片非常短,CPU通过不停地切换线程/进程执行(而这种切换如果太多会严重影响多线程程序的执行效率),让我们感觉多个线程是同时执行的,时间片一般是几十毫秒(ms)

2.2 减少上下文切换

既然多线程上下文切换,会消耗导致程序执行效率降低,那么如何减少切换呢?
减少上下文切换的方法有无锁并发编程、CAS算法、使用最少线程和使用协程

1. 无锁并发编程
多线程竞争锁时,会引起上下文切换,所以多线程处理数据时,可以用一些办法来避免使用锁,如将数据的D按照Hsh算法取模分段,不同的线程处理不同段的数据。

2. CAS算法
Java的Atomic包使用CAS算法来更新数据,而不需要加锁。

在x86架构下,底层通常使用CMPXCHG指令来实现CAS操作。以下是在x86汇编中简单示例:

lock cmpxchg [destination], register
  • lock: 这个前缀指示CMPXCHG操作是原子的,即在执行期间不会被中断。
  • cmpxchg: 比较目标内存位置的值和寄存器中的值,如果相等则将寄存器中的值写入目标内存位置,否则不执行任何操作。
  • [destination]: 是要操作的目标内存位置。
  • register: 是一个寄存器,用来存放要写入目标内存位置的值。

在实际执行时,CPU会比较目标内存地址中的值和寄存器的值,如果相等,则将寄存器的值写入目标内存地址,并返回成功。否则不进行写入操作,返回失败。整个过程是原子的,不会被其他线程的操作打断。

3. 使用最少线程
避免创建不需要的线程,比如任务很少,但是创建了很多线程来处理,这样会造成大量线程都处于等待状态。

4. 协程
在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。

2.3 死锁

死锁是指两个或多个进程(或线程)相互等待对方持有的资源,导致它们都无法继续执行的情况。在死锁状态下,没有任何一个进程能够继续执行,系统变得无响应。

public class DeadlockExample {
    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            synchronized (lock1) {
                System.out.println("Thread 1: Holding lock 1...");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {}
                System.out.println("Thread 1: Waiting for lock 2...");
                synchronized (lock2) {
                    System.out.println("Thread 1: Holding lock 1 and lock 2...");
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (lock2) {
                System.out.println("Thread 2: Holding lock 2...");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {}
                System.out.println("Thread 2: Waiting for lock 1...");
                synchronized (lock1) {
                    System.out.println("Thread 2: Holding lock 1 and lock 2...");
                }
            }
        });

        thread1.start();
        thread2.start();
    }
}

如何避免死锁

1. 避免一个线程同时获取多个锁。

2. 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。

3. 尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。

4. 对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。

2.4 资源限制

这里的资源限制包括硬件资源限制和软件资源限制。在进行多线程编程时,要考虑资源限制。

举个例子:某机器的宽带只有 1Mb/s,某资源下载速度为 500KB/s,你用 10个线程,下载速度也不会变成 5000KB/s,最多为 1Mb/s。

另外,如果将代码改成多线程,但由于资源限制,导致其仍然以单线程方式执行,那这个程序会更慢,因为相比于单线程,增加了上下文切换和资源调度的时间。这一点需要额外注意!

三、其他

3.1 CPU时间片优势

虽然线程或进程切换会消耗一定的时间和资源,但使用CPU时间片的方式有一些重要的原因和优势,以确保系统能够高效地运行和满足多任务处理的需求:

  1. 公平性和资源共享:使用时间片可以确保系统中的每个线程或进程都能够获得公平的CPU时间,并避免其中一个线程长时间独占 CPU 而导致其他任务得不到执行的情况。
  2. 多任务处理:时间片允许操作系统在不同的线程或进程之间进行切换,从而实现多任务处理。这种方式使得多个任务能够交替执行,提高系统的并发性能。
  3. 响应速度:通过时间片轮转,每个线程都有机会获得 CPU 时间,这有助于系统快速响应用户操作和事件,提高系统的整体响应速度。
  4. 资源利用率:通过合理设置时间片长度和调度算法,可以有效利用 CPU 资源,确保 CPU 在每个时间片内都被充分利用。
  5. 避免饥饿现象:使用时间片可以防止某些线程长时间无法获取 CPU 资源而产生饥饿现象,保证系统的公平性和稳定性。 尽管线程或进程的切换会有一定的开销,但通过合理优化调度算法和提高上下文切换效率等方式,可以尽量减少这种开销,从而确保系统在提供公平性、多任务处理、快速响应等方面具有较高的效率和性能。因此,CPU 时间片机制仍然是一种非常有效的多任务处理方式,被广泛应用于操作系统中。

四、总结

多线程编程在提高系统性能和并发性方面具有重要作用,但也面临着一些挑战,如上下文切换的开销、死锁的风险和资源限制等。通过合理使用CPU时间片、减少上下文切换、避免死锁和考虑资源限制,可以提高多线程编程的效率和稳定性。

  • 25
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shengjk1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值