多线程一定快吗?-- 不一定

在现代计算机编程中,多线程编程是一种常见的技术,被广泛认为能提高程序的执行效率。然而,多线程一定快吗?答案是否定的。多线程编程虽然有其显著的优点,但也存在一些潜在的缺陷和复杂性。下面我将深入探讨多线程编程的优缺点,以及在什么情况下多线程不一定能带来性能提升。

一、多线程的优点

  1. 提高处理器利用率:多线程允许程序在单个处理器上执行多个任务,从而更充分地利用处理器资源。特别是在多核处理器上,多线程可以将任务分配到不同的核心上并行执行,大大提高执行效率。

  2. 响应性更好:在用户交互的应用程序中,多线程可以让界面线程处理用户输入,同时后台线程处理数据运算,从而提高程序的响应性。

  3. 任务分解:将复杂的任务分解为多个线程可以使程序更容易管理和维护,因为每个线程可以专注于处理特定的子任务。

二、多线程的缺点

  1. 线程开销:创建和管理线程是有成本的。线程的切换和同步需要CPU时间和系统资源,这些开销可能抵消多线程带来的性能提升。

  2. 同步问题:多线程程序需要处理线程之间的数据同步问题。处理不当可能导致死锁、竞争条件等复杂的并发错误,从而影响程序的正确性和稳定性。

  3. 调试困难:多线程程序的调试和测试通常比单线程程序更复杂,因为线程的并发执行顺序难以预测,可能导致难以复现的错误。

  4. 资源争用:如果多个线程争夺同一资源(如内存或I/O设备),反而可能导致性能下降,因为线程之间需要频繁地等待和协调

三、什么时候多线程不一定快

在《并发编程的艺术》一书中,笔者曾给出了一段程序。

public class ConcurrencyTest {
    private static final long count = 10000l;

    public static void main(String[] args) throws InterruptedException {
        concurrency();
        serial();
    }

    private static void concurrency() throws InterruptedException {
        long start = System.currentTimeMillis();

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                int a = 0;
                for (long i = 0; i < count; i++) {
                    a += 5;
                }
            }
        });

        thread.start();

        int b = 0;
        for (long i = 0; i < count; i++) {
            b--;
        }

        long time = System.currentTimeMillis() - start;
        thread.join();
        System.out.println("concurrency :" + time + "ms, b = " + b);
    }

    private static void serial() {
        long start = System.currentTimeMillis();

        int a = 0;
        for (long i = 0; i < count; i++) {
            a += 5;
        }

        int b = 0;
        for (long i = 0; i < count; i++) {
            b--;
        }

        long time = System.currentTimeMillis() - start;
        System.out.println("serial:" + time + "ms, b = " + b + ", a = " + a);
    }
}
  1. 并发执行(concurrency方法)

    • 开始计时。
    • 启动一个新线程,在这个线程中进行计数操作,将a的值加5,共计10000次。
    • 主线程继续进行计数操作,将b的值减1,共计10000次。
    • 等待新线程完成。
    • 计算并发执行的总时间,并输出结果。
  2. 串行执行(serial方法)

    • 开始计时。
    • 在主线程中依次进行两个计数操作:首先将a的值加5,共计10000次;然后将b的值减1,共计10000次。
    • 计算串行执行的总时间,并输出结果

由于线程的启动和上下文切换需要时间,加上线程执行的同步开销,对于如此小规模的计算任务,并发执行未必会比串行执行更快。

实际执行中,你可能会看到并发执行时间与串行执行时间相近,甚至更慢。这正是因为线程的启动和管理本身带来了额外的开销,而这些开销在小规模任务中变得相对明显。

四、四种不适合多线程的任务

  1. I/O密集型任务:对于需要频繁进行磁盘读写或网络通信的I/O密集型任务,多线程可能不会显著提高性能,因为这些操作的瓶颈在于I/O速度而非CPU计算能力。

  2. 轻量级任务:如果任务本身很轻量,线程的创建和管理开销可能大于并行执行带来的性能提升。

  3. 全局锁的存在:如果程序中存在大量需要全局锁保护的数据结构,线程在访问这些数据时需要频繁地获取和释放锁,可能导致严重的性能瓶颈。

  4. 硬件限制:在单核处理器或CPU资源有限的环境下,多线程无法实现真正的并行执行,反而可能因为线程切换增加额外的开销。

结论

通过这段代码和结果分析可以看出,在计算量较小或操作较简单的情况下,并发执行未必比串行执行更快。多线程的优势主要在于能够处理I/O密集型任务或计算量大的任务,在这些情况下,多线程可以更好地利用CPU资源,提高程序的执行效率。

总之,是否使用多线程,应该根据具体的应用场景和任务特性来决定,而不是盲目追求并发。

 

-多个线程的几种实现方式包括:承Thread类,实现Runnable接口,实Callable接口,使用线程池。 - Java中的线程池是通过ThreadPoolExecutor类实现的。线程池维护了一个线程队列,可以复用线程,减少线程的创建和销毁开销,提了性能。 - 不建议直接使用Executors工具类创建线程池是因为它使用的是默认的线程池配置,可能导致线程数量过多,耗尽系统资源。OOM(Out of Memory)是由于创建过多的线程导致内存不足而发生的错误。 - Java内存模型(JMM)是一种规范,定义了多线程程序中各个变量的访问方式。它包括主内存和工作内存,通过控制变量的可见性和原子性来保证线程间的通信与同步。 - 并发编程可能会发生的问题包括:竞态条件、死锁、活锁、饥饿等。可见性问题指一个线程对共享变量的修改对其他线程是否可见,原子性问题指一个操作是否可以被中断或者同时执行。 - 并发编程下会出现原子性问题是因为多个线程同时修改同一个共享变量时,可能会导致不一致的结果。有序性问题是指程序执行的顺序与预期不符。可以使用synchronized关键字、Lock锁等来解决原子性和有序性问题。加上volatile关键字可以保证可见性,禁止指令重排序。 - 内存屏障是通过编译器和处理器来实现的,用于控制指令的执行顺序和内存的可见性。synchronized关键字会在进入和退出临界区时加上内存屏障。 - 单线程指令重排在不影响单线程执行结果的前提下进行优化,但可能会影响多线程的正确性。双重校验锁中使用volatile是为了禁止指令重排,确保多线程环境下的正确性。 - InnoDB的索引是通过B+树实现的。B+树具有树度低、查询效率、支持范围查询等优势。 - 聚簇索引与非聚簇索引的区别在于数据的存储方式。聚簇索引将数据行存储在叶子节点中,非聚簇索引则将叶子节点指向数据行。不是所有情况都需要取回表的数据,可以通过覆盖索引来避免回表操作。 - 最左前缀匹配指在使用联合索引时,只有从左到右使用索引的前缀部分才能发挥索引的作用。将区分度的字段放在最左边可以提索引的效率。唯一索引与普通索引的区别在于是否允许重复值。 - 排查慢SQL可以通过查看慢查询日志、使用性能分析工具(如EXPLAIN、SHOW PROFILE)、优化查询语句等方法。 - MySQL的锁包括行锁和表锁。行锁在并发性能上更好,但需要更多的系统资源,适合处理并发访问较的场景。表锁在资源消耗上较少,但并发性能相对较差,适合处理并发访问较低的场景。 - FOR UPDATE语句会对查询到的行加上行锁。 - 悲观锁是指在操作数据时始终假设会发生并发冲突,因此会将数据加锁以阻止其他事务的访问。乐观锁是指不加锁,而是通过版本号或时间戳等机制来判断是否发生冲突,减少了加锁的开销。悲观锁适用于并发冲突较多的场景,乐观锁适用于并发冲突较少的场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值