{Effective Java} Chap 10 Concurrency


It is inherent in much of what we do, and a requirement if you are to obtain good performance from multicore processors, which are now commonplace. This chapter contains advice to help you write clear, correct and well-documented cocurrent programs.

java.util.concurrent: Executor, Concurrent collection and Synchronizers.


Item 66: synchronize access to shared mutable data 

Synchronization is required for reliable communication between threads as well as for mutual exclusion.

Don't use Thread.stop, just use boolean flag to end a thread naturally.

//Properly synchronized cooperative thread termination
public class StopThead{
	private static boolean stopRequested;
	private static synchronized void requestStop() {
		stopRequested = true;
	}
	private static synchronized boolean stopRequested() {
		return stopRequested;
	}
	
	public static void main(String[] args){
		Thread backgroundThread = new Thread(new Runnable() {
			
			@Override
			public void run() {
				int i = 0;
				while(!stopRequested)
					i ++;
			}
		});
		
		backgroundThread.start();
		TimeUnit.SECONDS.sleep(1);
		requestStop();
	}
	
}

Synchronization has no effect unless both read and write operations are synchronized.

The synchronization on these methods is used solely for communication effects, not for mutual exclusion.

Volatile: performs no mutual exclusion, it guarentees that any thread that reads the field will see the most recently written value.


//Cooperative thread termination with a volatile field
public class StopThead{
	private static volatile boolean stopRequested;
	
	public static void main(String[] args){
		Thread backgroundThread = new Thread(new Runnable() {
			
			@Override
			public void run() {
				int i = 0;
				while(!stopRequested)
					i ++;
			}
		});
		
		backgroundThread.start();
		TimeUnit.SECONDS.sleep(1);
		stopRequested = true;
	}	
}

++ is not atomic.

Use AtomicLong which is part of java.util.concurrent.atomic.


Best: confine mutable data to a single thread, don't share mutable data.


Effectively immutable: one thread modify a data object for a while and then synchronize the act of sharing the object reference.

Safe publication:

store the object reference in a static field as part of class initialization; store it in a volatile field or a field with normal locking; put it into a concurrent collection.


In summary, when multiple threads share mutable data, each thread that reads or writes the data must perform synchronization. If not, there is no guarantee that one thread's changes will be visible to another. The penalties are liveness and safety failures, which are the most difficult to debug. They can be intermittent and timing-dependent, and program behavior can vary radically from one VM to another. If you need only inter-thread communication, and not mutual exclusion, use volatile is a choice but still tricky.




Item 67: avoid excessive synchronization

To avoid liveness (the program fails to make progress) and safety (the program computes the wrong answers) failures, never cede(放弃,割让) control to the client within a synchronized method or block.

Inside a synchronized region, dont invoke a method that is designed to be overriden or one provided by a client in the form of a function object, which might cause data corruption, deadlocks (GUI toolkits).


There is a better way to move the alien method invocations out of the synchronized block. Since 1.5, we can use a concurrent collection known as CopyOnWriteArrayList. Its internal array is never modified, iteration requires no blocking and is very fast. It's perfect for observer lists, which are rarely modified and often traversed.


An alien method invoked outside of a synchronized region is known as an open call, which can greatly increase concurrency.


As a rule, you should so as little work as possible inside synchronized regions. Obtain the lock, examine the shared data, transform it as necessary and drop the lock. 


Oversynchronization:  loss if parallelism and limit the VM's ability to optimize code execution


Make a mutable class thread-safe if it's intended for concurrent use. Don't synchronize internally if you can't achieve significantly higher concurrency. When in doubt, do not synchronize class, but document that it's not thread-safe.


If use internally synchronization, can use lock splitting, lock striping and nonblocking concurrency control.

If a method modifies a static field, must synchronize access to it even if the method is used only by a single thread.


In summary, to avoid deadlock and data corruption, never call an alien method from within a synchronized region. Synchronize class internally only if there is a good reason to do so, and document the decision clearly. 



Item 68: prefer executors and tasks to threads

Executor Framework is a flexible interface-based task execution facility, which helps you wait for a task to complete, wait for the executor service's graceful termination to complete.

If we want more than one thread to process requests from the queue. call a static factory called a thread pool. 

ThreadPoolExecutor lets you control nearly every aspect of a thread pool's operation.

How to choose:

1. small program or a lightly loaded server --- Executors.newCachedThreadPool --- demands no configuration, submitted tasks are not queued but handed off to a thread for execution; if no threads are avaliable, a new one is created.

