并发
博客专区 > kukudeku 的博客 > 博客详情
并发
kukudeku 发表于8个月前
并发
  • 发表于 8个月前
  • 阅读 21
  • 收藏 3
  • 点赞 0
  • 评论 0

腾讯云 新注册用户 域名抢购1元起>>>   

摘要: 并发编程的重要性毋庸置疑,因此这个周末,又看了一遍JavaSE 并发相关内容,记录一下心得

一、并发的多面性

用并发解决的问题大体上可以分为“速度”和“设计可管理性”两种。

1.1 更快的执行

如果有一台多处理器的机器,那么就可以在这些处理器之间发布多个任务,从而可以极大地提高吞吐量。这是使用强有力的多处理器Web服务器的常见情况,在为每个请求分配一个线程程序中,它可以将大量的用户请求分布到多个CPU上。

但是,并发通常是提高运行在单个处理器上的程序的性能。如果程序中的某个任务因为某个原因不能继续执行,那么我们说这个任务或者线程阻塞了。如果没有并发,则整个任务都将停止下来,直至外部条件发生变化。但是如果是使用并发程序,那么当一个任务阻塞时,程序中的其他任务还可以继续执行,因此整个程序可以保持继续向前执行。事实上,从性能来看,如果没有任务会阻塞,那么在单处理器机器上使用并发就没有任何意义。

1.2 改进代码设计

Java的线程机制是抢占式的,这表示调度机制会周期性地中断线程,将上下文切换到另一个任务,从而为每个线程都提供时间片,使得每个线程都会分配到数量合理的时间去驱动它的任务。

并发需要付出代价,包括复杂性代价,通常线程使你能够创建更加松散耦合的设计,否则,你的代码中各个部分都必须显示地关注哪些通常可以由线程来处理的任务。

二、线程的基本机制

并发编程使我们可以将程序划分为多个分离的、独立运行的任务。一个线程就是在进程中的一个单一的顺序控制流。

2.1 定义任务

  • 实现Runnable接口
  • 继承Thread类
  • 使用Executor。JavaSE5的java.util.concurrent包中的执行器Executor将为你管理Thread对象,从而简化了并发编程。常用的ExecutorService(具有生命周期的Executor)知道如何构建恰当的上下文来执行Runnable对象。使用示例:ExecutorService exec = Executors.newCacheThreadPool();执行完毕之后关闭。exec.shutdown();对shutdown()方法的调用可以防止新任务被提交给这个Executor,当前线程将继续运行在shutdown()被调用之前的提交的所有任务。
  • 实现Callable接口。Runnable是执行工作的独立任务,但是它不返回任何值。如果希望任务在完成时能够返回一个值,那么可以实现Callable接口。它的类型参数表示的是从方法call()中返回值,返回一个Future对象,并且必须使用ExecutorService.submit()方法调用它。

2.2 休眠

影响任务行为的一种简单方法是调用sleep(),这将使任务中止执行给定的时间。

2.3 优先级

线程的优先级将该线程的重要性传递给调度器。尽管CPU处理现有线程集的顺序是不确定的,但是调度器将倾向于让优先级最高的线程先执行。然而这并不是意味着优先级较低的线程将得不到执行,优先级较低的线程仅仅是执行的频率较低。在绝大多数时间里,所有线程都应该以默认的优先级运行。视图操纵线程优先级通常是一种错误。

2.4 让步

如果完成了在run()方法的循环的一次迭代过程中需要的工作,就可以给线程调度机制一个暗示。这个暗示将通过调用yield()方法来作出。当调用yield()时,你也只是在建议具有相同优先级的其他线程可以运行。

2.5 后台线程

后台线程是指在程序运行的时候在后台提供一种服务的线程,并且这种线程并不属于程序中不可或缺的部分。在线程启动前设置setDaemon(true);就可将该线程设置成后台线程。

2.6 加入一个线程

