Java并发编程实现概览

时间:2022-12-16 16:50:32

并发概览

>>同步

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

>>线程安全性

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

>>原子性和竞争条件

(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对象都可以用作一个实现同步的锁,这些锁被称为内置锁。
当某个线程请求一个由其他线程持有的锁时,发出请求的线程就会被阻塞,然而内置锁是可以重入的。因此如果某个线程试图获得一个已经有它自己持有的锁,那么这个请求就会成功。
注意,“锁”的持有者是实例对象,而不是类!

>>线程和集合类

(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保护,否则会出现问题。

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) 。

>>线程池

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

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

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

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接口。

>>显式锁

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

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

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

  

使用ReentrantLock来保护对象状态。

Lock lock = new ReentrantLock();
lock.lock();
try {
    //相关操作
} finally {
    lock.unlock();  //一定要释放锁
}

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

(3)可中断的锁

(4)ReadWriteLock 读-写锁

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

//ReadWriteLock 接口
public interface ReadWriteLock {
    Lock readLock();
    Lock writeLock();
}

  

>>计数器CountDownLatch和栅栏CyclicBarrier

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

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

>>信号量Semaphore

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

>>ThreadLocal 线程私有变量

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

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

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

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();
}

  

整理自 《Java并发编程实战》
Java Threads 多线程10分钟参考手册 
Java并发编程学习笔记

Java并发编程实现概览的更多相关文章

  1. Java并发编程基础

    Java并发编程基础 1. 并发 1.1. 什么是并发? 并发是一种能并行运行多个程序或并行运行一个程序中多个部分的能力.如果程序中一个耗时的任务能以异步或并行的方式运行,那么整个程序的吞吐量和可交互 ...

  2. Java并发编程中的若干核心技术,向高手进阶!

    来源:http://www.jianshu.com/p/5f499f8212e7 引言 本文试图从一个更高的视角来总结Java语言中的并发编程内容,希望阅读完本文之后,可以收获一些内容,至少应该知道在 ...

  3. 【Java并发编程实战】----- AQS(四):CLH同步队列

    在[Java并发编程实战]-–"J.U.C":CLH队列锁提过,AQS里面的CLH队列是CLH同步锁的一种变形.其主要从两方面进行了改造:节点的结构与节点等待机制.在结构上引入了头 ...

  4. 【Java并发编程实战】----- AQS(三):阻塞、唤醒:LockSupport

    在上篇博客([Java并发编程实战]----- AQS(二):获取锁.释放锁)中提到,当一个线程加入到CLH队列中时,如果不是头节点是需要判断该节点是否需要挂起:在释放锁后,需要唤醒该线程的继任节点 ...

  5. 【Java并发编程实战】----- AQS(二):获取锁、释放锁

    上篇博客稍微介绍了一下AQS,下面我们来关注下AQS的所获取和锁释放. AQS锁获取 AQS包含如下几个方法: acquire(int arg):以独占模式获取对象,忽略中断. acquireInte ...

  6. 【Java并发编程实战】-----“J.U.C”:CLH队列锁

    在前面介绍的几篇博客中总是提到CLH队列,在AQS中CLH队列是维护一组线程的严格按照FIFO的队列.他能够确保无饥饿,严格的先来先服务的公平性.下图是CLH队列节点的示意图: 在CLH队列的节点QN ...

  7. 【Java并发编程实战】-----“J.U.C”:CountDownlatch

    上篇博文([Java并发编程实战]-----"J.U.C":CyclicBarrier)LZ介绍了CyclicBarrier.CyclicBarrier所描述的是"允许一 ...

  8. 【Java并发编程实战】-----“J.U.C”:CyclicBarrier

    在上篇博客([Java并发编程实战]-----"J.U.C":Semaphore)中,LZ介绍了Semaphore,下面LZ介绍CyclicBarrier.在JDK API中是这么 ...

  9. 【Java并发编程实战】-----“J.U.C”:ReentrantReadWriteLock

    ReentrantLock实现了标准的互斥操作,也就是说在某一时刻只有有一个线程持有锁.ReentrantLock采用这种独占的保守锁直接,在一定程度上减低了吞吐量.在这种情况下任何的"读/ ...

随机推荐

  1. 【ASP.NET】利用Nuget打包package——GUI方式

    GUI方式 通过GUI的方式,可以下载如下的软件 NuGetPackageExplorer   打包dll 1.打开软件,在Package Content处点击右键 ,选择Add Lib 2.在lib ...

  2. iOS各个版本的新特性介绍

    官方汇总 What's News in iOS iOS 9.3 to iOS 10.0 API Differences Objective-C /usr/include Accelerate Audi ...

  3. mysql update时报错You are using safe update mode

    在使用mysql执行update的时候,如果不是用主键当where语句,会报如下错误,使用主键用于where语句中正常. ) Error Code: . You are using safe upda ...

  4. (转)c#多线程 Invoke方法的使用

    原文地址:http://www.cnblogs.com/lovko/archive/2008/12/19/1358748.html 在多线程编程中,我们经常要在工作线程中去更新界面显示,而在多线程中直 ...

  5. Export功能 导致 页面显示很多非法字符,还可能页面显示两次

    private void exportBinaryToExcel(byte[] bytes, string filename) { Response.AddHeader("Content-D ...

  6. xml基础学习笔记

    1 XML入门 1.1 引入 HTML: 负责网页的结构 CSS: 负责网页的样式(美观) Javascript: 负责在浏览器端与用户进行交互. 负责静态的网页制作的语言 HTML语言特点: 1)由 ...

  7. Gcc简介与常用命令

    一.对于GUN编译器来说,程序的编译要经历预处理.编译.汇编.连接四个阶段,如下图所示: 在预处理阶段,输入的是C语言的源文件,通常为*.c.它们通常带有.h之类头文件的包含文件.这个阶段主要处理源文 ...

  8. android Graphics(一):概述及基本几何图形绘制

    前言:我最近想抽空研究研究android的各种特效,android的特效真是其它平台无法比拟的,而且一个漂亮的UI交互,会给APP增色不少,而学习特效之前,有关graphics绘图的基础知识是必不可少 ...

  9. ER模型

    一.什么是ER模型 实体-联系图(Entity-RelationDiagram)用来建立数据模型,在数据库系统概论中属于概念设计阶段,形成一个独立于机器.独立于DBMS的ER图模型.通常将它简称为ER ...

  10. python 二进制转换

    #二进制装换msg = "大家好"msg1 = msg.encode(encoding='utf-8')#转换成二进制print(msg1)msg2 = msg1.decode(' ...