接口Runnable
和类Thread
的区别
Runnable -> run()
: 不是有单独的线程驱动的,需要依托其他线程Thread -> run()
: 具有自己的线程
使用Executor
进行线程管理
- 不占用当前启动线程的资源
- 程序会在调用
shutdown()
之前提交的所有任务完成后结束 ExecutorService
:CachedThreadPool
: 为每一个任务创建一个线程(一般用这个就好)FixedThreadPool
: 可以控制线程的数量SingleThreadPool
: 仅有一个线程,类似线程数量为1的FixedThreadPool
- 范例:
class LiftOff implements Runnable { private static int taskCount = 0; private final int id = taskCount++; @Override public void run() { for(int i=0; i<10; i++) System.out.println("["+id+"]("+Thread.currentThread().getId()+") " + i); } public static void main(String[] args) { ExecutorService exec = Executors.newCachedThreadPool(); // ExecutorService exec = Executors.newFixedThreadPool(3); // 使用有限的线程集完成并发 for(int i=0; i<5; i++) exec.execute(new LiftOff()); // 防止新任务被提交给这个Executor exec.shutdown(); } }
线程的一些基本操作
休眠
Thread.sleep(milliseconds)
: 休眠一段时间,参数为毫秒
优先级
- 查看优先级:
Thread.currentThread().getPriority()
- 设置优先级:
Thread.currentThread().setPriority()
- 参数
Thread.MAX_PRIORITY
最高(10)Thread.NORM_PRIORITY
中等(5)Thread.MIN_PRIORITY
最低(1)
- 注意: 尽管JDK有10个优先级,但它与多数操作系统都不能映射得很好。比如Windows7有7个优先级且不固定,所以这种映射关系也不是确定的。唯一可移植的方法是当调整优先级别的时候,只使用
MAX_PRIORITY
、NORM_PRIORITY
和MIN_PRIORITY
三种级别。
让步
使用Thread.yeild()
进行暗示,申请可以将资源调度给其他线程使用,但系统未必会切换线程
后台线程
- 程序在运行时在后台提供一种通用服务的线程,并且这种线程并不属于程序中不可或缺的部分
- 当所有的非后台线程结束后,程序也就终止了,同时会杀死所有后台进程
- 将线程设置为后台线程需要在线程启动前设置
thread.setDaemo(true)
加入一个线程(强制执行)
如果在某个线程x
中让线程t
上调用t.join()
,线程x
将被挂起,直到线程t
结束才恢复(即it.isAlive()
返回为false
)。对t.join()
方法的调用可以被中断,做法为调用x.interrupt()
。如果线程x
被中断或正常结束,线程t
也将和x
一同结束
线程运行中出现异常
- 在线程中捕获异常,默认会向外传播到外层
- 使用
Executor
来管理线程的创建,在每个创建时附上一个异常处理器Thread.UncaughtExceptionHandler
/** 测试方法 */
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool(new CacheExceptionThreadFactory());
exec.execute(new Thread() {
@Override
public void run() {
System.out.println("R线程名: " + getId());
System.out.println("R: " + currentThread().getUncaughtExceptionHandler().toString());
throw new RuntimeException("给你个异常瞧瞧");
}
});
exec.shutdown();
}
/** 异常处理器 */
class myUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
public void uncaughtException(Thread t, Throwable e) {
System.out.println("E线程名: " + t.getId() + "异常: " + e.getMessage());
}
@Override
public String toString() {
return "哈哈"+super.toString();
}
}
/** 线程生成工厂 */
class CacheExceptionThreadFactory implements ThreadFactory {
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setUncaughtExceptionHandler(new myUncaughtExceptionHandler());
return t;
}
}
资源共享竞争
volatile
修饰属性。保证数据在被修改后立即能写回内存,使得其他线程能读取到修改后的数据
synchronized
修饰方法或语句块。在上一个调用方法结束之前,其他调用该方法的线程全都被阻塞。
* 同步控制块:
synchronized(syncObject) {
// This code can be access by only task at a time
}
Lock
使用Lock
实现互斥机制,相比于synchronized
的简洁性,显式使用Lock
可以通过finally
将系统维护在正确的状态,而不像synchronized
出现错误后仅仅抛出一个异常。
private Lock lock = new ReentrantLock();
public void readOrWrite() {
lock.lock();
try{
// some operators
// return 必须出现在try{}中,确保unlock()不会过早发生,将数据暴露给下一个任务
return;
} finally {
lock.unlock();
}
}
tryLock(long timeout, TimeUnit unit)
可以设置获取锁的时间,如果在设定的时间内无法获取锁,可以先进行其他操作。
原子性
对基本数据类型的读取和赋值操作被认为是安全的原子性操作。
原子类:
AtomicInteger
AtomicLong
AtomicReference
等
自增线程安全性测试及解决方案
- 原因: 多个线程同时访问共享变量
i
,而JVM允许每个线程存储变量的副本,i++
的操作可以分为三步: 取值、自增、写回。存在一个线程在 自增 时,刚好有线程在 取值,因此最后会出现i
增加的结果总比预计的结果线程小。 - 测试例:
class TestIPlus {
private int val = 0;
public void run() {
for(int i=0; i<10; i++) {
this.val = 0;
final CountDownLatch count = new CountDownLatch(10000);
for(int j=0; j<100; j++) {
new Thread(){
@Override
public void run() {
for(int i=0; i<100; i++) {
TestIPlus.this.val++;
count.countDown();
}
}
}.start();
}
try {
count.await();
} catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.val);
}
}
}
使
i++
变得线程安全有3种方式:使用
synchronized
关键字,将i++
写成一个方法,并使用synchronized
修饰public synchronized void incI() { this.i++; }
使用
Lock
,在修改i
的位置加锁
private Lock lock = new ReentrantLock(); public void incI() { lock.lock(); try { i++; } finally { lock.unlock(); } }
- 使用原子类
AtomicInteger
class TestIPlus { private AtomicInteger val; public void run() { for(int i=0; i<10; i++) { this.val = new AtomicInteger(0); final CountDownLatch count = new CountDownLatch(10000); for(int j=0; j<100; j++) { new Thread() { @Override public void run() { for(int i=0; i<100; i++) { // 原子类自增 TestIPlus.this.val.getAndIncrement(); count.countDown(); } } }.start(); } try { count.await(); } catch(InterruptedException e) { e.printStackTrace(); } System.out.println(this.val); } } }
线程本地存储
防止任务在共享资源上产生冲突的第二种方式是根除对变量的共享。线程本地存储是一种自动化机制,可以为使用相同变量的每个不同的线程创建不同的存储。因此,如果你有5个线程都要使用变量x所表示的对象,那线程本地存储就会生成5个用于x的不同的存储块。创建和管理线程本地存储可以由java.lang.ThreadLocal
类来实现。ThreadLocal
对象通常当做静态域存储,在创建时,只能通过get()
和set()
方法来访问该对象的内容。
class ThreadLocalVariableHolder {
private static ThreadLocal<Integer> val = new ThreadLocal<Integer>() {
private Random rand = new Random(47);
protected Integer initialValue() {
return rand.nextInt(1000);
}
};
public static void increment() {
val.set(val.get() + 1);
}
public static Integer getValue() {
return val.get();
}
}
class Accessor implements Runnable {
private final int id;
public Accessor(int id) {
this.id = id;
}
@Override
public void run() {
while(!Thread.currentThread().isInterrupted()) {
ThreadLocalVariableHolder.increment();
// 每个线程都有自己的val
System.out.println(this);
}
}
@Override
public String toString() {
return "#" + id + ": " + ThreadLocalVariableHolder.getValue();
}
}
线程四种状态
- 新建(New):当线程被创建时,它只会短暂的处于这种状态。此时它已经分配了必需的系统资源,并执行了初始化。此刻线程已经有资格获得CPU时间了,之后调度器将把这个线程转变为可运行状态或阻塞状态。
- 就绪(Runnable):在这种状态下,只要调度器把时间片分配给线程,线程就可以运行。也就是说,在任意时刻,线程可以运行也可以不运行。只要调度器能分配到时间片给线程,它就可以运行。
- 阻塞(Blocked):线程能够运行,但有某个条件阻止了它的运行。当线程处于阻塞状态时,调度器将忽略线程,不会分配给线程任何CPU时间。直到线程重新进入就绪状态,它才有可能执行操作。
- 死亡(Dead):处于死亡或终止状态的线程将不会再是可调度的,并且再也不会得到CPU时间,它的任务已结束,或不再是可运行的。任务死亡的通常方式是从
run()
方法返回,但是任务的线程还可以被中断。
线程出现阻塞的原因
- 调用
sleep(millseconds)
使任务进入休眠。 - 调用
wait()
使线程挂起。直到线程得到notify()
或notifyAll()
消息(或者在JavaSE5的java.util.concurrent
类库中等价的signal()
或signalAll()
消息)使线程进入就绪状态。 - 任务在等待某个输入/输出完成。
- 任务试图在某个对象上调用其同步控制方法,但是对象锁不可用,因为另一个任务已经获得了这个锁。
中断任务
Thread.interrupt()
: 将线程设置为中断状态- 通过
Executor
管理线程,shutdownNow()
可关闭由它启动的所有线程,或者使用submit(runnable)
后,获得线程的上下文Future<?>
,通过调用cancel()
来关闭某一线程。 - 注意1:
I/O
和在synchronized
块上的等待是不可中断的 注意2: 一个任务能调用在同一对象中的其他的
synchronized
方法,因为这个对象已经持有锁了public class MultiLock { public synchronized void f1(int count) { if(count-- > 0) { System.out.println("f1() calling f2() with count " + count); f2(count); } } public synchronized void f2(int count) { if(count-- > 0) { System.out.println("f2() calling f1() with count " + count); f1(count); } } public static void main(String[] args) { final MultiLock multiLock = new MultiLock(); new Thread() { public void run() { multiLock.f1(10); } }.start(); } } /* output: f1() calling f2() with count 9 f2() calling f1() with count 8 f1() calling f2() with count 7 f2() calling f1() with count 6 f1() calling f2() with count 5 f2() calling f1() with count 4 f1() calling f2() with count 3 f2() calling f1() with count 2 f1() calling f2() with count 1 f2() calling f1() with count 0 */
线程之间的协作
任务协作时,关键问题是这些问题间的握手。为了实现这种握手,我们使用了相同的基础特性:互斥。在这种情况下,互斥能够确保只有一个任务能够响应某个信号,这样就可以根除任何可能的竞争条件。在互斥之上,我们为任务添加了一种途径,可以将自身挂起,直到某些外部条件发生变化,表示是时候让任务向前开动了为止。这种握手问题可以通过Object
的方法wait()
和notify()
来安全的实现。Java SE 1.5 的并发类库还提供了await()
和signal()
方法的Condition
对象。
wait()
和notify()
- 当调用
sleep()
或yield()
时锁并没有释放。当调用wait()
时,线程的执行被挂起,对象上的锁被释放。 - 只能在同步控制方法中或同步控制块中调用
wait()
、notify()
或notifyAll()
。如果在非同步控制方法里调用这些方法,程序能通过编译,但运行的时候,将得到IllegalMonitorStateException
异常。 测试例
class TestThread implements Runnable { public final int id; public TestThread(int id) { this.id = id; } public synchronized void wake(){ this.notify(); } @Override public void run() { System.out.println("#" + this.id + "sleep..."); try{ synchronized (this) { this.wait(); } System.out.println("unlock...."); } catch (InterruptedException e) { System.out.println("[InterruptedException]" + "#" + this.id); } System.out.println("#" + this.id + "ok..."); } } /** 测试语句 */ Runnable runnable = new TestThread(0); Thread thread = new Thread(runnable); thread.start(); try{ Thread.sleep(2000); } catch (InterruptedException e) { System.out.println("InterruptedException..."); } synchronized(runnable) { System.out.println("hello"); runnable.notify(); }
awite()
、signal()
和signalAll()
- 使用互斥并允许任务挂起的基本类
Condition
,你可以通过在Condition
上调用await()
来挂起一个任务,或通过signal()
来唤醒一个任务(根据在Condition
上的挂起顺序)或调用signalAll()
来唤醒所有在Condition
上挂起的任务。 测试例
class TestCondition { private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); public void f1() { lock.lock(); try { Thread.sleep(500); System.out.println("f1() is running and waitting ..."); condition.await(); System.out.println("f1() wait over..."); } catch(InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void f2() { lock.lock(); try { Thread.sleep(500); System.out.println("f2() is running and waitting ..."); condition.await(); System.out.println("f2() wait over..."); } catch(InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void f3() { lock.lock(); try { Thread.sleep(2000); System.out.println("f3() wake some thread..."); this.condition.signal(); System.out.println("f3() wake some thread over..."); // this.condition.signalAll(); Thread.yield(); } catch(InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } }
同步队列BlockingQueue
- 使用同步队列来解决任务协作任务,同步队列在任何时刻都只允许一个任务插入或移除元素。通过
put()
存放元素,通过take()
取出元素。 - 示例(生产吐司面包,两台机器制作,一台抹黄油,一台抹果酱)
public class D0723 {
public static void main(String[] args) {
BlockingQueue<Toast> dryQueue = new LinkedBlockingQueue<Toast>();
BlockingQueue<Toast> butterQueue = new LinkedBlockingQueue<Toast>();
BlockingQueue<Toast> jamQueue = new LinkedBlockingQueue<Toast>();
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new ToastMachine(dryQueue));
exec.execute(new ToastMachine(dryQueue));
exec.execute(new ButterMachine(dryQueue, butterQueue));
exec.execute(new JamMachine(butterQueue, jamQueue));
exec.shutdown();
try {
TimeUnit.MILLISECONDS.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
exec.shutdownNow();
try {
System.out.println("===================");
while(!dryQueue.isEmpty()) {
System.out.println(dryQueue.take() + "尚未抹上黄油");
}
System.out.println("===================");
while(!butterQueue.isEmpty()) {
System.out.println(butterQueue.take() + "尚未抹上果酱");
}
System.out.println("===================");
while(!jamQueue.isEmpty()) {
System.out.println(jamQueue.take() + "全部完成");
}
System.out.println("===================");
} catch (Exception e) {
e.printStackTrace();
}
}
}
/** 吐司面包实体类 */
class Toast {
public static enum Status {
DRY,
BUTTERED,
JAMMED
}
// 制作吐司所处的当前状态
private Status status = Status.DRY;
// 吐司id
private final int id;
public Toast(int id) {
this.id = id;
}
public void dry() {
this.status = Status.DRY;
}
public void butter() {
this.status = Status.BUTTERED;
}
public void jam() {
this.status = Status.JAMMED;
}
public Status getStatus() {
return this.status;
}
@Override
public String toString() {
return "[ #" + id + " ] -> ";
}
}
/** 吐司制作机 */
class ToastMachine implements Runnable {
// 已完成队列
private BlockingQueue<Toast> finishQueue;
// 吐司制作标签的顺序
public static Integer order = 1;
public ToastMachine(BlockingQueue<Toast> finishQueue) {
this.finishQueue = finishQueue;
}
@Override
public void run() {
while(!Thread.interrupted()) {
try {
Toast toast = null;
synchronized (ToastMachine.order) {
toast = new Toast(ToastMachine.order++);
}
toast.dry();
finishQueue.put(toast);
System.out.println(toast.toString() + "制作完成");
Thread.sleep(1000);
} catch(InterruptedException e) {
System.out.println("制作机已停止工作");
break;
// e.printStackTrace();
}
}
}
}
/** 自动抹黄油机 */
class ButterMachine implements Runnable {
// 未完成队列
private BlockingQueue<Toast> noFinishQueue;
// 已完成队列
private BlockingQueue<Toast> finishQueue;
public ButterMachine(BlockingQueue<Toast> noFinishQueue, BlockingQueue<Toast> finishQueue) {
this.noFinishQueue = noFinishQueue;
this.finishQueue = finishQueue;
}
@Override
public void run() {
while(!Thread.interrupted()) {
try {
if(!noFinishQueue.isEmpty()) {
Toast toast = noFinishQueue.take();
toast.butter();
finishQueue.put(toast);
System.out.println(toast.toString() + "抹上黄油");
}
Thread.sleep(500);
} catch(InterruptedException e) {
System.out.println("黄油机已停止工作");
// e.printStackTrace();
break;
}
}
}
}
/** 自动果酱机 */
class JamMachine implements Runnable {
// 未完成队列
private BlockingQueue<Toast> noFinishQueue;
// 已完成队列
private BlockingQueue<Toast> finishQueue;
public JamMachine(BlockingQueue<Toast> noFinishQueue, BlockingQueue<Toast> finishQueue) {
this.noFinishQueue = noFinishQueue;
this.finishQueue = finishQueue;
}
@Override
public void run() {
while(!Thread.interrupted()) {
try {
if(!noFinishQueue.isEmpty()) {
Toast toast = noFinishQueue.take();
toast.jam();
finishQueue.put(toast);
System.out.println(toast.toString() + "抹上果酱");
}
Thread.sleep(500);
} catch(InterruptedException e) {
System.out.println("果酱机已停止工作");
// e.printStackTrace();
break;
}
}
}
}
管道
使用管道进行输入输出,将任务向管道中写(PipedWriter
类),将任务从管道中读(PipedReader
类)
死锁
- 定义: 某个任务在等待另一个任务,而后者又等待别的任务,这样一直下去,直到这个链条上的任务又在等待第一个任务释放锁。这得到了一个任务之间相互等待的连续循环,没有哪个线程能继续。
- 产生条件(四个同时满足)
- 互斥条件,任务使用的资源中至少有一个是不能共享的。
- 至少有一个任务它必须只有一个资源且正在等待获取一个当前被别的任务持有的资源。
- 资源不能被任务抢占,任务必须把资源释放当做普通事件。
- 必须有循环等待。
新类库中的构件
1. CountDownLatch
- 它被用来同步一个或多个任务,强制他们等待由其他任务执行的一组操作完成。向
CountDownLatch
对象设置一个初始计数值,任何在这个对象上调用await()
都将阻塞,直到这个计数值到达0。其他任务在结束其工作时,可以在该对象上调用countDown()
来减小这个计数值。CountDownLatch
被设计为只能触发一次,计数值不能被重置。 示例
public static void TestCountDownLatch() { int n = 10000; int[] nums = new int[n]; final CountDownLatch counter = new CountDownLatch(10); final int max = n/10; for(int i=0; i<10; i++) { final int start = i*max; new Thread() { @Override public void run() { for(int i=0; i<max; i++) { nums[start+i] = i+start; } counter.countDown(); } }.start(); } try { counter.await(); System.out.println("All time is ok!"); } catch (InterruptedException e) { e.printStackTrace(); } }
2. CyclicBarrier
循环栅栏
- 创建一组任务,它们并行执行工作,然后在下一个步骤之前等待,直至所有的任务都完成。与
CountDownLatch
的区别为可以多次重用。 示例
final CyclicBarrier barrier = new CyclicBarrier(5, new Runnable(){ @Override public void run() { System.err.println("all tasks are over"); } }); for(int i=0; i<5; i++) { new Thread() { @Override public void run() { System.out.println("one of task is over"); try { barrier.await(); } catch(InterruptedException|BrokenBarrierException e) { e.printStackTrace(); } } }.start();; }
3. DelayQueue
延时队列
- 这是一个无界的
BlockingQueue
,用于放置实现了Delayed
接口的对象,其中的对象只能在其到期时才能从队列中取走。这种队列是有序的,即队头对象的延迟到期时间最长。如果没有任何延迟到期,那么就不会有任何头元素,并且poll()
将返回null
(正因为这样,你不能将null
放置到这种队列中)。 示例
/** 延时任务 */ class DelayTask implements Delayed, Runnable { private final String name; private final long endTime; private CountDownLatch latch; public DelayTask(String name, int endTime, CountDownLatch latch) { this.name = name; this.endTime = System.currentTimeMillis()+endTime*1000; this.latch = latch; System.out.println(this.name + " start running "); } @Override public void run() { System.out.println(this.name + " end running "); latch.countDown(); } @Override public long getDelay(TimeUnit unit) { // 当第一个参数缩减为0时,即触发 return unit.convert(endTime-System.currentTimeMillis(), TimeUnit.MILLISECONDS); } @Override public int compareTo(Delayed o) { if(o == null || this.getClass() != o.getClass()) return 1; if(this == o) return 0; return (int)(this.endTime - ((DelayTask)o).endTime); } } /** 测试方法 */ public static void testDelayQueue() { Random rand = new Random(); int size = 10; DelayQueue<DelayTask> q = new DelayQueue<DelayTask>(); CountDownLatch latch = new CountDownLatch(size); for(int i=0; i<size; i++) { q.put(new DelayTask("thread#"+i, rand.nextInt(10), latch)); } // 监控线程 Thread thread = new Thread() { public void run() { try { while(!Thread.interrupted()) { System.out.println("get one "); q.take().run(); } } catch (InterruptedException e) { e.printStackTrace(); } } }; // 设置为后台守护线程,主线程结束时,也自动结束 thread.setDaemon(true); thread.start(); try { latch.await(); System.out.println("game over"); } catch (InterruptedException e) { e.printStackTrace(); } }
4. PriorityBlockingQueue
优先级同步队列
- 这是一个很基础的优先级队列,它具有可阻塞的读取操作。其中在优先级队列中的对象是按照优先级顺序从队列中出现的任务。
示例
/** 带有优先级的任务 */ class PriorityTask implements Runnable, Comparable<PriorityTask> { /** 任务优先级 */ private int priority; private String name; public PriorityTask(String name, int priority) { this.name = name; this.priority = priority; System.out.println(this.toString()); } @Override public void run() { System.out.println("hello " + toString()); try { Thread.sleep((int)(Math.random()*1000)); } catch(InterruptedException e) { e.printStackTrace(); } } @Override public int compareTo(PriorityTask o) { if(o == null) return 1; if(this == o) return 0; return o.priority - this.priority; } @Override public String toString() { return name + ", $" + priority; } } // 测试优先级同步队列 public static void testPriority() { // 从小到大排序 PriorityBlockingQueue<PriorityTask> queue = new PriorityBlockingQueue<PriorityTask>(); for(int i=0; i<10; i++) { queue.put(new PriorityTask("task#" + i, (int)(Math.random()*10))); // System.out.println(queue.peek().toString()); } PriorityTask tmpPriorityTask = null; while(!queue.isEmpty()) { tmpPriorityTask = queue.poll(); tmpPriorityTask.run(); } }
5. ScheduledExecutor
ScheduleThreadPoolExecutor
有一些方法,schedul()
使任务运行一次,或者scheduleAtFixedRate()
使任务在每隔规定的时间重复执行任务。public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
6. Semaphore
- 正常的锁在任何时刻都只允许一个任务访问一项资源,而计数信号量允许n个任务同时访问这个资源。
7. Exchanger
Exchanger
是在两个任务之间交换对象的栅栏。当这些任务进入栅栏时,它们各自拥有一个对象,当它们离开时,它们都拥有之前由对象持有的对象。Exchanger
的典型应用场景 是:一个任务在创建对象,这些对象的生产代价很高昂,而另一个任务在消费这些对象。通过这种方式,可以有更多的对象在创建的同时被消费。
性能调优
比较synchronized
、Lock
和Atomic
三种互斥技术的性能
- JDK文档说明:当对一个对象的临界值更新被限制为只涉及单个变量时,只有使用
Atomic
对象这种方式才能工作。 - 使用
Lock
通常会比使用synchronized
要高效许多,而且synchronized
的开销范围看起来变化范围太大,而Lock
相对比较一致。 - 什么时候使用
synchronized
关键字呢?
- 互斥方法的方法体是非常之小的。(好习惯:只互斥那些你绝对必须互斥的部分)
synchronized
关键字锁产生的代码与Lock
所需的加锁-try/finally-解锁
惯用法所产生的代码相比,可读性提高了很多。因此在编程时,以synchronized
关键字入手,只有在性能调优时才替换为Lock
对象这种做法。
- 什么时候使用
Atomic
呢?
- 你只有一个要被修改的
Atomic
对象,并且这个对象独立于其他所有的对象 - 安全的做法:以更加传统的互斥方式入手,只有在性能方面的需求能够明确指示时,再替换为
Atomic
。
- 你只有一个要被修改的
ReadWriteLock
- 对向数据结构相对不频繁地写入,但是有多个任务要经常读取这个数据结构的这类情况进行了优化。
- 特点
- 可以同时又多个读取者
- 当写锁被其他任务持有时,任何读者不能访问,等待写锁释放。
- 能否提高程序的性能是完全不确定的,取决于如下因素:
- 数据被读取的频率与被修改的频率相比较的结果
- 读取和写入操作的时间(锁将更加复杂,因此短操作并不能带来好处)
- 有多少线程竞争
- 是否在多处理机器上运行等因素
- 效果需要通过实验来证明。
多进程的主要缺陷
- 等待共享资源的时候性能降低
- 需要处理线程的额外CPU花费
- 糟糕的程序设计导致不必要的复杂度
- 有可能产生一些病态行为,如饿死、竞争、死锁和活锁(多个运行各自任务的线程使得整体无法完成)
- 不同平台导致的不一致性