一个线程上可以在其他线程上调用join()方法,其效果是等待一段时间直到第二个线程结束才继续执行。如果某个线程在另一个线程t上调用t.join(),此线程将被挂起,直到目标线程t结束才恢复。

三、共享受限资源

3.1 解决共享资源竞争

3.1.1 synchronized

Java提供关键字synchronized,为防止资源冲突提供了内置支持。当任务要执行被synchronized关键字保护的代码片段时,它将检查锁是否可用,然后获取锁,执行代码,释放锁。

共享资源一般是以对象形式的内存片段,但也可以是文件、IO流或者是打印机。对于某个对象来说,其所有的synchronized方法共享一个锁,这可以被用来防止多任务同时访问被编码为对象内存。一个任务可以多次获得对象锁。如果一个方法在同一个对象行调用了第二个方法,后者由调用了同一个对象上的另一个方法,就会发生这种情况。JVM负责跟踪对象被加锁的次数。如果一个对象呗解锁(即锁被完全释放),其计数变为0.在任务第一次给对象加锁的时候,计数变为1.每当这个相同的任务在这个对象上获得锁时,计数都会递增。

3.1.2 使用显示的Lock对象

Java SE5的java.util.concurrent类库包含了显式的互斥机制。Lock对象必须被显式地创建、锁定和释放。因此,它与内建的锁形式相比,代码缺乏优雅性。但是,对于解决某些类型的问题来说,它更加灵活。Lock示例:private Lock lock = new ReentrantLock();//获得锁:lock.lock();//释放锁lock.unlock();

当你使用Lock对象时,你必须防止放置在finally子句中带有unlock()的try-finally语句中。注意,return语句必须在try子句中出现,以确保unlock()不会过早发生,从而将数据暴露。

尽管try-finally所需的代码比synchronized关键字要多,但这也代表了显式的Lock对象的优点之一。如果在使用synchronized关键字时,某些事物失败了,那么就会抛出异常。但是你没有机会去做任何清理工作,以维护系统使其处于良好状态。有了显式的Lock对象,你就可以使用finally子句将系统维护在正确的状态了。

大体上,当你使用synchronized关键字时,需要写的代码量更少,并且用户错误出现的可能性也会降低,因此通常只有在解决特殊问题时,才使用显式的Lock对象。例如,用synchronized关键字不能尝试获得锁且最终获取锁会失败,或者尝试着获取锁一段时间,然后放弃它,这个时候要实现这些,你必须使用concurrent类库。

3.1.3 原子性atomic

原子操作是不能被线程调度机制中断的操作,一旦操作开始,那么它一定可以在可能发生的“上下文切换”之前执行完毕。

原子性可以应用于除了long和double之外的所有的基本类型之上的简单操作。JVM可以将64位(long和double变量)的读取和写入当做两个分离的32位操作来执行,这就产生了一个读取和写入操作中间发生上下文切换,从而导致不同的任务可以看到不正确结果的可能性。但是,当你定义的long或double变量时,如果使用volatile关键字修饰,就会获得原子性。

因此原子操作可由线程来保证其不可中断,这些代码不需要被同步,但是即使是这样,它也是一种过于简化的机制。有时甚至看起来应该安全的原子操作,实际上也可能不安全。

3.1.4 原子类

JavaSE5 引入了注入AtomicInteger、AtomicLong、AtomicReference等特殊的原子性变量类,他们提供下面形式的原子性条件更新操作:

boolean compareAndSet(expectedValue,updateValue);

这些类被调整为可以使用在某些现代处理器上的可获得的,并且是在机器级别上的原子性,因此在使用它们时,通常不需要担心。在涉及性能调优时,他们就有了用武之地。可以立时Atomic原子类来消除synchronized关键字。

3.1.5 临界区

四、线程之间的协作

当你使用线程来同时运行多个任务时,可以通过使用锁(互斥)来同步两个任务的行为,从而使得一个任务不会干涉到另一个任务的资源。现在了解下多线程之间如何进行协作的。