2. heavily loaded production server --- Executors.newFixedThreadPool --- give a fixed number of threads


Don't write own work queues, don't work directly with threads. The key abstract now is task (Runnable and Callable which returns a value). Use executor for execution and collections for aggregation.


ScheduledThreadPoolExecutor: more flexible, support multiple threads and recovers gracefully from tasks that throw exceptions. But Timer uses only a thread for task execution and will cease if throws uncaught exception.


 


Item 69: prefer concurrency utilities to wait and notify

Given the difficulty of using wait and notify correctly, you should use the higher-level concurrency utilities instead.

Concurrent collections provide high-performance concurrent implementations of collection interfaces. It is impossible to exclude concurrent activity from a concurrent collection; locking it will have no effect but to slow the program. The client can't atomically compose method invocations on concurrent collections.

ConcurrentHashMap is very fast and offering excellent concurrency. Use ConcurrentHashMap in preference to Collections.synchronizedMap or HashTable.

State-dependent modify operations: putIfAbsent(key, value) for ConcurrentMap.

Blocking operations: BlockingQueue use take functions, can be used for worker queues (producer-consumer queues). Most ExecutorService such as ThreadPoolExecutor use BlockingQueue.


Synchronizers: enable threads to wait for one another.

CountDownLatch: single-use barriers that allow one or more threads to wait for other threads to do sth. The constructor takes an int that is the number of times the countDown method must be invoked on the latch (门栓,闭锁) before all waiting threads are allowed to proceed. 


E.g

// Simple framework for timing concurrent execution
	public static long time(Executor executor, int concurrency,
			final Runnable action) throws InterruptedException {
		final CountDownLatch ready = new CountDownLatch(concurrency);
		final CountDownLatch start = new CountDownLatch(1);
		final CountDownLatch done = new CountDownLatch(concurrency);

		for (int i = 0; i < concurrency; i++) {
			executor.execute(new Runnable() {

				@Override
				public void run() {
					ready.countDown(); // Tell timer we're ready
					try {
						start.await(); // Wait till peers are ready
						action.run();
					} catch (InterruptedException e) {
						Thread.currentThread().interrupt();
					} finally {
						done.countDown(); // Tell timer we're done
					}
				}
			});

			ready.await(); // Wait for all workers to be ready
			long startNanos = System.nanoTime();
			start.countDown(); // And they're off!
			done.await(); // Wait for all workers to finish
			return System.nanoTime() - startNanos;
		}
	}



