Java线程安全小结

简介:

一、引言

Java开发过程中许多的时候都会涉及到各种各样的并发编程的问题,然而说起并发编程总需要格外的关注线程安全的问题。最近呢一直在基于Jstorm开发日志处理程序,由于Jstorm的特性,多线程随处可见。所以程序中也需要特别关注线程安全的问题。这次项目开发过程也遇到了不少的问题,通过不断的查询资料,不断的修改问题也确实收获了不少的知识。因此写一下最近关于并发编程的学习和总结。

二、多线程基础

在并发编程中,线程和锁起着至关重要的作用,要完成健壮的并发程序就必须要正确的使用线程和锁。在我的实战过程中我觉得有两点需要我们特别的注意。

  1. 对可变共享资源的操作
  2. 当前的锁对象

这两个地方不考虑清楚很容易写出线程不安全的代码。后面我会结合相关的示例来说明这一点。

术语

共享资源:能够被多个线程同时访问的资源
竞态条件:当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件
临界区:导致竞态条件发生的代码区

什么是线程安全?

在并发编程中我们最为关心的便是线程安全的问题。只有线程安全的程序在并发编程中才有用武之地。

那么,到底什么是线程安全呢?

当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么就称这个类时线程安全的。

上面这句话给出了线程安全的一个定义,然而不同的实际场景这个正确的行为可能各不相同,需要我们开发去主观的判断。我在实战过程中往往会在每个操作执行过程中去考虑此时共享资源所处的状态,然后不断的测试代码的运行,查看中间过程共享资源的真实状态,从而实现出线程安全的代码。

三、关于线程安全的实战

上面概括的讲了一些基础的知识,接下来纪录下最近工作中遇到的问题。在基础中我强调了两个注意事项,在接下来的示例中我们就能够发现弄清楚代码中对共享资源的操作和当前同步中获得的所对象是什么对写出线程安全的代码的重要性。

字符串常量作为对象锁

bingfa1
再次考虑两个注意点,第一,共享资源是什么;第二当前的对象锁是什么。
上图中的list集合即为共享资源,同步代码块中是对共享的操作。而当前的synchronized获取的锁则为字符串s。由于以上图中创建的字符串直接存储在jvm的常量池中,而且一个jvm中只保存一份。因此每个线程要想访问同步代码块中的代码都需要获得字符串s的锁。所以多个线程竞争同一个锁,从而到达每次只有一个线程可以访问同步代码块中的代码,即线程安全。

bingfa2
这次代码只改动了很小一部分,但是结果却是很出乎意料,现在这段代码编程了线程不安全的了。当时出现这个问题的时候我们也是有点意外。
遇到问题,首先判断哪些地方操作了共享资源list,检查整个代码发现只有图中的同步代码块中的代码。于是判断,共享资源的操作都在同步代码块中,共享资源的操作不是问题的关键。那边应该就是第二个条件对象锁出现了问题。对比图一的代码,图一之所以线程安全是因为所对象只有一个,每个线程去竞争同一个对象锁,从而保证同一时间只有一个线程执行同步代码块中的代码。所以猜测图二中的s对象不唯一,每个线程的锁住的对象各不相同,从而导致同一时间有多个线程执行了同步代码块中的代码。

接下来我们看看图二中字节码反编译后的结果。

bingfa3
图中可以看到编译器对字符串操作符进行了处理,在用+连接两个字符串时编译器最终会通过StringBuilder的append方法拼接,最后通过调用toString方法获取字符串,因此不难理解这里每个线程都会new出自己的StringBuilder对象,所以这里每个线程获取的对象锁都是自己的StringBuilder对象,并没有去竞争同一个对象锁。从而造成了线程不安全的产生。

上面这个示例主要是错误的锁对象的使用造成了线程不安全。

错用this关键字

在使用同步的过程中经常会看到synchronized(this)的写法。此时应该要明白这里this的真正代表的含义。这里this表示当前对象,而synchronized(this)则表示需要需要获取当前对象的锁,才行访问同步代码块中的对象。因此synchronized(this)只适用多线程共用同一个实例

public class SyncTest implements Runnable {
    private static final List<String> listA = new ArrayList<>();
    private static final List<String> listB = new ArrayList<>();
    private static final List<String> listC = new ArrayList<>();
    private static final List<String> listD = new ArrayList<>();

