Java对并发编程的支持

简介:

1.同步

如何同步多个线程对共享资源的访问是多线程编程中最基本的问题之一。
当多个线程并发访问共享数据时会出现数据处于计算中间状态或者不一致的问题,从而影响到程序的正确运行。我们通常把这种情况叫做竞争条件(race condition),把并发访问共享数据的代码叫做关键区域(critical section)。
同步就是使得多个线程顺序进入关键区域从而避免竞争条件的发生。

2.线程安全性

编写线程安全的代码的核心是要对状态访问操作进行管理,尤其是对共享的和可变的状态访问。 
线程安全性的定义:当多个线程访问某个类时,这个类始终能表现出正确的行为,那么就称这个类是线程安全的。 
无状态对象一定是线程安全的。

3.原子性和竞争条件

(1)具有原子性的操作被称为原子操作
在Java中,对除了long和double之外的基本类型的简单操作都具有原子性。简单操作就是赋值或者return。比如”a = 1;“和 “return a;”这样的操作都具有原子性。
在某些JVM中”a += b”可能要经过这样三个步骤:
1) 读取:取出a和b 
2) 修改:计算a+b 
3) 写入:将计算结果写入内存
非原子操作都会存在线程安全问题,需要我们使用同步技术(sychronized)来让它变成一个原子操作。concurrent包下提供了一些原子类,比如:AtomicInteger、AtomicLong、AtomicReference等。

(2)竞争条件,指的是在并发编程中,由于不恰当的执行时序而出现不正确的结果的情况
当某个计算结果的正确性取决于多个线程的交替执行时序时就会发生竞态条件。 
常见的两种竞争条件是:“先检查后执行”和 “读取-修改-写入”。

(3)内置锁和可重入锁
Java提供了一种内置锁机制来支持原子性:同步代码块。同步代码块包括两个部分:一个是作为锁的对象引用,一个是作为由这个锁保护的代码块。每个Java对象都可以用作一个实现同步的锁,这些锁被称为内置锁。
当某个线程请求一个由其他线程持有的锁时,发出请求的线程就会被阻塞,然而内置锁是可以重入的。因此如果某个线程试图获得一个已经有它自己持有的锁,那么这个请求就会成功。 
注意,“锁”的持有者是实例对象,而不是类!

4.线程和集合类

(1)线程安全的集合类
java.util.Vector
java.util.Stack
java.util.HashTable
java.util.concurrent.ConcurrentHashMap
java.util.concurrent.CopyOnWriteArrayList
java.util.concurrent.CopyOnWriteArraySet
java.util.concurrent.ConcurrentLinkedQueue

(2)非线程安全集合类
java.util.BitSet
java.util.HashSet (LinkedHashSet)
java.util.TreeSet
java.util.HashMap (WeekHashMap, TreeMap, LinkedHashMap, IdentityHashMap)
java.util.ArrayList (LinkedList)
java.util.PriorityQueue
这些非线程安全的集合可以通过java.util.Collections.SynchronizedList、SynchronizedMap、SynchronizedSet等方法包装成线程安全的集合。包装器类简单地给被包装集合的各项操作加上了synchronized保护。值得注意的是在使用游标遍历这些包装器集合的时候必须加上额外的synchronized保护,否则会出现问题。

1
2
3
4
5
6
7
List list = Collections.synchronizedList( new  ArrayList()); 
     ... 