For interval timing, always use System.nanoTime (more accurate and precise, not affected by adjustments to the system's real-time clock) in preference to System.currentTimeMills.


Always use the wait loop idiom to invoke the wait method; never invoke it outside of a loop. The loop serves to test the condition before and after waiting.


//The standard idiom for using the wait method
	synchronized (obj){
		while(< condition does not hold>)
			obj。wait();//(Release lock and reacquires on wakeup)
		... // Perform action appropriately to condition 
	}


Several reasons a thread might wake up when the condition does not hold:

1. another thread could have obtained the lock and changed the guarded state between the time a thread invoked notify and the time the waiting thread woke.

2. another thread could have invoked notify accidentally or maliciously when the condition does not hold. Classes expose themselves to this sort of mischief by waiting on publicly accessible objects. Any wait contained in a synchronized method of a publicly accessible object is susceptible to this problem.

3. the notifying thread could be overly generous in waking waiting threads. For example, the notifying thread might invoke notifyAll even if only some of the waiting threads have their condition satisfied.

4. the waiting thread could (rarely) wake up in the absence of a notify. This is known as a spurious wakeup.


It is reasonable to always use notifyAll. But for optimization, you may choose notify.


In summary, there is seldom if ever a reason to use wait and notify in new code. If you maintain code that use wait and notify, make sure it always invokes wait from within a while loop using the standard idiom. The notifyAll should generally be used in preference to notify.



Item 70: document thread safety

Javadoc doesnt include the synchronized modifier in its output. The presence of the synchronized modifier in a method declaration is an implementation detail, not a part of its exported API.

To enable safe concurrent use, a class must clearly document what level of thread safety it supports.

1. immutable --- instances of this class appear constant. No external synchronization is necessary. E.g String, Long, and BigInteger.

2. unconditionally thread-safe --- instances of this class are mutable, but the class has sufficient internal synchronization that its instances can be used concurrently without the need for any external synchronization. E.g Random and ConcurrentHashMap.

3. conditionally thread-safe --- like unconditionally one, except that some methods require external synchronization for safe concurrent use. E.g the collections returned by Collections.synchronized wrappers whose iterators require external synchronizations.

4. not thread-safe --- instances of this class are mutable. To use them concurrently, client must surround each method invocation or sequence with external synchronization of the clients' choosing. E.g general-purpose collection, such as ArrayList and HashMap.

5. thread-hostile --- this class is not safe for concurrent use even if all method invocations are surrounded by external synchronization. Thread hostility usually results from modifying static data without synchronization. No one writes a thread-hostile class on purpose; such classes result from the failure to consider concurrency. Luckily, there are very few. E.g System.runFinalizerOnExit method but has been deprecated.


In summary, every class should clearly document its thread safety properties with a carefully worded prose description or a thread safety annotation. Conditionally thread-safe classes must document which method invocation sequences require external synchronization, and whick lock to acquire when executing these sequences. 

If you write an unconditionally thread-safe class, consider using a private lock object in place of synchronized methods. This protects you against synchronization interference by clients and subclasses and gives you the flexibility to adopt a more sophisticated approach to concurrency control in a later release.


Item 71: use lazy intitialization judiciously

Delaying the initialization of a field until its value is needed. Don't do it unless you need to. It decreases the cost of initializing a class or creating an instance, at the expense of increasing the cost of accessing the lazily initialized field.

If a field is accessed only on a fraction of the instances of a class and it is costly to initialize the field, then lazy initialization may be worthwhile.

Under most circumstances, normal initialization is preferable to lazy initialization.


If you use initialization to break an initialization circularity, use a synchronized accessor.


If you need to use lazy initialization for performance on a static field, use the lazy initialization holder class idiom. A modern VM will synchronize field access only to initialize the class. Once the class is initialized, the VM will patch the code so that subsequent access to the field does not involve any testing or synchronization.


If you need to use lazy initialization for performance on an instance field, use the double-check idiom, which avoids the cost of locking when accessing the field after it has been initialized. Once without locking, if not initialized, with locking and initialize.

E.g

	// Normal initialization of an instance field
	private final FieldType field = computeFiledValue();
	
	// The simplest and clearest way!! Lazy initialization of instance field - synchronized accessor
	private FiledType field;
	
	synchronized FieldType getField(){
		if(field == null)
			field = computeFiledValue();
		return field;
	}
	
	//Better than double-check!! Lazy initialization holder class idiom for static fields
	private static class FieldHolder{
		static final FieldType field = computeFieldValue();
	}
	
	// First call, initial field, latter just access field.
	static FieldType getField() {
		return FieldHolder.field; 
	}
	
	// Double-check idiom for Lazy initialization of instance fields
	private volatile FieldType field;
	FieldType getField(){
		FieldType result = field;
		if(result == null)	// First check without locking
			synchronized (this) {
				result = field;
				if(result == null) 	// Second check with locking
					field = result = computeFieldValue();
			}
		return result;
	}
	
	//Single-check idiom - can cause repeated initialization
	private volatile FieldType field;
	FieldType getField(){
		FieldType result = field;
		if(result == null) 	
			field = result = computeFieldValue();
			
		return result;
	}




In summary, you should initialize most fields normally, not lazily. If must use, for instance fields, it is the double-check idiom; for static fields, the lazy initialization holder class idiom; for instance fields that can tolerate repeated initialization, consider single-check idiom.



Item 72: don't depend on the thread scheduler

Any program that relies on the thread scheduler for correctness or performance is likely to be nonportable.

The best way to write a robust, responsive, portable program is to ensure that the average number of runnable threads (Threads that are waiting are not runnable) is not significantly greater than the number of processors.


Threads should not run if they are not doing useful work. Tasks should be reasonably small and independent.

Threads should not busy-wait.

Resist the temptation to fix the program by putting in calls to Thread.yield to satisfy hungry threads. Thread.yield has no testable semantics and may have different performance on different JVM. Better to reduce the number of concurrently runnable threads.

Thread.yield returns control to its caller. Use Thread.sleep(1) instead of yield.


Thread priorities are among the least portable features of the Java platform. 


In summary, don't depend on the thread scheduler for correctness. As a corollary, don't rely on yield or priorities. But priorities may be used to improve the quality of service that already worked, never use it to fix program that barely works.



Item 73: avoid thread groups

Thread groups don't provide any security functionality to speak of. 

They are obsolete.

To summarize, the functions of thread group is flawed. Just ignore it, and use thread pool executors.



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Anyanyamy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值