    @Override
    public void run() {
        while (true) {
            List<String> list = null;
            TestEnum testEnum = TestEnum.getEnumByTime();
            switch (testEnum) {
                case aaa:
                    list = listA;
                    break;
                case bbb:
                    list = listB;
                    break;
                case ccc:
                    list = listC;
                    break;
                case ddd:
                    list = listD;
                    break;
            }
          
            synchronized (this) {
            /*在多实例并发访问的时候会出现线程不安全的现象,
            因为每个实例竞争的对象锁不是同一个,但是static的变量所有实例共享
            */
                list.add(Thread.currentThread().getName() + ","
                        + System.currentTimeMillis());

                for (String a : list) {
                    System.out.println(a + ",size = " + list.size());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

四、总结

理论知识只有运用在实践过程中才能更加深刻更加透彻的被理解。在线程安全的问题出现时,搞清两个重点去排查问题的手段也非常的实用。并发编程的第一要务便是安全,在安全的基础之上才是性能。总之,最近基于Jstorm开发程序使自己多线程的理解又更加深了一些。在工作中深度学习点滴技术。

目录
相关文章
|
2天前
|
存储 监控 Java
【Java并发】【线程池】带你从0-1入门线程池
欢迎来到我的技术博客!我是一名热爱编程的开发者,梦想是编写高端CRUD应用。2025年我正在沉淀中,博客更新速度加快,期待与你一起成长。 线程池是一种复用线程资源的机制,通过预先创建一定数量的线程并管理其生命周期,避免频繁创建/销毁线程带来的性能开销。它解决了线程创建成本高、资源耗尽风险、响应速度慢和任务执行缺乏管理等问题。
89 60
【Java并发】【线程池】带你从0-1入门线程池
|
13天前
|
Java 程序员 开发者
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
72 14
|
16天前
|
安全 Java 程序员
Java 面试必问!线程构造方法和静态块的执行线程到底是谁?
大家好,我是小米。今天聊聊Java多线程面试题:线程类的构造方法和静态块是由哪个线程调用的?构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节有助于掌握Java多线程机制。下期再见! 简介: 本文通过一个常见的Java多线程面试题,详细讲解了线程类的构造方法和静态块是由哪个线程调用的。构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节对掌握Java多线程编程至关重要。
48 13
|
17天前
|
安全 Java 开发者
【JAVA】封装多线程原理
Java 中的多线程封装旨在简化使用、提高安全性和增强可维护性。通过抽象和隐藏底层细节,提供简洁接口。常见封装方式包括基于 Runnable 和 Callable 接口的任务封装,以及线程池的封装。Runnable 适用于无返回值任务,Callable 支持有返回值任务。线程池(如 ExecutorService)则用于管理和复用线程,减少性能开销。示例代码展示了如何实现这些封装,使多线程编程更加高效和安全。
|
17天前
|
人工智能 监控 安全
Java智慧工地(源码):数字化管理提升施工安全与质量
随着科技的发展,智慧工地已成为建筑行业转型升级的重要手段。依托智能感知设备和云物互联技术,智慧工地为工程管理带来了革命性的变革,实现了项目管理的简单化、远程化和智能化。
35 4
|
1月前
|
监控 Java
java异步判断线程池所有任务是否执行完
通过上述步骤,您可以在Java中实现异步判断线程池所有任务是否执行完毕。这种方法使用了 `CompletionService`来监控任务的完成情况,并通过一个独立线程异步检查所有任务的执行状态。这种设计不仅简洁高效,还能确保在大量任务处理时程序的稳定性和可维护性。希望本文能为您的开发工作提供实用的指导和帮助。
117 17
|
2月前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
1月前
|
缓存 安全 算法
Java 多线程 面试题
Java 多线程 相关基础面试题
|
2月前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
|
2月前
|
消息中间件 缓存 安全
Java多线程是什么
Java多线程简介:本文介绍了Java中常见的线程池类型,包括`newCachedThreadPool`(适用于短期异步任务)、`newFixedThreadPool`(适用于固定数量的长期任务)、`newScheduledThreadPool`(支持定时和周期性任务)以及`newSingleThreadExecutor`(保证任务顺序执行)。同时,文章还讲解了Java中的锁机制,如`synchronized`关键字、CAS操作及其实现方式,并详细描述了可重入锁`ReentrantLock`和读写锁`ReadWriteLock`的工作原理与应用场景。

热门文章

最新文章