synchronized (list) { 
     Iterator i = list.iterator();  // Must be in synchronized block 
     while  (i.hasNext()) 
         foo(i.next()); 

  

(3)线程通知集合类

java.util.concurrent.ArrayBlockingQueue
java.util.concurrent.LinkedBlockingQueue
java.util.concurrent.SynchronousQueue
java.util.concurrent.PriorityBlockingQueue
java.util.concurrent.DelayQueue
这些集合类都实现了BlockingQueue接口。阻塞队列的特点是当从队列中取出元素时如果队列为空,线程会被阻塞直到队列中有元素被插入。当从队列中插入元素时如果队列已满,线程会被阻塞直到队列中有元素被取出出现空闲空间。阻塞队列可以用来实现生产者消费者模式(Producer/Consumer Pattern) 。

5.线程池

频繁地创建和销毁线程会降低程序的性能。
应用程序可以创建线程的数量是受机器物理条件制约的,过多的线程会耗尽机器的资源,在设计程序的时候需要限制并发线程的数量。
线程池在启动的时候一次性初始化若干个线程(也可以根据负载按需启动,也有闲置一定时间的线程会被销毁的策略),然后程序把任务交给线程池去执行而不是直接交给某个线程执行,由线程池给这些任务分配线程。
当某个线程执行完一个任务后,线程池会把它设成空闲状态以备下一个任务重用而不是销毁它。
线程池在初始化的时候需要指定线程数量上限,当并发任务数量超过线程数量的时候,

线程池不会再创建新的线程而是让新任务等待,这样我们就不在需要担心线程数量过多耗尽系统资源了。JDK1.5开始为我们提供了标准的线程池。

(1)Executor接口
Java的线程池实现了以下Executor接口:

1
2
3
4
Java的线程池实现了以下Executor接口:        
public  interface  Executor { 
     void  execute(Runnable command); 
}

在多线程编程中,执行器是一种常用的设计模式,它的好处在于提供了一种简单有效的编程模型,我们只需把需要并发处理的工作拆分成独立的任务,然后交给执行器去执行即可而不必关心线程的创建,分配和调度。
JDK主要提供了两种功能的执行器:ThreadPoolExecutor和ScheduledThreadPoolExecutor。ThreadPoolExecutor是基本的线程池实现,ScheduledThreadPoolExecutor在前者基础上增加了任务调度的功能,在把任务交给它时我们可以指定任务的执行时间,而不是立刻执行。

(2)Executors创建线程池
java.util.concurrent.Executors是用来创建线程池的工厂类,
通过它提供的工厂方法,我们可以方便地创建不同特性的线程池,包括缓存线程池、各种优先级线程池等。

(3)Future接口
Executor接口并没有看起来那么理想,有时候我们执行一个任务是要得到计算的结果,有时候我们需要对任务有更多控制,例如知道它是否完成,或者中途终止它。返回void的execute方法并不能满足我们这些需求。当然我们可以在传入的Runnable类上下功夫来提供类似的功能,但是这样做繁琐且容易出错。实际上线程池实现了一个更为丰富的ExecutorService接口,它定义了执行任务并返回代表该任务的Future对象的submit方法。
通过Future接口,我们可以查看已经被提交给线程池执行的任务是否完成,获取执行的结果或者终止任务。

(4) Runnable 和Callable 接口
实现了Runnable或Callable接口的类都可以作为任务提交给线程池执行,这两个接口的主要区别在于Callable的call方法有结果返回并且可以抛出异常而Runnable的run方法返回void且不允许有可检查的异常抛出(只能抛runtime exception)。因此如果我们的任务执行后有结果返回,应该使用Callable接口。


6.显式锁

协调共享对象访问的机制:JDK5之前是synchronized和volatile(),JDK5增加了ReentrantLock,现在可以用Lock显式的lock()和unlock(),并且有定时锁,读写锁等。

(1)ReentrantLock
ReentrantLock实现了Lock接口,并提供了与synchronized相同的互斥性和内存可见性。

1
2
3
4
5
6
7
8
9
10
public  interface  Lock {
void  lock();
//如果当前线程未被中断,则获取锁定。
     void  lockInterruptibly()  throws  InterruptedException;
     boolean  tryLock();
     boolean  tryLock( long  timeout, TimeUnit unit)
     throws  InterruptedException;
     void  unlock();
     Condition newCondition();
}

  


使用ReentrantLock来保护对象状态。

1
2
3
4
5
6
7
Lock lock =  new  ReentrantLock();
lock.lock();
try  {
     //相关操作
finally  {
     lock.unlock();   //一定要释放锁
}

(2)轮询锁和定时锁
可定时的与可轮询的锁获取模式是由tryLock方法实现的,与无条件的锁获取模式相比,它具有更完善的错误恢复机制。

(3)可中断的锁

(4)ReadWriteLock 读-写锁

ReadWriteLock 维护了一对相关的锁定,一个用于只读操作,另一个用于写入操作。
//ReadWriteLock 接口

1
2
3
4
5
//ReadWriteLock 接口
public  interface  ReadWriteLock {
     Lock readLock();
     Lock writeLock();
}

  

7.计数器CountDownLatch和栅栏CyclicBarrier

(1)CountDownLatch,计数器或者闭锁
CountDownLatch是一个同步辅助类,java.util.concurrent.CountDownLatch,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。用给定的计数 初始化 CountDownLatch。由于调用了 countDown() 方法,所以在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。
CountDownLatch即一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行

(2)CyclicBarrier,栅栏
通过闭锁(CountDownLatch)来同时启动一组相关线程,或等待一组相关线程的结束。可是闭锁是一次性对象,一旦进入终止状态,就不能被重置。栅栏类似于闭锁,它能够阻塞一组线程直到某个事件发生。
CyclicBarrier在并行迭代算法中是非常有用。


8.信号量Semaphore

计数信号量(Counting Semaphore)用来控制同时访问某个特定资源的操作数量,或者同时执行某个指定操作的数量。
有时候我们有多个相同的共享资源可以同时被多个线程使用。我们希望在锁的基础上加上一个计数器,根据资源的个数来初始化这个计数器,每次成功的lock操作都会使计数器的值减去1,只要计数器的值不为零就表示还有资源可以使用,lock操作就能成功。每次unlock操作都会给这个计数器加1。只有当计数器的值为0的时候lock操作才会阻塞当前线程。这就是Java中的信号量Semaphore。
Semaphore类提供的方法和Lock接口非常类似,当把信号量的资源个数设置成1时,信号量就退化为普通的锁。

9.ThreadLocal 线程私有变量

(1)是变量不是线程
如果每个线程都有自己私有的成员变量,那么我们也不需要同步。ThreadLocal就是线程的私有变量,每个使用ThreadLocal变量的线程都会有自己独立的ThreadLocal对象,因此就不存在多个线程访问同一个变量的问题。
它并不是一个Thread,而是threadlocalvariable(线程局部变量)。

(2)ThreadLocal 的实现原理
每个Thread对象有自己用来存储私有ThreadLocal对象的容器ThreadLocalMap,当某个线程调用ThreadLocal对象的get()方法来 取值的时候,
get方法首先会取得当前线程对象,然后取出该线程的ThreadLocalMap,然后检查自己是否已经在map中,如果自己已经存在,直接返回map中的value。

如果不存在,把自己作key并初始化一个value加入到当前线程的map中。

1
2
3
4
5
6
7
8
9
10
public  T get() { 
         Thread t = Thread.currentThread(); 
         ThreadLocalMap map = getMap(t); 
         if  (map !=  null ) { 
             ThreadLocalMap.Entry e = map.getEntry( this ); 
             if  (e !=  null
                 return  (T)e.value; 
        
         return  setInitialValue(); 

  


本文转自邴越博客园博客,原文链接:http://www.cnblogs.com/binyue/p/5073952.html,如需转载请自行联系原作者

相关文章
|
17天前
|
安全 Java 开发者
深入理解Java并发编程:线程安全与性能优化
【4月更文挑战第9天】本文将深入探讨Java并发编程的核心概念,包括线程安全和性能优化。我们将详细解析Java中的同步机制,包括synchronized关键字、Lock接口以及并发集合等,并探讨它们如何影响程序的性能。此外,我们还将讨论Java内存模型,以及它如何影响并发程序的行为。最后,我们将提供一些实用的并发编程技巧和最佳实践,帮助开发者编写出既线程安全又高效的Java程序。
23 3
|
18天前
|
Java
Java 并发编程:深入理解线程池
【4月更文挑战第8天】本文将深入探讨 Java 中的线程池技术,包括其工作原理、优势以及如何使用。线程池是 Java 并发编程的重要工具,它可以有效地管理和控制线程的执行,提高系统性能。通过本文的学习,读者将对线程池有更深入的理解,并能在实际开发中灵活运用。
|
14天前
|
安全 算法 Java
深入理解Java并发编程:线程安全与性能优化
【4月更文挑战第11天】 在Java中,高效的并发编程是提升应用性能和响应能力的关键。本文将探讨Java并发的核心概念,包括线程安全、锁机制、线程池以及并发集合等,同时提供实用的编程技巧和最佳实践,帮助开发者在保证线程安全的前提下,优化程序性能。我们将通过分析常见的并发问题,如竞态条件、死锁,以及如何利用现代Java并发工具来避免这些问题,从而构建更加健壮和高效的多线程应用程序。
|
19天前
|
Java
Java并发编程:深入理解线程池
【4月更文挑战第7天】在现代软件开发中,多线程编程已经成为一种不可或缺的技术。为了提高程序性能和资源利用率,Java提供了线程池这一强大工具。本文将深入探讨Java线程池的原理、使用方法以及如何根据实际需求定制线程池,帮助读者更好地理解和应用线程池技术。
15 0
|
1天前
|
存储 安全 Java
Java并发编程中的高效数据结构:ConcurrentHashMap解析
【4月更文挑战第25天】在多线程环境下,高效的数据访问和管理是至关重要的。Java提供了多种并发集合来处理这种情境,其中ConcurrentHashMap是最广泛使用的一个。本文将深入分析ConcurrentHashMap的内部工作原理、性能特点以及它如何在保证线程安全的同时提供高并发性,最后将展示其在实际开发中的应用示例。
|
2天前
|
Java API 调度
[AIGC] 深入理解Java并发编程:从入门到进阶
[AIGC] 深入理解Java并发编程:从入门到进阶
|
2天前
|
Dubbo Java 应用服务中间件
Java从入门到精通:3.2.2分布式与并发编程——了解分布式系统的基本概念,学习使用Dubbo、Spring Cloud等分布式框架
Java从入门到精通:3.2.2分布式与并发编程——了解分布式系统的基本概念,学习使用Dubbo、Spring Cloud等分布式框架
|
3天前
|
Java
Java中的并发编程:理解和应用线程池
【4月更文挑战第23天】在现代的Java应用程序中,性能和资源的有效利用已经成为了一个重要的考量因素。并发编程是提高应用程序性能的关键手段之一,而线程池则是实现高效并发的重要工具。本文将深入探讨Java中的线程池,包括其基本原理、优势、以及如何在实际开发中有效地使用线程池。我们将通过实例和代码片段,帮助读者理解线程池的概念,并学习如何在Java应用中合理地使用线程池。
|
7天前
|
安全 Java 开发者
Java并发编程:深入理解Synchronized关键字
【4月更文挑战第19天】 在Java多线程编程中,为了确保数据的一致性和线程安全,我们经常需要使用到同步机制。其中,`synchronized`关键字是最为常见的一种方式,它能够保证在同一时刻只有一个线程可以访问某个对象的特定代码段。本文将深入探讨`synchronized`关键字的原理、用法以及性能影响,并通过具体示例来展示如何在Java程序中有效地应用这一技术。
|
8天前
|
安全 Java 调度
Java并发编程:深入理解线程与锁
【4月更文挑战第18天】本文探讨了Java中的线程和锁机制,包括线程的创建(通过Thread类、Runnable接口或Callable/Future)及其生命周期。Java提供多种锁机制,如`synchronized`关键字、ReentrantLock和ReadWriteLock,以确保并发访问共享资源的安全。此外,文章还介绍了高级并发工具,如Semaphore(控制并发线程数)、CountDownLatch(线程间等待)和CyclicBarrier(同步多个线程)。掌握这些知识对于编写高效、正确的并发程序至关重要。