15.java5的CyclicBarrier同步工具
例如:组织人员(线程)郊游,约定一个时间地点(路障),人员陆续到达地点,等所有人员全部到达,开始到公园各玩各的,再到约定时间去食堂吃饭,等所有人到齐开饭……
java.util.concurrent.CyclicBarrier
一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环的 barrier。
CyclicBarrier 支持一个可选的 Runnable 命令,在一组线程中的最后一个线程到达之后(但在释放所有线程之前),该命令只在每个屏障点运行一次。若在继续所有参与线程之前更新共享状态,此屏障操作很有用。
构造方法摘要 |
|
CyclicBarrier(int parties) 创建一个新的 CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,但它不会在启动 barrier 时执行预定义的操作。 |
|
CyclicBarrier(int parties, Runnable barrierAction) 创建一个新的 CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,并在启动 barrier 时执行给定的屏障操作,该操作由最后一个进入 barrier 的线程执行。 |
|
方法摘要 |
|
int |
await() 在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。 |
int |
await(long timeout, TimeUnit unit) 在所有参与者都已经在此屏障上调用 await 方法之前将一直等待,或者超出了指定的等待时间。 |
int |
getNumberWaiting() 返回当前在屏障处等待的参与者数目。 |
int |
getParties() 返回要求启动此 barrier 的参与者数目。 |
boolean |
isBroken() 查询此屏障是否处于损坏状态。 |
void |
reset() 将屏障重置为其初始状态。 |
例:
ExecutorService service =Executors.newCachedThreadPool();
final CyclicBarrier cb = newCyclicBarrier(3); 约定3个人
for (int i=0; i<3; i++)产生3个人
{ 每个人的任务
Runnablerunnable = newRunnable()
{
publicvoid run()
{ 开始出发到目的地
Thread.sleep((long)Math.random()*1000);
SOP(ThreadName即将到达集合点1,
当前已有cb.getNumberWaiting()+1个
+ (cb.getNumberWaiting()==2?"都到齐了,继续走啊":"正在等候"))
cb.await();到了其他人没来就等
人到齐了再继续进行
Thread.sleep((long)Math.random()*1000);
SOP(ThreadName即将到达集合点2)
cb.await();到了其他人没来就等
}
}
service.execute(runnable);
}
16.java5的CountDownLatch同步工具
好像倒计时计数器,调用CountDownLatch对象的countDown方法就将计数器减1,当到达0时,所有等待者就开始执行。
举例:多个运动员等待裁判命令: 裁判等所有运动员到齐后发布结果
代码示例:
ExecutorService service =Executors.newCachedThreadPool();
裁判发布命令的计数器,计数器为0,运动员就跑
final CountDownLatch cdOrder = new CountDownLatch(1);
运动员跑到终点的计数器,为0裁判宣布结果
final CountDownLatch cdAnswer = newCountDownLatch(3);
产生3个运动员
for (int i=0; i<3; i++)
{ 运动员的任务
Runnablerunnable = new Runnable(){
public void run()
{
SOP(ThreadName准备接受命令)
等待发布命令
cdOrder.await(); 计数器为0继续向下执行
SOP(ThreadName已接受命令) order计数器为0了
Thread.sleep(Random);开始跑步
cdAnswer.countDown();跑到终点了,计数器减1
}
};
service.execute(runnable);运动员开始任务
}
Thread.sleep(1000)裁判休息一会 再发布命令
SOP(即将发布命令)
cdOrder.countDown();命令计数器置为0,发布命令
SOP(命令已经发布,等待结果)
cdAnswer.await(); 等待所有运动员,计数器为0 所有运动员到位
SOP(宣布结果)
java.util.concurrent.CountDownLatch
一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。用给定的计数 初始化 CountDownLatch。由于调用了 countDown()方法,所以在当前计数到达零之前,await方法会一直受阻塞。之后,会释放所有等待的线程,await的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier。
CountDownLatch 是一个通用同步工具,它有很多用途。将计数 1 初始化的 CountDownLatch 用作一个简单的开/关锁存器,或入口:在通过调用 countDown()的线程打开入口前,所有调用 await的线程都一直在入口处等待。用 N 初始化的CountDownLatch 可以使一个线程在 N 个线程完成某项操作之前一直等待,或者使其在某项操作完成 N 次之前一直等待。
CountDownLatch 的一个有用特性是,它不要求调用 countDown 方法的线程等到计数到达零时才继续,而在所有线程都能通过之前,它只是阻止任何线程继续通过一个 await。
构造方法摘要 |
|
CountDownLatch(int count) 构造一个用给定计数初始化的 CountDownLatch。 |
|
方法摘要 |
|
void |
await() 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断。 |
boolean |
await(long timeout, TimeUnit unit) 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。 |
void |
countDown() 递减锁存器的计数,如果计数到达零,则释放所有等待的线程。 |
long |
getCount() 返回当前计数。 |
String |
toString() 返回标识此锁存器及其状态的字符串。 |
17.java5的Exchanger同步工具
用于实现两个人之间的数据交换,每个人在完成一定的事务后想与对方交换数据,第一个先拿出数据的人会一直等待第二个人,直到第二个人拿着数据到来时,才能彼此交换数据。
举例:毒品交易 双方并不是同时到达,有先有后,只有都到达了,瞬间交换数据,各自飞
代码演示:
ExecutorService service =Executors.newCachedThreadPool();
final Exchanger exchanger = newExchanger();
毒贩:
service.execute(new Runnable()
{ 毒贩做的事
public void run()
{
String(毒品) data1 = 毒品
SOP(毒贩正在将data1换出去)
Thread.sleep(Random)换的过程
毒贩到位了,拿着毒品等待毒人接头,接头后就能换到钱了
String data2 = (String)exchanger.exchange(data1);
SOP(毒贩换到了钱:data2)
}
});
毒人:
service.execute(new Runnable()
{ 吸毒人做的事
public void run()
{
String(钱) data1 = 钱
SOP(毒人正在将data1换出去)
Thread.sleep(Random)换的过程
吸毒人到位了,拿着钱等待毒贩接头,接头后就能换到毒品了
String data2 =(String)exchanger.exchange(data1);
SOP(毒人换到了毒品:data2)
}
});
java.util.concurrent.Exchanger
可以在对中对元素进行配对和交换的线程的同步点。每个线程将条目上的某个方法呈现给exchange方法,与伙伴线程进行匹配,并且在返回时接收其伙伴的对象。Exchanger 可能被视为 SynchronousQueue 的双向形式。Exchanger 可能在应用程序(比如遗传算法和管道设计)中很有用。
用法示例:以下是重点介绍的一个类,该类使用Exchanger 在线程间交换缓冲区,因此,在需要时,填充缓冲区的线程获取一个新腾空的缓冲区,并将填满的缓冲区传递给腾空缓冲区的线程。
class FillAndEmpty {
Exchanger
DataBuffer initialEmptyBuffer = ... a made-up type
DataBuffer initialFullBuffer = ...
class FillingLoop implements Runnable {
public void run() {
DataBuffer currentBuffer = initialEmptyBuffer;
try {
while (currentBuffer != null) {
addToBuffer(currentBuffer);
if (currentBuffer.isFull())
currentBuffer = exchanger.exchange(currentBuffer);
}
} catch (InterruptedException ex) { ... handle ... }
}
}
class EmptyingLoop implements Runnable {
public void run() {
DataBuffer currentBuffer = initialFullBuffer;
try {
while (currentBuffer != null) {
takeFromBuffer(currentBuffer);
if (currentBuffer.isEmpty())
currentBuffer = exchanger.exchange(currentBuffer);
}
} catch (InterruptedException ex) { ... handle ...}
}
}
void start() {
new Thread(new FillingLoop()).start();
new Thread(new EmptyingLoop()).start();
}
}
构造方法摘要 |
|
Exchanger() 创建一个新的 Exchanger。 |
|
方法摘要 |
|
V |
exchange(V x) 等待另一个线程到达此交换点(除非当前线程被中断),然后将给定的对象传送给该线程,并接收该线程的对象。 |
V |
exchange(V x, long timeout, TimeUnit unit) 等待另一个线程到达此交换点(除非当前线程被中断,或者超出了指定的等待时间),然后将给定的对象传送给该线程,同时接收该线程的对象。 |
18.java5阻塞队列的应用
队列包含固定长度的队列和不固定长度的队列,先进先出
固定长度的队列往里放数据,如果放满了还要放,阻塞式队列就会等待,直到有数据取出,空出位置后才继续放;非阻塞式队列不能等待就只能报错了。
讲Condition时提到了阻塞队列的原理,Java中已经实现了阻塞队列ArrayBlockingQueue
BlockingQueue
支持两个附加操作的Queue,这两个操作是:获取元素时等待队列变为非空,以及存储元素时等待空间变得可用。
BlockingQueue 方法以四种形式出现,对于不能立即满足但可能在将来某一时刻可以满足的操作,这四种形式的处理方式不同:第一种是抛出一个异常,第二种是返回一个特殊值(null 或 false,具体取决于操作),第三种是在操作可以成功前,无限期地阻塞当前线程,第四种是在放弃前只在给定的最大时间限制内阻塞。下表中总结了这些方法:
|
抛出异常 |
特殊值 |
阻塞 |
超时 |
插入 |
add(e) |
offer(e) |
put(e) |
offer(e, time, unit) |
移除 |
remove() |
poll() |
take() |
poll(time, unit) |
检查 |
element() |
peek() |
不可用 |
不可用 |
BlockingQueue 不接受 null 元素。试图 add、put 或offer 一个 null 元素时,某些实现会抛出 NullPointerException。null 被用作指示poll 操作失败的警戒值。
BlockingQueue 可以是限定容量的。它在任意给定时间都可以有一个 remainingCapacity,超出此容量,便无法无阻塞地 put 附加元素。没有任何内部容量约束的 BlockingQueue 总是报告 Integer.MAX_VALUE 的剩余容量。
BlockingQueue 实现主要用于生产者-使用者队列,但它另外还支持 Collection 接口。因此,举例来说,使用remove(x) 从队列中移除任意一个元素是有可能的。然而,这种操作通常不会有效执行,只能有计划地偶尔使用,比如在取消排队信息时。
BlockingQueue 实现是线程安全的。所有排队方法都可以使用内部锁或其他形式的并发控制来自动达到它们的目的。然而,大量的 Collection 操作(addAll、containsAll、retainAll 和 removeAll)没有 必要自动执行,除非在实现中特别说明。因此,举例来说,在只添加了c 中的一些元素后,addAll(c) 有可能失败(抛出一个异常)。
java.util.concurrent.ArrayBlockingQueue
extends AbstractQueue
一个由数组支持的有界阻塞队列。此队列按 FIFO(先进先出)原则对元素进行排序。队列的头部 是在队列中存在时间最长的元素。队列的尾部是在队列中存在时间最短的元素。新元素插入到队列的尾部,队列获取操作则是从队列头部开始获得元素。
这是一个典型的“有界缓存区”,固定大小的数组在其中保持生产者插入的元素和使用者提取的元素。一旦创建了这样的缓存区,就不能再增加其容量。试图向已满队列中放入元素会导致操作受阻塞;试图从空队列中提取元素将导致类似阻塞。
此类支持对等待的生产者线程和使用者线程进行排序的可选公平策略。默认情况下,不保证是这种排序。然而,通过将公平性 (fairness) 设置为 true 而构造的队列允许按照FIFO 顺序访问线程。公平性通常会降低吞吐量,但也减少了可变性和避免了“不平衡性”。
此类及其迭代器实现了Collection 和Iterator 接口的所有可选方法。
此类是JavaCollections Framework 的成员。
构造方法摘要 |
|
ArrayBlockingQueue(int capacity) 创建一个带有给定的(固定)容量和默认访问策略的 ArrayBlockingQueue。 |
|
ArrayBlockingQueue(int capacity, boolean fair) 创建一个具有给定的(固定)容量和指定访问策略的 ArrayBlockingQueue。 |
|
ArrayBlockingQueue(int capacity, boolean fair, Collection extends E> c) 创建一个具有给定的(固定)容量和指定访问策略的 ArrayBlockingQueue,它最初包含给定 collection 的元素,并以 collection 迭代器的遍历顺序添加元素。 |
|
方法摘要 |
|
boolean |
add(E e) 将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回 true,如果此队列已满,则抛出 IllegalStateException。 |
void |
clear() 自动移除此队列中的所有元素。 |
boolean |
contains(Object o) 如果此队列包含指定的元素,则返回 true。 |
int |
drainTo(Collection super E> c) 移除此队列中所有可用的元素,并将它们添加到给定 collection 中。 |
int |
drainTo(Collection super E> c, int maxElements) 最多从此队列中移除给定数量的可用元素,并将这些元素添加到给定 collection 中。 |
Iterator<E> |
iterator() 返回在此队列中的元素上按适当顺序进行迭代的迭代器。 |
boolean |
offer(E e) 将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回 true,如果此队列已满,则返回 false。 |
boolean |
offer(E e, long timeout, TimeUnit unit) 将指定的元素插入此队列的尾部,如果该队列已满,则在到达指定的等待时间之前等待可用的空间。 |
E |
peek() 获取但不移除此队列的头;如果此队列为空,则返回 null。 |
E |
poll() 获取并移除此队列的头,如果此队列为空,则返回 null。 |
E |
poll(long timeout, TimeUnit unit) 获取并移除此队列的头部,在指定的等待时间前等待可用的元素(如果有必要)。 |
void |
put(E e) 将指定的元素插入此队列的尾部,如果该队列已满,则等待可用的空间。 |
int |
remainingCapacity() 返回在无阻塞的理想情况下(不存在内存或资源约束)此队列能接受的其他元素数量。 |
boolean |
remove(Object o) 从此队列中移除指定元素的单个实例(如果存在)。 |
int |
size() 返回此队列中元素的数量。 |
E |
take() 获取并移除此队列的头部,在元素变得可用之前一直等待(如果有必要)。 |
Object[] |
toArray() 返回一个按适当顺序包含此队列中所有元素的数组。 |
|
toArray(T[] a) 返回一个按适当顺序包含此队列中所有元素的数组;返回数组的运行时类型是指定数组的运行时类型。 |
String |
toString() 返回此 collection 的字符串表示形式。 |
阻塞队列的实现原理(Condition锁中有提到await signal)
19.java5同步集合类的应用
传统集合实现同步的问题
举了一个例子:Map集合线程不同步导致的问题。
解决办法:使用同步的Map集合 使用集合工具类中的方法将不同步的集合转为同步的Collections.synchronizedMap(newMap())这个方法返回一个同步的集合
publicstatic
{return newSynchronizedMap
SynchronizedMap类相当于一个代理类,通过查看源代码发现:该类中的所有方法都是直接返回:原Map集合方法调用后的结果,只是将返回结果的代码放在了同步代码块中以实现同步,构造是将同步锁默认置为当前对象。
HashSet与HashMap的关系与区别:
HashSet是单列的,HashMap是双列的(键值对)
关系:HashSet内部使用的是HashMap中的键,不考虑值。
查看HashSet的源代码发现其内部就是用HashMap实现的,只是没有使用HashMap的V,只使用了它的K。
JDK1.5中提供了并发 Collection:提供了设计用于多线程上下文中的 Collection 实现:
ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet、CopyOnWriteArrayList 和 CopyOnWriteArraySet。当期望许多线程访问一个给定 collection 时,ConcurrentHashMap 通常优于同步的 HashMap,ConcurrentSkipListMap 通常优于同步的 TreeMap。当期望的读数和遍历远远大于列表的更新数时,CopyOnWriteArrayList优于同步的ArrayList。
ConcurrentSkipListMap
ConcurrentSkipListSet
CopyOnWriteArrayList
这一般需要很大的开销,但是当遍历操作的数量大大超过可变操作的数量时,这种方法可能比其他替代方法更有效。在不能或不想进行同步遍历,但又需要从并发线程中排除冲突时,它也很有用。“
CopyOnWriteArraySet
它最适合于具有以下特征的应用程序:set 大小通常保持很小,只读操作远多于可变操作,需要在遍历期间防止线程间的冲突。它是线程安全的。 因为通常需要复制整个基础数组,所以可变操作(add、set 和 remove 等等)的开销很大。 迭代器不支持可变 remove 操作。 使用迭代器进行遍历的速度很快,并且不会与其他线程发生冲突。在构造迭代器时,迭代器依赖于不变的数组快照。
传统集合中存在的其它问题:对集合迭代时,不能对集合中的元素进行修改(添加、删除……),Java5中提供的并发集合就解决了这个问题。
20. 空中网挑选实习生的面试题1
现有的程序代码模拟产生了16个日志对象,并且需要运行16秒才能打印完这些日志,请在程序中增加4个线程去调用parseLog()方法来分头打印这16个日志对象,程序只需要运行4秒即可打印完这些日志对象。原始代码如下:
public class Test1
{
publicstatic void main(String[] args)
{
SOP(begin:+sys.currentTimeMillis()/1000);
//模拟处理16行日志,下面的代码产生16个日志对象,需运行16秒才能打印完
//修改程序代码,开4个线程让这16个日志在4秒钟打完
for (iint i=0; i<16; i++) //这行代码不能改动
{
final String log = “”+(i+1); //这行代码不能改动
{
Test1.parseLog(log);
}
}
}
//parseLog方法内部代码不能改动
public staticvoid parseLog(String log)
{
SOP(log+”:”+(sys.currentTimeMillis()/1000));
try
{
Thread.sleep(1000);
}
catch(InterruptedException e)
{
e.printStackTrace();
}
}
}
刚看到题目还想着很简单;直接在Test.parseLog(log)的地方new4个线程,都执行打印任务即可,仔细一看不行,在这里new4个线程的话就是16*4个线程了,所以要将线程在for循环外边创建出来,for内部将产生的日志对象装在一个共享变量里,在线程内部从共享变量中取数据打印。要考虑线程同步互斥问题,这个共享变量要具备同步功能,可以使用ArrayBlockingQueue这种阻塞式队列来存储日志对象。也可以使用普通集合,但拿数据要考虑同步问题,可能会浪费时间。
在for循环外部创建线程,定义共享变量
finalBlockingQueue
for(int i=0; i<4; i++) 创建4个线程
{
new Thread(new Runnable()
{
public void run()
{
while (true)
{ 先从集合中取到日志对象,再打印
String log =queue.take(); 要处理异常
parseLog(log);
}
}
}).start();
}
在16次for循环内部将产生的日志对象装入共享变量中
queue.put(log); 要处理异常
这道题只要用到同步集合来共享数据就可以了 List集合的Vector也可以
21. 空中网挑选实习生的面试题2
现成程序中的Test类中的代码在不断地产生数据,然后交给TestDo.doSome()方法去处理。就好像生产者不断地产生数据,消费者不断地消费数据。请将程序改造成有10个线程来消费生产者产生的数据,这些消费者都调用TestDo.doSome()方法去进行处理,故每个消费者都需要一秒才能处理完,程序应保证这些消费者线程依次有序地消费数据,只有上一个消费者消费完后,下一个消费者才能消费数据,下一个消费者是谁都可以,但要保证这些消费者线程拿到的数据是有序的。原始代码如下:
public class Test2
{
publicstatic void main(String[] args)
{
SOP(begin+sys.currentTimeMillis()/1000);
for (int i=0; i<10; i++) //这行不能改动
{
String input = i+””; //这行不能改动
String output =TeatDo.doSome(input);
SOP(ThreadName+output);
}
}
}
//不能改动此TestDo类
class TestDo
{
publicstatic String doSome(String input)
{
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
String output = input + “:” + (Sys.currentTimeMillis());
return output;
}
}
看这题和上一题差不多,也是数据共享问题了,弄一个同步集合存起来。
用同样的方法一样解决 new ArrayBlockingQueue()
张老师又讲了另一个同步队列:SynchronousQueue一种阻塞队列,其中每个插入操作必须等待另一个线程的对应移除操作,反之亦然。同步队列没有任何内部容量,甚至连一个队列的容量都没有。不能在同步队列上进行 peek,因为仅在试图要移除元素时,该元素才存在;除非另一个线程试图移除某个元素,否则也不能(使用任何方法)插入元素;也不能迭代队列,因为其中没有元素可用于迭代。
SynchronousQueue是一个特殊队列,即便是空的也不能插入元素,也读不到元素,要往里边插入的时候如果没有读取操作,插入操作就会阻塞,等到有读取操作出现时,插入操作检测到了读取操作,才能把数据插入进去,而读取操作正好可以拿到刚刚插入进去的数据。就好比毒品买卖,我拿着毒品给谁呢,只有买毒品的人来了,才能立马给他,他也拿到了。与Exchanger类似,不过Exchanger是单对单的交换,SynchronousQueue可以多个抢数据,我拿着毒品等人来买,一下来了3个人买,谁抢到了就是谁的;或者我拿3包毒品,3个人同时每人一份。
这道题用synchronousQueue的话会一下子将10个数据全打印出来,因为10次循环一次放一个并没有人来取,所以没有放进去,后来一下10个线程来取数据,就一下放进去拿走了。我测试的时候没有这种情况,都是间隔一秒一秒的。测试后发现,将doSome处理后的结果存进去,就会有间隔,而直接存进去,取数据后再处理的话就是一下一片了。分析后知道:put时没有take,10个数据都在等待存入,如果存入的数据是doSome(input)的话,开始取数据时才开始执行doSome所以就会有间隔了。直接存数据,取出后在doSome就是一下拿到10个数据了。
要解决这个问题,可以使用厕所抢坑的方式解决,使用Semaphore来获取许可,每取一次数据释放一次即可。
final Semaphorex = new Semaphore(1); 一次一个
finalSynchronousQueue queue = new SynchronousQueue();
每次取数据前都要先获取许可
x.acquire();
取完后释放许可
x.release();
这种方式与使用Lock方式一样
22. 空中网挑选实习生的面试题3
现有程序同时启动了4个线程去调用TestDo.doSome(key, value)方法,由于TestDo.doSome(key, value)方法内的代码是先暂停1秒,然后再输出以秒为单位的当前时间值,所以,会打印出4个相同的时间值,如下所示:
4:4:1258199615
1:1:1258199615
3:3:1258199615
1:2:1258199615
请修改代码,如果有几个线程调用TestDo.doSome(key,value)方法时,传递进去的key相等(equals比较为true),则这几个线程应互斥排队输出结果,即当有两个线程的key都是“1”时,她们中的一个要比另外其他线程晚1秒输出结果,如下所示:
4:4:1258199615
1:1:1258199615
3:3:1258199615
1:2:1258199616
总之,当每个线程中指定的key相等时,这些相等key的线程应每隔一秒依次输出时间值(要用互斥),如果key不同,则并行执行(相互之间不互斥)。原始代码如下:
//不能改动此Test类
public class Test3 extends Thread
{
privateTestDo testDo;
privateString key;
privateString value;
publicTest3(String key, String key2, String value)
{
this.testDo = TestDo.getInstance();
/*常量“1”和 “1”是同一个对象,下面这行代码就是要用“1”+“”的
方式产生新的对象,以实现内容没有改变,仍然相等(都还为“1”),
但对象却不再是同一个的效果
*/
this.key = key + key2;
this.value = value;
}
publicstatic void main(String[] args) throws InterruptedException
{
Test3 a = new Test3(“1”, “”, “1”);
Test3 b = new Test3(“1”, “”, “2”);
Test3 c = new Test3(“3”, “”, “3”);
Test3 d = new Test3(“4”, “”, “4”);
SOP(begin+:+sys.currentTimeMillis()/1000);
a.start();
b.start();
c.start();
d.start();
}
publicvoid run()
{
testDo.doSome(key, value);
}
}
class TestDo
{
privateTestDo(){}
privatestatic TestDo _instance = new TestDo();
public staticTestDo getInstance()
{
return _instance;
}
public voiddoSome(Object key, String value)
{
//此大括号内的代码是需要局部同步的代码,不能改动!
{
try
{
Thread.sleep(1000);
SOP(key+”:”+value+”:”+sys.currentTimeMillis()/1000);
}
catch (InterruptedExceptione)
{
e.printStackTrace();
}
}
}
}
看完这道题第一个想法是在标记位置加上同步代码块,但是锁不好弄了,因为每次都新建了一个key对象来接受实际key,没法获取到实际key对象。
想到了Lock对象,所以建一个Lock对象,判断key的值是否和指定值“1“相同,如果相同就锁上,不同不管,finally里在解锁前进行判断,避免没上锁还要解锁发生问题。
Lock lock = newReentrantLock();
publicvoid doSome(Object key, String value)
{
if(key.equals("1"))
lock.lock();
//System.out.println("OKOKOOK");
//synchronized("1")
try
//此大括号内的代码是需要局部同步的代码,不能改动!
{
try
{
Thread.sleep(1000);
System.out.println(key+":"+value+":"+System.currentTimeMillis()/1000);
}
catch(InterruptedException e)
{
e.printStackTrace();
}
}finally
{
if(key.equals("1"))
lock.unlock();
}
}
但上面的方式写死了,如果换2呢 还要改代码,现在要不管是什么只要相同都互斥,将这些加进来的key放到一个集合ArrayList中,每次传进来一个key,先把传进来的key作为锁对象,再判断这个对象有没有存在锁集合中,如果没有,就把它存进去,同时就用这个key做锁;如果已经存在了,就是说这个key已经做过锁对象了,就需要将以前做锁的那个对象拿出来,再让它来当锁,与传进来的key对象一样,这样就产生互斥效果了。
需要注意:拿原来的锁对象时要迭代锁集合,因为有多个线程在运行,所以迭代时有可能出现其他线程的key没有做过锁,需要将它加到锁集合中,可是这时候这个线程还在迭代过程中,迭代时不能操作集合中的数据,就会发生异常。要解决这个问题,就需要用到同步集合了。CopyOnWriteArrayList
使用ArrayList时就经常出异常,换CopyOnWriteArrayList后没有异常了
//将所有传过来的key都存起来
//privateList
privateCopyOnWriteArrayList
publicvoiddoSome(Object key, String value)
{
//先用这个key当锁,用过一次就存到集合中
Objecto = key;
//判断这个锁用过没有
if (!keys.contains(o))
{
//如果这个key没有用过,就用它当锁,把它存到锁集合中
keys.add(o);
}
else //锁集合中已经有了这个key
{
//这个key已经当过锁了,就把它拿出来,还用它做锁,就和现在的key互斥了
//因为不知道原来key的位置,所有需要进行遍历
for(Iterator
{
//当前遍历到的对象
Objectoo = it.next();
//如果找到了,就让它做锁
if(oo.equals(o))
{
o= oo;
break; //找到了,不用再循环了
}
}
//o= keys.get(keys.indexOf(o)); //key和o不是同一个对象,拿不到
}
synchronized(o)
//此大括号内的代码是需要局部同步的代码,不能改动!
{
try
{
Thread.sleep(1000);
System.out.println(key+":"+value+":"+System.currentTimeMillis()/1000);
}
catch(InterruptedException e)
{
e.printStackTrace();
}
}
}
a = “1”+””;
b = “1”+””;
a和b是同一个对象,常量相加equals为真 ==为假
Object o1 = new String("1");
Object o2 = new String("1");
System.out.println(o1==o2); //false
System.out.println(o1.equals(o2)); //true
System.out.println(o1); //1
System.out.println(o2); //1
Object o3 = "1"+"";
Object o4 = "1"+"";
System.out.println(o3==o4); //true
System.out.println(o3.equals(o4)); //true
Object o5 = "2"+"";
Object o6 = get("2","");
System.out.println(o5==o6); //false
System.out.println(o5.equals(o6)); //true
System.out.println(o5+"__"+o6); //2__2
publicstatic Object get(String a,String b)
{
return a+b;
}
11.java5的线程锁技术
java.util.concurrent.locks 为锁和等待条件提供一个框架的接口和类,
接口摘要 |
||
Condition |
Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。 |
|
Lock |
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。 |
|
ReadWriteLock |
ReadWriteLock 维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。 |
|
类摘要 |
||
AbstractOwnableSynchronizer |
可以由线程以独占方式拥有的同步器。 |
|
AbstractQueuedLongSynchronizer |
以 long 形式维护同步状态的一个 AbstractQueuedSynchronizer 版本。 |
|
AbstractQueuedSynchronizer |
为实现依赖于先进先出 (FIFO) 等待队列的阻塞锁和相关同步器(信号量、事件,等等)提供一个框架。 |
|
LockSupport |
用来创建锁和其他同步类的基本线程阻塞原语。 |
|
ReentrantLock |
一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。 |
|
ReentrantReadWriteLock |
支持与 ReentrantLock 类似语义的 ReadWriteLock 实现。 |
|
ReentrantReadWriteLock.ReadLock |
ReentrantReadWriteLock.readLock() 方法返回的锁。 |
|
ReentrantReadWriteLock.WriteLock |
ReentrantReadWriteLock.writeLock() 方法返回的锁。 |
|
Lock比传统线程模型中的synchronized更加面向对象,锁本身也是一个对象,两个线程执行的代码要实现同步互斥效果,就要使用同一个锁对象。锁要上在要操作的资源类的内部方法中,而不是线程代码中。
public interface Lock
所有已知实现类:
ReentrantLock, ReentrantReadWriteLock.ReadLock,ReentrantReadWriteLock.WriteLock
随着灵活性的增加,也带来了更多的责任。不使用块结构锁就失去了使用 synchronized 方法和语句时会出现的锁自动释放功能。在大多数情况下,应该使用以下语句:
Lock l = ...;
l.lock();
try {
// access the resource protected by this lock
} finally {
l.unlock();
}
锁定和取消锁定出现在不同作用范围中时,必须谨慎地确保保持锁定时所执行的所有代码用 try-finally 或 try-catch 加以保护,以确保在必要时释放锁。
方法摘要 |
|
void |
lock() 获取锁。 |
void |
lockInterruptibly() 如果当前线程未被中断,则获取锁。 |
Condition |
newCondition() 返回绑定到此 Lock 实例的新 Condition 实例。 |
boolean |
tryLock() 仅在调用时锁为空闲状态才获取该锁。 |
boolean |
tryLock(long time, TimeUnit unit) 如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁。 |
void |
unlock() 释放锁。 |
Lock与synchronized对比,打印字符串例子