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开发程序使自己多线程的理解又更加深了一些。在工作中深度学习点滴技术。

目录
相关文章
|
12天前
|
监控 安全 Java
在 Java 中使用线程池监控以及动态调整线程池时需要注意什么?
【10月更文挑战第22天】在进行线程池的监控和动态调整时,要综合考虑多方面的因素,谨慎操作,以确保线程池能够高效、稳定地运行,满足业务的需求。
91 38
|
9天前
|
安全 Java
java 中 i++ 到底是否线程安全?
本文通过实例探讨了 `i++` 在多线程环境下的线程安全性问题。首先,使用 100 个线程分别执行 10000 次 `i++` 操作,发现最终结果小于预期的 1000000,证明 `i++` 是线程不安全的。接着,介绍了两种解决方法:使用 `synchronized` 关键字加锁和使用 `AtomicInteger` 类。其中,`AtomicInteger` 通过 `CAS` 操作实现了高效的线程安全。最后,通过分析字节码和源码,解释了 `i++` 为何线程不安全以及 `AtomicInteger` 如何保证线程安全。
java 中 i++ 到底是否线程安全?
|
14天前
|
Java 调度
[Java]线程生命周期与线程通信
本文详细探讨了线程生命周期与线程通信。文章首先分析了线程的五个基本状态及其转换过程,结合JDK1.8版本的特点进行了深入讲解。接着,通过多个实例介绍了线程通信的几种实现方式,包括使用`volatile`关键字、`Object`类的`wait()`和`notify()`方法、`CountDownLatch`、`ReentrantLock`结合`Condition`以及`LockSupport`等工具。全文旨在帮助读者理解线程管理的核心概念和技术细节。
31 1
[Java]线程生命周期与线程通信
|
4天前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
5天前
|
Java 开发者
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
23 4
|
5天前
|
消息中间件 供应链 Java
掌握Java多线程编程的艺术
【10月更文挑战第29天】 在当今软件开发领域,多线程编程已成为提升应用性能和响应速度的关键手段之一。本文旨在深入探讨Java多线程编程的核心技术、常见问题以及最佳实践,通过实际案例分析,帮助读者理解并掌握如何在Java应用中高效地使用多线程。不同于常规的技术总结,本文将结合作者多年的实践经验,以故事化的方式讲述多线程编程的魅力与挑战,旨在为读者提供一种全新的学习视角。
25 3
|
11天前
|
安全 Java
在 Java 中使用实现 Runnable 接口的方式创建线程
【10月更文挑战第22天】通过以上内容的介绍,相信你已经对在 Java 中如何使用实现 Runnable 接口的方式创建线程有了更深入的了解。在实际应用中,需要根据具体的需求和场景,合理选择线程创建方式,并注意线程安全、同步、通信等相关问题,以确保程序的正确性和稳定性。
|
6天前
|
安全 Java 调度
Java中的多线程编程入门
【10月更文挑战第29天】在Java的世界中,多线程就像是一场精心编排的交响乐。每个线程都是乐团中的一个乐手,他们各自演奏着自己的部分,却又和谐地共同完成整场演出。本文将带你走进Java多线程的世界,让你从零基础到能够编写基本的多线程程序。
18 1
|
10天前
|
缓存 Java 调度
Java中的多线程编程:从基础到实践
【10月更文挑战第24天】 本文旨在为读者提供一个关于Java多线程编程的全面指南。我们将从多线程的基本概念开始,逐步深入到Java中实现多线程的方法,包括继承Thread类、实现Runnable接口以及使用Executor框架。此外,我们还将探讨多线程编程中的常见问题和最佳实践,帮助读者在实际项目中更好地应用多线程技术。
17 3
|
12天前
|
监控 安全 Java
Java多线程编程的艺术与实践
【10月更文挑战第22天】 在现代软件开发中,多线程编程是一项不可或缺的技能。本文将深入探讨Java多线程编程的核心概念、常见问题以及最佳实践,帮助开发者掌握这一强大的工具。我们将从基础概念入手,逐步深入到高级主题,包括线程的创建与管理、同步机制、线程池的使用等。通过实际案例分析,本文旨在提供一种系统化的学习方法,使读者能够在实际项目中灵活运用多线程技术。