当任务协作时,关键问题是这些任务之间的握手。为了实现这种握手,我们使用了相同的基础特性:互斥。在这种情况下,互斥能够确保只有一个任务可以响应某个信号,这样就可以根除任何可能的竞争条件。在互斥之上,我们可以将其自身挂起,直到某些外部条件发生变化。这种握手可以通过Object的wait()和notify()/notifyAll()来安全地实现。JavaSE5的并发类还提供了具有await()和singal()/singalAll()方法的Condition对象,以及BlockingQueue的阻塞队列。在引入BlockingQueue之前,可以利用管道输入输阻塞队列来实现。

4.1 wait()和notify()/notifyAll()

wait()--忙等待。wait()会在等待外部条件发生变化而将任务挂起,并且只有在notify()和notifyAll()发生时,才会被唤醒。调用sleep()的时候锁并没有释放,调用yield()也属于这种情况。当一个任务在方法里遇到了对wait()的调用时,线程的执行被挂起,对象上的锁被释放。notifyAll()并不能唤醒任何地方的等待的线程,当特定所调用notifyAll()时,只能唤醒该锁的线程。

应该强调的是,wait()和notify()/notifyAll()是Object 的方法。

4.2 使用显示的Lock和Condition对象

JavaSE5中,“juc”包下使用互斥的并允许任务挂起的基本类是Condition,你可以通过在Condition上调用await()来挂起一个任务。当外部条件发生变化时,意味着某个任务应该继续执行,则可以调用signal()来通知这个任务,从而唤醒一个任务,或者调用signalAll()来唤醒所有在这个Condition上被其自身挂起的任务。使用示例:

Class Test{
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void test(){
lock.lock();
try{
contition.signalAll();
}finally{
lock.unlock();
}
}
public void waitForWaxing() throws InterruptedExcepiton{
lock.lock();
try{
condition.await();
}finally{
lock.unlock();
}
}
}

4.3 同步队列BlockingQueue

wait()和notify()/notifyAll()方法以一种非常低级的方式解决了任务互斥操作问题,即每次交互时都要握手。在许多情况下,你可以瞄向更高的抽象级别,使用同步队列来解决任务协作问题,同步队列在任何时刻都只允许一个任务插入或移除元素。在java.util.concurrent.BlockingQueue接口中提供了这个队列,这个队列有大量的标准实现。你通常可以使用LinkedBlockingQueue,他是一个无界队列,还可以使用ArrayBlockingQueue,它有固定的尺寸,因此你可以在它被阻塞之前,向其中放置有限数量的元素。

如果消费者试图从队列中获取对象,而该队列此时为空,那么这些队列任务可以挂起任务,并且当有更多元素可用时回复消费者任务。阻塞队列可以解决大量的问题,并且比wait()和notifyALL()相比,更简单可靠的多。示例如下:

package org.test.ch21;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.SynchronousQueue;

class LiftOff implements Runnable {
	protected int countDown =10;
	private static int taskCount = 0;
	private final int id = taskCount++;
	
	public LiftOff() {
		super();
	}
	
	public LiftOff(int countDown) {
		super();
		this.countDown = countDown;
	}

	public String status(){
		return "#"+id+"("+(countDown > 0 ? countDown:"LiftOff")+").";
	}
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(countDown -- > 0){
			System.out.print(status()+" ");
			Thread.yield();
		}
	}
}
	
class LiftOffRunner implements Runnable {
	private BlockingQueue<LiftOff> rockets;
	
	public LiftOffRunner(BlockingQueue<LiftOff> rockets) {
		super();
		this.rockets = rockets;
	}

