java并发编程使用简介

Reading time ~5 minutes

此文章为原创文章,引用请标记文章出处: java并发编程使用简介

概述

线程无处不在,即使在java中没有显示创建线程,但在框架中仍可能会创建线程,因此在这些线程调用的代码同样也要保证线程安全。

术语

对象的状态:存储的状态变量(例如实例或静态域)中的数据。

共享:变量可以由多个线程同时访问

可变:变量的值在其声明周期内可以发生变化。

线程安全性:当多个线程访问某个类时,不管运行环境采用何种调度方法或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。

原子性:即一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行,只有简单的读取、赋值(而且必须是将数字赋值给某个变量,变量之间的相互赋值不是原子操作)才是原子操作。

复合操作:包含了一组必须以原子方式执行的操作以确保线程安全性。所以为了线程的安全性,“先检查后执行”以及“读取-修改-写入”等操作都必须是原子的。可以使用jdk的Atomic相关类。

可见性:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

volatile变量:当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值

java同步容器、并发类的使用场景

同步容器

使用synchronized同步代码块或同步方法,如:jdk的Collections.synchronizedList(List list)相关方法底层就是同步方法的方式,Vector、Hashtable类的方法基本上都采用同步方法的方式。如果需要使用比同步集合拥有更好的并发性请使用并发容器。

并发容器

  • 并发Collection

ConcurrentHashMap:使用分段锁机制,实现是把Map中正在被写入的部分进行锁定,不影响其他部分的读写,所有执行读取操作的线程和执行写入操作的线程可以并发访问Map。只有当应用程序需要加锁Map以进行独占访问或者需要依赖同步map带来的一些其它作用时,才应该放弃使用ConcurrentHashMap

CopyOnWriteArrayList:所有可变操作(add、set 等等)都是通过对底层数组进行一次新的复制的数组来实现的。仅当迭代操作远远多于修改操作时,才应该使用“写入时复制”容器CopyOnWriteArrayList。

ConcurrentNavigableMap:支持并发访问,它还能让该类的 headMap(),subMap(),tailMap() 之类的方法返回的 map具有并发访问能力.

  • 并发Queue

重点:主要用于生产者-消费者场景

LinkedBlockingQueue:阻塞的队列,入队和出队都用了加锁,当队空的时候线程会暂时阻塞。

ArrayBlockingQueue:ArrayBlockingQueue是初始容量固定的阻塞队列,我们可以用来作为数据库模块成功竞拍的队列,比如有10个商品,那么我们就设定一个10大小的数组队列。

PriorityBlockingQueue:按优先级排序队列,可以自定义排序规则

SynchronousQueue:是一个长度只有1的特殊的队列。通过线程阻塞防止第二个元素的插入或获取空队列的元素。当线程池是无界的或者可以拒绝任务时,才有实际的价值。

ConcurrentLinkedQueue:使用的是CAS原语无锁队列实现,是一个异步队列,入队的速度很快,出队进行了加锁,性能稍慢。

DelayQueue:对元素进行持有直到一个特定的延迟到期,放入队列中的元素必须实现java.util.concurrent.Delayed接口

  • 并发Deque

重点:线程既是一个队列的生产者又是这个队列的消费者的时候可以使用到

LinkedBlockingDeque:双端队列是一个你可以从任意一端插入或者抽取元素的队列。每个消费者都有各自的双端队列并且当一个消费者完成了自己双端队列中的全部工作,那么它可以从其它消费者双端队列的末尾秘密获取工作,极大减少了竞争。非同步的有ArrayDeque、LinkedList

同步器

CountDownLatch:在计数器为0之前,一个或多个线程一直等待,就像倒计时。最重要的方法是countDown()和await(),前者主要是倒数一次,后者是等待倒数到0,如果没有到达0,就只有阻塞等待了

FutureTask:只有当所有线程的run或者call方法都执行完成,则通过get()方法获取计算结果,否则默认阻塞该方法,但也提供超时取消任务的方法。

Semaphore:信号量为空即没有获取到许可的线程将阻塞,用于控制访问资源的线程个数。

CyclicBarrier:当指定数量的线程都调用了await()之后,就表示这些线程都可以继续执行,否则就会等待

