Java如何实现多线程场景下的线程安全

简介: 单线程可以正确运行的程序不代表在多线程场景下能够正确运行,这里的正确性往往不容易被发现,它会在并发数达到一定量的时候才可能出现。这也是在测试环节不容易重现的原因。因此,多线程(并发)场景下,如何编写线程安全(Thread-Safety)的程序,对于程序的正确和稳定运行有重要的意义。下面将结合示例,谈谈如何在Java语言中,实现线程安全的程序。

1 引言


    当前随着计算机硬件的快速发展,个人电脑上的CPU也是多核的,现在普遍的CUP核数都是4核或者8核的。因此,在编写程序时,需要为了提高效率,充分发挥硬件的能力,则需要编写并行的程序。Java语言作为互联网应用的主要语言,广泛应用于企业应用程序的开发中,它也是支持多线程Multithreading)的,但多线程虽好,却对程序的编写有较高的要求。

    单线程可以正确运行的程序不代表在多线程场景下能够正确运行,这里的正确性往往不容易被发现,它会在并发数达到一定量的时候才可能出现。这也是在测试环节不容易重现的原因。因此,多线程(并发)场景下,如何编写线程安全(Thread-Safety)的程序,对于程序的正确和稳定运行有重要的意义。下面将结合示例,谈谈如何在Java语言中,实现线程安全的程序。

   为了给出感性的认识,下面给出一个线程不安全的示例,具体如下:

packagecom.example.learn;
publicclassCounter {
privatestaticintcounter=0;
publicstaticintgetCount(){
returncounter;
    }
publicstaticvoidadd(){
counter=counter+1;
    }
}

    这个类有一个静态的属性counter,用于计数。其中可以通过静态方法add()对counter进行加1操作,也可以通过getCount()方法获取到当前的计数counter值。如果是单线程情况下,这个程序是没有问题的,比如循环10次,那么最后获取的计数counter值为10。但多线程情况下,那么这个结果就不一定能够正确获取,可能等于10,也可能小于10,比如9。下面给出一个多线程测试的示例:

packagecom.example.learn;
publicclassMyThreadextendsThread{
privateStringname ;
publicMyThread(Stringname){
this.name=name ;
    }
publicvoidrun(){
Counter.add();
System.out.println("Thead["+this.name+"] Count is "+Counter.getCount());
    }
}
///////////////////////////////////////////////////////////packagecom.example.learn;
publicclassTest01 {
publicstaticvoidmain(String[] args) {
for(inti=0;i<5000;i++){
MyThreadmt1=newMyThread("TCount"+i);
mt1.start();
        }
    }
}

这里为了重现计数的问题,线程数调至比较大,这里是5000。运行此示例,则输出可能结果如下:

Thead[TCount5] Countis4Thead[TCount2] Countis9Thead[TCount4] Countis4Thead[TCount14] Countis10..................................
Thead[TCount4911] Countis4997Thead[TCount4835] Countis4998Thead[TCount4962] Countis4999

   注意:多线程场景下,线程不安全的程序输出结果具有不确定性。

2 synchronized方法


    基于上述的示例,让其变成线程安全的程序,最直接的就是在对应的方法上添加synchronized关键字,让其成为同步的方法。它可以修饰一个类,一个方法和一个代码块。对上述计数程序进行修改,代码如下:

packagecom.example.learn;
publicclassCounter {
privatestaticintcounter=0;
publicstaticintgetCount(){
returncounter;
    }
publicstaticsynchronizedvoidadd(){
counter=counter+1;
    }
}

3 加锁机制


    另外一种常见的同步方法就是加锁,比如Java中有一种重入锁ReentrantLock,它是一种递归无阻塞的同步机制,相对于synchronized来说,它可以提供更加强大和灵活的锁机制,同时可以减少死锁发生的概率。示例代码如下:

packagecom.example.learn;
importjava.util.concurrent.locks.ReentrantLock;
publicclassCounter {
privatestaticintcounter=0;
privatestaticfinalReentrantLocklock=newReentrantLock(true);
publicstaticintgetCount(){
returncounter;
    }
publicstaticvoidadd(){
lock.lock();
try {
counter=counter+1;
        } finally {
lock.unlock();
        }
    }
}

4 使用Atomic对象


    由于锁机制会影响一定的性能,而有些场景下,可以通过无锁方式进行实现。Java内置了Atomic相关原子操作类,比如AtomicInteger, AtomicLong, AtomicBoolean和AtomicReference,可以根据不同的场景进行选择。下面给出示例代码:

packagecom.example.learn;
importjava.util.concurrent.atomic.AtomicInteger;
publicclassCounter {
privatestaticfinalAtomicIntegercounter=newAtomicInteger();
publicstaticintgetCount(){
returncounter.get();
    }
publicstaticvoidadd(){
counter.incrementAndGet();
    }
}