	public void add(LiftOff lo){
		try{
			rockets.put(lo);
		}catch(InterruptedException e){
			System.out.print("Interrupted during put()");
		}
	}
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		try {
			while (!Thread.interrupted()){
				LiftOff rocket = rockets.take();
				rocket.run();//use this thread;
			}
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			System.out.println("Waking from take()");
		}
		System.out.print("Exiting LiftOffRunner");
	}
}
public class TestBlockingQueue{
	static void getKey(){
		try {
			System.out.println("输入任意内容,解除阻塞:");
			new BufferedReader(new InputStreamReader(System.in)).readLine();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			throw new RuntimeException(e);
		}
	}
	static void getKey(String message){
		System.out.println(message+" ");
		getKey();
	}
	static void test(String msg,BlockingQueue<LiftOff> queue){
		System.out.println(msg);
		LiftOffRunner runner = new LiftOffRunner(queue);
		Thread t = new Thread(runner);
		t.start();
		for(int i=0;i<5;i++){
			runner.add(new LiftOff(5));
		}
		getKey("Press 'Enter' ("+msg+")"+" ");
		t.interrupt();
		System.out.println("Finished "+msg+" test ");
	}
	public static void main(String[] args) {
		test("LinkedBlockingQueue", new LinkedBlockingQueue<LiftOff>());
		test("ArrayBlockingQueue",new ArrayBlockingQueue<LiftOff>(3));
		test("SynchronousQueue",new SynchronousQueue<LiftOff>());
		
	}
}

运行结果:

LinkedBlockingQueue
Press 'Enter' (LinkedBlockingQueue)  
输入任意内容,解除阻塞:
#0(4). #0(3). #0(2). #0(1). #0(LiftOff). #1(4). #1(3). #1(2). #1(1). #1(LiftOff). #2(4). #2(3). #2(2). #2(1). #2(LiftOff). #3(4). #3(3). #3(2). #3(1). #3(LiftOff). #4(4). #4(3). #4(2). #4(1). #4(LiftOff). eee
Finished LinkedBlockingQueue test 
ArrayBlockingQueue
Waking from take()
Exiting LiftOffRunner#5(4). #5(3). #5(2). #5(1). #5(LiftOff). #6(4). #6(3). #6(2). #6(1). #6(LiftOff). #7(4). #7(3). #7(2). #7(1). #7(LiftOff). Press 'Enter' (ArrayBlockingQueue)  
输入任意内容,解除阻塞:
#8(4). #8(3). #8(2). #8(1). #8(LiftOff). #9(4). #9(3). #9(2). #9(1). #9(LiftOff). 000
Finished ArrayBlockingQueue test 
SynchronousQueue
Waking from take()
Exiting LiftOffRunner#10(4). #10(3). #10(2). #10(1). #10(LiftOff). #11(4). #11(3). #11(2). #11(1). #11(LiftOff). #12(4). #12(3). #12(2). #12(1). #12(LiftOff). #13(4). #13(3). #13(2). #13(1). #13(LiftOff). #14(4). #14(3). #14(2). #14(1). #14(LiftOff). Press 'Enter' (SynchronousQueue)  
输入任意内容,解除阻塞:
888
Finished SynchronousQueue test 
Waking from take()
Exiting LiftOffRunner

4.4 任务间使用管道进行输入/输出

通过输入/输出在线程间进行通信通常很有用。在Java中对应的类是PipedWriter类(允许任务向管道写)和PipedReader类(允许不同任务从同一管道中读)。这个模型可以看做是“生产者-消费者”问题的变体。管道基本上是一个阻塞队列,存在于多个引入BlockingQueue之前的Java 版本中。详细内容可查看博文:https://my.oschina.net/itblog/blog/515822

五、死锁

一个对象可以有synchronized方法或者其他形式的加锁机制来防止别的任务在互斥还没有释放的时候就访问这个对象。但是当某个任务在等待另一个任务,而后者又等待别的任务,这样一直下去,直到这个链条上的任务又在等待第一个任务释放锁,这就得到了一个任务之间相互等待的连续循环,没有哪个线程能够继续。这称之为死锁。

一个死锁的经典案例就是“哲学家就餐”问题。

六、新类库中的构件

6.1 CountDownLatch

他被用来同步一个或多个任务,强制他们等待由其他任务执行的一组操作完成。