Exchanger:用于两个线程交换数据,线程执行exchange(V x) 时需要等待另一个线程也执行exchange(V x) 才交换数据

并发原子类

重点:修改方法的底层实现是通过JNI调用操作系统的原生程序

AtomicBoolean:可以用原子方式更新的 boolean 值。

AtomicInteger:可以用原子方式更新的 int 值。

AtomicLong:可以用原子方式更新的 long 值。

AtomicIntegerArray:可以用原子方式更新其元素的 int 数组。

AtomicLongArray:可以用原子方式更新其元素的 long 数组。

AtomicReference:可以被原子性读和写的对象引用变量,如:String

Executor框架

重点:异步执行机制,替换传统的new Thread(runnable).start()方式,使我们能够在后台执行任务;实现了线程池

Executors:返回Executor、ExecutorService、ScheduledExecutorService类的实现类对象。

ExecutorCompletionService: 同时执行几组任务,通过take()获取存放完成任务的队列上的任务。

ThreadPoolExecutor:提供可调的、灵活的线程池,但需要由Executors类的方法来提供实例。

ScheduledThreadPoolExecutor:基本同ThreadPoolExecutor,它能够将任务延后执行,或者间隔固定时间多次执行

ForkJoinPool:jdk7提供,是一个特殊的线程池,它的设计是为了更好的配合 分叉-和-合并 任务分割的工作。

RecursiveTask:jdk7提供,是一种会返回结果的任务。它可以将自己的工作分割为若干更小任务,并将这些子任务的执行结果合并到一个集体结果。可以有几个水平的分割和合并。

同步锁

重点:提供比synchronized更灵活的锁方式,允许以三种形式(可中断、不可中断和定时)尝试获取锁,并允许以任何顺序获取和释放多个锁

ReentrantLock:标准的互斥锁,每次最多只有一个线程能持有ReentrantLock。

ReentrantReadWriteLock:允许多个线程在同一时间对某特定资源进行读取,但同一时间内只能有一个线程对其进行写入。

实践

1.在实际情况中,应该尽可能使用现有的线程安全对象,如jdk提供的原子类等来管理类的状态,这体现了重用,而重用是能降低开发工作量、开发风险以及维护成本的。

2.对于可能被多个线程同时访问的可变状态变量,在访问它时都需要持有同一个锁。

3.如果使用的是同步代码块方式,应该将不影响共享状态且执行时间较长的操作从同步代码块中分离出去,从而即维护线程安全性又提高代码的并发性

4.当执行时间较长的计算或者可能无法快速完成的操作时,如:网络IO等,一定不要持有锁

5.除非需要更高的可见性,否则应将所有的域声明为private;除非需要某个域是可变的,否则应将其声明为final

6.final修饰集合变量,保证集合安全,不能保证集合内的数据安全;final修饰基本类型变量,能保证数据安全;final修饰的是可变对象,那么在访问这些域所指向的对象的状态时仍然需要同步。

7.不建议过度依赖volatile变量,只有当访问该变量不需要加锁时采用。

8.同步容器类在迭代器修改的时候需要使用同步代码来封装

9.使用完 ExecutorService 之后你应该将其关闭,以使其中的线程不再运行。shutdown() 方法在终止前允许执行以前提交的任务,而 shutdownNow() 方法阻止等待任务启动并试图停止当前正在执行的任务。两个方法底层都是通过interrupte中断实现

10.在java5.0或更高的JDK中,将很少使用Timer,而使用ScheduledThreadPoolExecutor,构建调度服务可以使用DelayQueue,它实现了BlockingQueue

11.并发程序的性能更多是吞吐量和可伸缩性上,因为java程序中串行操作的主要来源是独占方式的资源锁,因此通常可以通过以下方式来提升可伸缩性:减少锁的持有时间。降低锁的粒度,以及采用非独占的锁或非阻塞锁来代替独占锁。

参考资料

1.Java里的CompareAndSet(CAS)

2.Java 并发工具包 java.util.concurrent 用户指南

3.好书一本:《JAVA并发编程实战》

jdk-timer、spring-task、quartz的比较

这篇文章将简单介绍目前常用的定时任务框架! Continue reading

小岛经济学-读书笔记

Published on May 10, 2018

java常见的http请求库

Published on May 05, 2018