5 无状态对象


    前面提到,线程不安全的一个原因就是多个线程同时访问某个对象中的数据,数据存在共享的情况,因此,如果将数据变成独享的,即无状态(stateless)的话,那么自然就是线程安全的。而所谓的无状态的方法,就是给同样的输入,就能返回一致的结果。下面给出示例代码:

packagecom.example.learn;
publicclassCounter {
publicstaticintsum (intn) {
intret=0;
for (inti=1; i<=n; i++) {
ret+=i;
        }
returnret;
    }
}

    前面提到了几种线程安全的方法,总体的思想要不就是通过锁机制实现同步,要不就是防止数据共享,防止在多个线程中对数据进行读写操作。另外,有些文章中说到,可以在变量前使用volatile修饰,来实现同步机制,但这个经过测试是不一定的,有些场景下,volatile依旧不能保证线程安全。虽然上述是线程安全的经验总结,但是还是需要通过严格的测试进行验证,实践是检验真理的唯一标准。

相关文章
|
2天前
|
存储 Linux C语言
c++进阶篇——初窥多线程(二) 基于C语言实现的多线程编写
本文介绍了C++中使用C语言的pthread库实现多线程编程。`pthread_create`用于创建新线程,`pthread_self`返回当前线程ID。示例展示了如何创建线程并打印线程ID,强调了线程同步的重要性,如使用`sleep`防止主线程提前结束导致子线程未执行完。`pthread_exit`用于线程退出,`pthread_join`用来等待并回收子线程,`pthread_detach`则分离线程。文中还提到了线程取消功能,通过`pthread_cancel`实现。这些基本操作是理解和使用C/C++多线程的关键。
|
2天前
|
安全 Java 程序员
Java多线程详解
Java多线程详解
|
2天前
|
缓存 安全 Java
Java线程面试题含答案
Java线程面试题含答案
|
2天前
|
Java API
|
2天前
|
Java
Java Socket编程与多线程:提升客户端-服务器通信的并发性能
【6月更文挑战第21天】Java网络编程中,Socket结合多线程提升并发性能,服务器对每个客户端连接启动新线程处理,如示例所示,实现每个客户端的独立操作。多线程利用多核处理器能力,避免串行等待,提升响应速度。防止死锁需减少共享资源,统一锁定顺序,使用超时和重试策略。使用synchronized、ReentrantLock等维持数据一致性。多线程带来性能提升的同时,也伴随复杂性和挑战。
|
1月前
|
安全 Java
java保证线程安全关于锁处理的理解
了解Java中确保线程安全的锁机制:1)全局synchronized方法实现单例模式;2)对Vector/Collections.SynchronizedList/CopyOnWriteArrayList的部分操作加锁;3)ConcurrentHashMap的锁分段技术;4)使用读写锁;5)无锁或低冲突策略,如Disruptor队列。
23 2
|
23天前
|
存储 安全 Java
深入理解Java并发编程:线程安全与锁机制
【5月更文挑战第31天】在Java并发编程中,线程安全和锁机制是两个核心概念。本文将深入探讨这两个概念,包括它们的定义、实现方式以及在实际开发中的应用。通过对线程安全和锁机制的深入理解,可以帮助我们更好地解决并发编程中的问题,提高程序的性能和稳定性。
|
25天前
|
安全 Java API
Java 8中的Stream API:简介与实用指南深入理解Java并发编程:线程安全与锁优化
【5月更文挑战第29天】本文旨在介绍Java 8中引入的Stream API,这是一种用于处理集合的新方法。我们将探讨Stream API的基本概念,以及如何使用它来简化集合操作,提高代码的可读性和效率。 【5月更文挑战第29天】 在Java并发编程中,线程安全和性能优化是两个核心议题。本文将深入探讨如何通过不同的锁机制和同步策略来保证多线程环境下的数据一致性,同时避免常见的并发问题如死锁和竞态条件。文章还将介绍现代Java虚拟机(JVM)针对锁的优化技术,包括锁粗化、锁消除以及轻量级锁等概念,并指导开发者如何合理选择和使用这些技术以提升应用的性能。
|
1月前
|
安全 Java
【JAVA进阶篇教学】第十篇:Java中线程安全、锁讲解
【JAVA进阶篇教学】第十篇:Java中线程安全、锁讲解
|
27天前
|
缓存 安全 Java
深入理解Java并发编程:线程安全与锁优化
【5月更文挑战第27天】 在Java并发编程中,线程安全和性能优化是两个核心议题。本文将深入探讨如何在保证线程安全的前提下,通过合理使用锁机制来提升程序性能。我们将从基本的同步关键字出发,逐步介绍更高级的锁优化技术,包括可重入锁、读写锁以及乐观锁等,并配以实例代码来展示这些技术的应用。

热门文章

最新文章