可以向CountDownLatch对象设置一个初始计数值,任何在这个对象上调用wait()的方法都将阻塞,知道这个计数值到达0。其他任务在结束工作时,可以在该对象上调用countDown()来减小这个计数值。CountDownLatch被设计为只触发一次,计数值不能被重置。如果需要重置计数值,可以使用CyclicBarrier。

调用countDown()的任务在产生这个调用时并没有被阻塞,只有对await()的调用才会被阻塞,知道计数值到达0.

CountDownLatch的典型用法是将一个程序分为n个相互独立的可解决任务,并创建值为0的CountDownLatch。当这个任务完成时,都会在这个锁存器上调用countDown()。等待问题被解决的任务在这个锁存器上调用await(),将它们自己拦住,知道锁存器计数结束。下面是演示这个技术 的一种示例:

package org.test.ch21;

import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

class TaskPortion implements Runnable{
	private static int counter = 0;
	private final int id = counter++;
	private static Random rand = new Random(47);
	private final CountDownLatch latch;
	
	public TaskPortion(CountDownLatch latch) {
		super();
		this.latch = latch;
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		try {
			doWork();
			latch.countDown();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	private void doWork() throws InterruptedException {
		// TODO Auto-generated method stub
		TimeUnit.MILLISECONDS.sleep(rand.nextInt(2000));
		System.out.println(this+"comleted");
	}

	@Override
	public String toString() {
		return String.format("%1$-3d", id);
	}
}
class WatiingTask implements Runnable{
	private static int counter = 0;
	private final int id = counter++;
	private final CountDownLatch latch;
	
	public WatiingTask(CountDownLatch latch) {
		super();
		this.latch = latch;
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		try {
			latch.await();
			System.out.println("Latch barrier passed for "+this);
		} catch (InterruptedException e) {
			System.out.println(this+" interruped");
		}
	}

	@Override
	public String toString() {
		return String.format("WatiingTask %1$-3d" , id);
	}
	
	
}
public class TestCountDownLatch {
	static final int SIZE = 10;
	public static void main(String[] args) {
		ExecutorService exec = Executors.newCachedThreadPool();
		CountDownLatch latch = new CountDownLatch(SIZE);
		for(int i=0;i<10;i++){
			exec.execute(new WatiingTask(latch));
		}
		for(int i=0;i<SIZE;i++){
			exec.execute(new TaskPortion(latch));
		}
		System.out.println("Launched all tasks");
		exec.shutdown();
	}
}

运行结果如下:

Launched all tasks
9  comleted
7  comleted
5  comleted
8  comleted
1  comleted
2  comleted
6  comleted
4  comleted
0  comleted
3  comleted
Latch barrier passed for WatiingTask 1  
Latch barrier passed for WatiingTask 0  
Latch barrier passed for WatiingTask 3  
Latch barrier passed for WatiingTask 2  
Latch barrier passed for WatiingTask 4  
Latch barrier passed for WatiingTask 5  
Latch barrier passed for WatiingTask 7  
Latch barrier passed for WatiingTask 8  
Latch barrier passed for WatiingTask 6  
Latch barrier passed for WatiingTask 9  

6.2 CyclicBarrier

CyclicBarrier用法与CountDownLatch很像,只是CyclicBarrier是能够复用的。

6.3 DelayQueue

这是一个无界的BlockingQueue,用于放置实现了Delayed接口的对象,其中的对象只能在其中到期才能从队列中区中。这种队列是有序的,即对头对象的延迟到期时间最长。如果没有任何延迟到期,那么就不会有任何头元素,并且poll()将返回null。这是因为如此,不能null元素放到这种队列中。

6.4 PriorityBlockingQueue

这是一个很基础的优先级队列,它具有可阻塞的读取操作。(我没用过,不太了解,也不常用)

6.5 JUC框架

请参照http://www.cnblogs.com/skywang12345/p/3498454.html

共有 人打赏支持
粉丝 120
博文 37
码字总数 94902
×
kukudeku
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: