本文转载自ring0hx的博客:
http://blog.csdn.net/ring0hx/article/details/6858582
虽然不够深入,但是很全面
1 同步
如何同步多个线程对共享资源的访问是多线程编程中最基本的问题之一。当多个线程并发访问共享数据时会出现数据处于计算中间状态或者不一致的问题,从而影响
到程序的正确运行。我们通常把这种情况叫做竞争条件(race condition),把并发访问共享数据的代码叫做关键区域(critical
section)。同步就是使得多个线程顺序进入关键区域从而避免竞争条件的发生。
1.1 Synchronized关键字
Synchronized是Java多线程编程中最常用的关键字。所有的Java
对象都有自己唯一的隐式同步锁。该锁只能同时被一个线程获得,其他试图获得该锁的线程都会被阻塞在对象的等待队列中直到获得该锁的线程释放锁才能继续工
作。Synchronized关键字通常有两种用法。当Synchronized关键字用于类方法定义中时,表示所有调用该方法的线程都必须获得当前对象
的锁。这种方式比较简单,但是同步的粒度比较大,当一个线程要执行某个对象的同步方法的时候,必须同时没有任何其他线程在执行该对象的任一同步方法。此
外,同步方法中的所有代码均在同步块中,获得锁的线程必须在执行完所有的代码离开该方法后才会释放锁,这些代码中可能只有一部分涉及到对共享资源(例如成
员变量)的访问需要同步,其余则不需要,那么这样粗粒度的同步显然增加了其他线程的等待时间。Synchronized的另一种
用法允许作用在某个对象上,并且只同步一段代码而不是整个方法。
synchronized (object) {
// 需要同步的代码
}
这里synchronized所作用的对象可以是类的某个成员变量,也可以是这个类对象(用this表示)。这种用法使得程序员可以根据需要同步不同的成员变量,而不总是当前类对象,提高了灵活性。
值得一提的是,并不是只有对象才有锁,类本身也有自己的锁,这使得static方法同样可以用synchronized来修饰。访问同步static方法的线程需要获得类的同步锁才能继续执行。
1.2 Volatile关键字
在Java内存模型中每个线程拥有自己的本地存储(例如寄存器),并且允许线程拥有变量值的拷贝。这使得本来不需要同步的一些原子操作,例如
boolean成员变量存储和读取也变得不安全。设想我们有个叫做done的boolean成员变量和一个当done为true时才会停止的循环,该循环
由后台线程执行,另一个UI线程等待用户输入,用户按下某个按钮以后会把done设成true从而终止循环。由于UI线程自己本地拥有done的拷贝,用
户在按下按钮时只是把自己本地的done设成了true而没有及时更新主内存中的done,所以后台线程由于看不到done的改变而不会终止。即使主内存
中的done变化了,后台线程也会因为自己本地的变量值没有及时更新而没有察觉到done的变化。解决这一问题的方法之一是为done提供
synchronized的setter和getter方法,这是因为获得同步锁会迫使所有变量的值从临时存储(寄存器)写会主内存。除此之外,Java
提供了一个解决这个问题更为优雅的方法:Volatile关键字。每次使用volatile变量,JVM都会保证从主内存中读取它的值;同样每次修改
volatile变量,JVM都会把值写回到主内存中。
Volatile适用的场景比较严格,必须很清楚地看到volatile只是告诉JVM对于该变量的读写必须每次都在主内存中进行而禁止使用临时的拷贝来
优化,它只是出于JVM特殊的内存模型的需要,并没有同步的功能。因此只有对volatile变量进行的原子操作(读取和赋值)才是线程安全的,像自
增++自减--这样包含多个命令的操作仍然需要其它的同步措施。
另一个需要注意的的地方是当用volatile修饰数组的时候,它只是说数组的引用是volatile的,而数组中的元素还是和普通变量一样,可能被
JVM优化,我们无法为数组中的元素加上volatile修饰。解决上述问题的方法是使用Atomic变量。作为使用volatile修饰数组的一个例
子,可以参考java.util.concurrent.CopyOnWriteArrayList。它的add操作是通过复制原来的数组并把新元素添加
到新数组末尾然后再把内部数组引用变量指向新数组来实现的,因此数组变量经常会被修改,需要使用volatile。
1.3 显式锁Lock
尽管synchronized关键字可以解决大多数同步问题,J2SE5.0还是引入了Lock接口。相比使用synchronized关键字获取对象隐
式的同步锁,我们称Lock为显式锁。使用显式锁的一个显而易见的好处是它不再属于某个对象,从而可以在多个对象可以共享它。Lock接口有lock()
和unlock()两个方法,使用它们和使用synchronized关键字类似,在进入需要同步的代码之前调用lock,在离开同步代码块时调用
unlock。通常unlock会被放在finally中以保证即使同步代码块中有异常发生,锁仍然可以被释放。
和使用synchronized关键字和lock()方法总是把未能获得锁的线程阻塞不同,Lock接口还提供了非阻塞的tryLock()方法。调用
tryLock方法的线程如果未能获得锁会立刻返回false,线程可以继续执行其他代码而避免等待,这为程序员提供了更多自由。
Lock接口还提供了一个newCondition () 方法,它返回一个Condition对象。Condition对象的作用和Object用于线程通知的wait-notify机制相同。
1.4 信号量Semaphore
有时候我们有多个相同的共享资源可以同时被多个线程使用。我们希望在锁的基础上加上一个计数器,根据资源的个数来初始化这个计数器,每次成功的lock操
作都会使计数器的值减去1,只要计数器的值不为零就表示还有资源可以使用,lock操作就能成功。每次unlock操作都会给这个计数器加1。只有当计数
器的值为0的时候lock操作才会阻塞当前线程。这就是Java中的信号量Semaphore。
Semaphore类提供的方法和Lock接口非常类似,当把信号量的资源个数设置成1时,信号量就退化为普通的锁。
1.5 读写锁ReadWriteLock
对共享资源的访问通常可以分为读取和写入。在有些应用场景中读取可能需要花费较长时间,我们需要使用互斥锁来阻止并发的写入操作以保证数据的一致性。但是
对于并发的读取线程其实并不需要使用同步。事实上只有使数据发生变化的操作才需要同步,我们希望有一种方法可以把读取和写入区分开来,读取和写入的操作之
间是互斥的,但是多个读取操作可以同时进行,这样可以有效提高读取密集型程序的性能。J2SE5.0提供了ReadWriteLock接口并提供了实现该
接口的ReentrantReadWriteLock类:
public interface
ReadWriteLock {
Lock readLock();
Lock writeLock();
}
从接口方法中不难看出读写锁中包含读锁和写锁。实现类ReentrantReadWriteLock为我们提供了更多便捷的方法来使用读写锁,例如isWriteLocked可以用来检测是否被写锁定。
2 线程通知
除了同步锁,Java
Object还有两个可用于线程间通知的同步方法wait和notify。调用对象wait方法的线程会被阻塞在该对象的等待队列中直到其他线程调用
notify方法来唤醒它。每次notify调用只能唤醒一个在等待队列中的线程,notifyAll方法可以唤醒所有在该对象等待队列中的线程。
3 最小化同步
线程同步通过让线程顺序进入同步代码块解决了多个线程竞争同一资源而引起的不确定性,但是牺牲了效率,因此为了取得更好地性能,我们需要尽可能少地使用同
步。事实上并不是所有的竞争条件都是需要避免的,只有当竞争条件出现在非线程安全的代码段时才会引起问题。
3.1 Atomic 变量
如果一个操作是原子操作,例如给一个boolean 变量赋值,我们就不需要同步。Java提供了一些Atomic类,使得一些本来不是原子操作(例如自增操作 ++,它包含了取值、加1、赋值三个原子操作)也能够原子执行,从而不需要使用同步。
Java提供了4个基本的原子类,AtomicInteger, AtomicLong,
AtomicBoolean和AtomicReference分别提供针对int,long,boolean,object的原子操作。有意思的是如果你
打开JDK的源代码想看看这些原子操作是如何实现的,你会失望地发现代码里面没有使用任何同步或其它技术。如果你在自己的程序中写下同样地代码,那么它们
并不是原子的。
3.2 Thread Local 变量
如果每个线程都有自己私有的成员变量,那么我们也不需要同步。ThreadLocal就是线程的私有变量,每个使用ThreadLocal变量的线程都会
有自己独立的ThreadLocal对象,因此就不存在多个线程访问同一个变量的问题。当然由于ThreadLocal变量为线程私有,它也就不可以用于
在多个线程间共享状态。
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();
}
4 线程池Thread Pool
线程虽然不像进程需要那么多资源,但是它的创建也是有一定开销的,频繁地创建和销毁线程会降低程序的性能;此外应用程序可以创建线程的数量是受机器物理条
件制约的,过多的线程会耗尽机器的资源,因此我们在设计程序的时候需要限制并发线程的数量。解决这两个问题的通常做法是使用线程池。线程池在启动的时候一
次性初始化若干个线程(也可以根据负载按需启动,也有闲置一定时间的线程会被销毁的策略),然后程序把任务交给线程池去执行而不是直接交给某个线程执行,
由线程池给这些任务分配线程。当某个线程执行完一个任务后,线程池会把它设成空闲状态以备下一个任务重用而不是销毁它。线程池在初始化的时候需要指定线程
数量上限,当并发任务数量超过线程数量的时候,线程池不会再创建新的线程而是让新任务等待,这样我们就不在需要担心线程数量过多耗尽系统资源了。
JDK1.5开始为我们提供了标准的线程池。
4.1 执行器Executor
Java的线程池实现了以下Executor接口:
public interface
Executor {
void
execute(Runnable command);
}
在多线程编程中,执行器是一种常用的设计模式,它的好处在于提供了一种简单有效的编程模型,我们只需把需要并发处理的工作拆分成独立的任务,然后交给执行
器去执行即可而不必关心线程的创建,分配和调度。J2SE5.0主要提供了两种功能的执行器:ThreadPoolExecutor和
ScheduledThreadPoolExecutor。ThreadPoolExecutor是基本的线程池实
现,ScheduledThreadPoolExecutor在前者基础上增加了任务调度的功能,在把任务交给它时我们可以指定任务的执行时间,而不是立
刻执行。
java.util.concurrent.Executors是用来创建线程池的工厂类,通过它提供的工厂方法,我们可以方便地创建不同特性的线程池。
4.2 Future接口
Executor接口并没有看起来那么理想,有时候我们执行一个任务是要得到计算的结果,有时候我们需要对任务有更多控制,例如知道它是否完成,或者中途
终止它。返回void的execute方法并不能满足我们这些需求。当然我们可以在传入的Runnable类上下功夫来提供类似的功能,但是这样做繁琐且
容易出错。既然J2SE为我们提供了线程池的标准实现把我们从多线程编程中解放出来,这些常见的需求当然也会很好地满足。事实上线程池实现了一个更为丰富
的ExecutorService接口,它定义了执行任务并返回代表该任务的Future对象的submit方法。
通过Future接口,我们可以查看已经被提交给线程池执行的任务是否完成,获取执行的结果或者终止任务。
4.3 Runnable 和Callable 接口
实现了Runnable或Callable接口的类都可以作为任务提交给线程池执行,这两个接口的主要区别在于Callable的call方法有结果返回
并且可以抛出异常而Runnable的run方法返回void且不允许有可检查的异常抛出(只能抛runtime
exception)。因此如果我们的任务执行后有结果返回,应该使用Callable接口。
5 线程和集合类
5.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
5.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());
}
5.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) 。
相关推荐
《Java线程实战手册》是Java并发编程领域的一份重要参考资料,主要针对Java开发者,旨在帮助他们深入理解和熟练掌握Java中的多线程技术。虽然这里只提供了第一章的内容,但这一章通常会涵盖基础理论和核心概念,对于...
5. **多线程**:Java提供了一套强大的多线程机制,包括`Thread`类和`Runnable`接口。手册中详细解释了如何创建和管理线程,以及同步和通信机制,如`synchronized`关键字和`wait()`, `notify()`方法。 6. **网络编程...
Java API中文参考手册是Java开发者不可或缺的工具之一,它提供了详尽的Java类库文档,帮助程序员理解并使用Java平台的各种API。这份压缩包“JavaApi中文参考手册.zip”包含了全面的API信息,使得开发者无需在线查找...
《2010最新java开发必备参考手册》是一份针对Java开发者的重要资源,它涵盖了从初学者到高级程序员所需的各种知识。这份手册旨在提供全面、深入的Java编程指南,帮助开发者提升技能,解决实际问题。 一、Java语言...
在并发处理方面,Java 6.0提供了Concurrent包的扩展,如`java.util.concurrent`,包含了许多线程安全的数据结构和高级并发工具,如Future、ExecutorService和CyclicBarrier,这些工具大大简化了多线程编程。...
线程和并发处理也是Java的强项,`java.lang.Thread`类和`java.util.concurrent`包提供了多线程和高并发环境下的工具。 网络编程在Java中也很重要,`java.net`包提供了Socket和ServerSocket类,用于创建客户端和...
Java具有简单性、面向对象、分布式、健壮性、安全性、平台独立与可移植性、多线程、动态性等特点。Java可以编写桌面应用程序、Web应用程序、分布式系统和嵌入式系统应用程序等。本文档讲解Java 12基础语法、虚拟机和...
在Java技术参考手册中,你可以找到关于类库的详细信息,如核心API、集合框架、多线程、网络编程以及输入/输出流等内容。这些文档深入浅出地解释了如何使用Java的标准库来构建高效、稳定的应用程序。 J2EE(Java 2 ...
Java 2 类库参考手册是一本详尽的资源,旨在帮助开发者理解和利用Java平台的核心类库。这本书由包概述和类描述组成,是开发者在编程过程中查找特定类或类成员信息的重要工具。以下是对其中一些关键知识点的详细阐述...
5. **多线程**:Java内置了对多线程的支持,`java.lang.Thread`类和`Runnable`接口是创建线程的主要方式,`synchronized`关键字用于线程同步,`wait()`、`notify()`和`notifyAll()`方法控制线程间的协作。...
7. **多线程**:Java提供了丰富的线程API,如Thread类和Runnable接口,以及synchronized关键字、wait()和notify()方法等同步控制手段。理解线程的生命周期、并发编程的挑战以及如何使用并发工具类(如...
Java/jDK中文参考手册是Java开发者的必备工具之一,它详尽地解释了Java语言的语法、类库以及开发工具的使用方法。这份手册对于初学者和经验丰富的开发者都具有很高的价值,因为它提供了清晰易懂的解释和示例,帮助...
《Java完全参考手册(第8版)》这本书详细涵盖了这些新特性和Java语言的各个方面,包括基本语法、类库、异常处理、多线程、网络编程、I/O流、XML处理、数据库连接等。书中不仅有详尽的API说明,还有大量的实例代码,...
8. **多线程**:Java支持多线程编程,可以使用Thread类或Runnable接口创建线程,同步机制如synchronized关键字、wait/notify机制、Lock接口等用于处理并发问题。 9. **网络编程**:Java的Socket编程允许开发网络...
第七章 多线程 第八章 Java的"异常" 第九章 Java输入输出操作 java新手教程.chm JAVA_精通swing程序设计.chm JAVA_类库中文版Java API_Sun公司官方出版.chm JAVA_Thinking in Java(中文版 由yyc,spirit整理)....
Java中文参考手册包含了这些特性和更多API的详细说明,涵盖了类库、异常、多线程、网络编程、I/O、反射、安全管理等多个方面。对于开发者来说,这份手册是理解和掌握Java 5及更高版本的关键资源。通过查阅此手册,你...
Java 1.8 API中文手册是Java开发者的重要参考资料,它详细介绍了Java 1.8版本中的各种类库、接口、方法和异常等核心组件。这个手册以中文的形式提供了丰富的编程指南,帮助开发者理解和使用Java 1.8的特性。下面我们...
8. `java.concurrent`包:从Java 5开始引入,提供了线程管理和并发编程的支持,如ExecutorService、Future、Semaphore、CyclicBarrier等,有助于编写高效的多线程程序。 Java API手册还包含了详细的类和方法说明,...
Java2类库参考手册.....Java是Sun公司推出的新型面向对象程序设计语言。它将面向对象、平台无关性、稳固性、安全性、多线程等诸多特性集于一身,为用户提供了一个良好的程序设计环境。 Java提供了强大的应用程序...