线程学习4
发布日期:2021-05-14 17:05:56 浏览次数:13 分类:精选文章

本文共 2486 字,大约阅读时间需要 8 分钟。

Java多线程并发编程安全问题解析

Run()和start()方法的区别

Run()方法属于普通方法,可以通过多次调用执行任务。而Start()方法是用来启动线程的,只能执行一次。当线程启动后,start()不会再次执行该方法。所以在需要多线程并发执行任务时,可以通过Run()方法来实现多次执行。

线程终止方式

线程可以通过以下方式终止:

  • 全局自定义变量:通过设置一个可见的全局自定义变量为false来表示终止。
  • 使用Interrupt()方法:使用thread.interrupt()来中断线程终止执行。
  • 使用提供的stop()方法:虽然stop()方法已经被废弃,但你仍可以通过该方法强制终止线程,不过这种方式可能导致资源泄漏,需要谨慎使用。
  • 线程状态

    线程在其生命周期中会经历多种状态:

  • 新建状态(new):线程被创建但尚未开始执行。
  • 运行中状态(runnable,包括running和ready):线程可以立即运行或正在准备运行。
  • 等待超时状态(TIMED_WAITING):线程等待获得某个条件超时后进入该状态。
  • 阻塞状态(BLOCKED):线程在等待I/O操作或其他系统资源完成。
  • 终止状态(terminated):线程已经正常终止或被强行终止。
  • 线程安全问题

    线程安全问题主要由以下几个方面引起:

  • CPU抢占式执行(排他性):多个线程同时执行会导致CPU时间片的抢占共享。
  • 非原子性:线程并发访问同一共享变量可能导致执行结果不一致。
  • 指令重排序:现代编译器对指令进行优化会导致多线程环境下的混乱。
  • 不可见性:工作内存和主内存之间的数据不一致可能导致错误。
  • 共享变量竞争:多个线程同时修改同一变量会导致数据不一致和race条件。
  • 线程的工作方式

    线程在执行任务时,首先会访问自己的工作内存区域中的变量和数据。当需要访问主内存中的共享变量时,线程会将工作内存无效化,并从主内存中读取变量,这样可以确保线程间的数据一致性。

    Volatile的使用

    Volatile是Java中解决线程安全问题的一种轻量级方法。它通过以下方式实现线程安全:

  • 禁止指令重排序:确保线程间的数据顺序一致性。
  • 可见性排序:线程工作内存与主内存保持同步,避免数据不一致。
  • 注意:Volatile不能解决原子性问题,需要结合其他机制(如Lock)配合使用。

    线程安全的解决方案

    1. Synchronized锁机制

    synchronized是Java中最常用的线程安全解决方案。它通过加锁和释放锁的方式实现线程排他性。以下是synchronized的实现原理和使用场景:

  • 加锁过程:获得锁对象,阻止其他线程进来。
  • 释放过程:线程执行完毕后释放锁,允许下一个线程获取锁。
  • synchronized默认使用的是非公平锁,执行效率较高,但在多核环境下可能导致线程饥饿。 Krishma 线程锁升级机制启动后,性能有了显著提升。

    2. ThreadLocal

    ThreadLocal用于解决每个线程都有自己的局部变量问题。它可以确保每个线程都有一个独立的变量空间,避免线程之间的干扰和冲突。

    3. Lock锁机制

    Lock提供了更高级别的锁控制,可以灵活配置锁策略(如公平锁、偏向锁等)。相比synchronized,Lock的锁是手动管理的,但提供了更大的灵活性。

    Synchronized的使用要点

  • synchronization块必须围绕一个锁对象(Monity器)进行操作。
  • Synchronized默认是非公平锁,但在单调 nepříETA 的情况下可以通过设置preferredLockThread目达到公平锁的效果。
  • 面试提问:Synchronized在JDK6之前的版本性能较差,主要原因在于锁膨胀。但从JDK6开始,Java采用锁升级机制,对大多数情况下的synchronized锁实现了轻量级化。
  • Java线程锁机制优化

    从JDK6开始,Java对synchronized进行了优化:

  • 偏向锁:第一个进入同步块的线程会获取轻量级锁。后续线程如果无需等待会自旋,避免thrashing。
  • 自旋锁:如果当前线程处于等待状态,但其他线程并未增加锁竞争强度时,线程可以自旋以减少等待延迟。
  • 重量级锁:当自旋无法获得锁时,线程会被排队等待,进入等待队列。
  • Lock锁的使用

    ReentrantLock是一个灵活的锁机制,可以手动显式地进行加锁和解锁操作。以下是Lock的典型使用方法:

    final Lock lock = new ReentrantLock();
    try {
    lock.lock();
    // 可以执行多次加锁,因为ReentrantLock支持递归锁
    number++;
    } finally {
    lock.unlock();
    }

    注意事项:

  • lock()方法必须放置在try语句之外。
  • 如果将lock()放置在try块内,可能会导致异常后未能正确解锁锁的情况。
  • ReentrantLock默认是非公平锁,但可以通过构造函数设置为公平锁。
  • Synchronized与Lock的对比

    特性 Synchronized Lock
    是否修饰代码块 Snow,可以修饰方法、代码块、静态方法 只可以修饰代码块
    自动加锁/解锁 Yes No
    锁的默认策略 Non-fair 可以是fair或者non-fair
    锁的实现机制 基于JVM中的Monity器 基于手动管理的locks
    是否提供锁的可重用性 Yes(可以多次加锁) Yes(支持递归)

    Volatile与Synchronized的区别

  • Volatile用于轻量级解决线程安全问题,主要针对可见性和原子性问题,而无法处理数据一致性问题。
  • Synchronized用于解决线程安全中排队问题,是一种全面的线程安全机制,可以实现数据一致性。
  • Note: Synchronized能够解决线程安全问题中的排队、可见性和原子性问题,而Volatile只能解决可见性问题,不能解决原子性问题。3165144

    上一篇:字符串转整数, Fibonacci数列,合法括号序列判断
    下一篇:线程3

    发表评论

    最新留言

    感谢大佬
    [***.8.128.20]2025年04月21日 10时11分13秒

    关于作者

        喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
    -- 愿君每日到此一游!

    推荐文章