Java多线程并发最佳实践

JAVA实用技巧

2017-03-05

227

0

编写并发代码是比较难,尽管Java语言提供了许多同步和并发支持,但是最终写出没有Bug的Java并发代码还是需要依靠个人的勤奋与专业知识。Java多线程并发最佳实践是一组实践的好点子,有助于你快速开发出优质的并发代码。如果你是新手,需要熟悉一些基本概念,再来阅读本文会更有针对性。

1.使用本地变量

应该总是使用本地变量,而不是创建一个类或实例变量,通常情况下,开发人员使用对象实例作为变量可以节省内存并可以重用,因为他们认为每次在方法中创建本地变量会消耗很多内存。下面代码的execute()方法被多线程调用,为了实现一个新功能,你需要一个临时集合Collection,代码中这个临时集合作为静态类变量使用,然后在execute方法的尾部清除这个集合以便下次重用,编写这段代码的人可能认为这是线程安全的,因为 CopyOnWriteArrayList是线程安全的,但是他没有意识到,这个方法execute()是被多线程调用,那么可能多线程中一个线程看到另外一个线程的临时数据,即使使用Collections.synchronizedList也不能保证execute()方法内的逻辑不变性,这个不变性是:这个集合是临时集合,只用来在每个线程执行内部可见即可,不能暴露给其他线程知晓。

解决办法是使用本地List而不是全局的List。

2.使用不可变类

不可变类比如String Integer等一旦创建,不再改变,不可变类可以降低代码中需要的同步数量。

3.最小化锁的作用域范围

任何在锁中的代码将不能被并发执行,如果你有5%代码在锁中,那么根据Amdahl's law,你的应用形象就不可能提高超过20倍,因为锁中这些代码只能顺序执行,降低锁的涵括范围,上锁和解锁之间的代码越少越好。

4.使用线程池的Excutor,而不是直接new Thread执行

创建一个线程的代价是昂贵的,如果你要得到一个可伸缩的Java应用,你需要使用线程池,使用线程池管理线程。JDK提供了各种ThreadPool线程池和Executor。

5.宁可使用同步而不要使用线程的wait notify

从Java 1.5以后增加了需要同步工具如CycicBariier, CountDownLatch 和 Sempahore,你应当优先使用这些同步工具,而不是去思考如何使用线程的wait和notify,通过BlockingQueue实现生产-消费的设计比使用线程的wait和notify要好得多,也可以使用CountDownLatch实现多个线程的等待:

 

package com.thinkgem.jeesite.test;

import java.util.concurrent.CountDownLatch;

/**
 * CountDownLatch 的使用,使用CountDownLatch等待线程执行完毕,然后往下执行
 * @author sunhj
 * 时间:2017-3-5上午10:35:41
 * 公司:三三一网络科技
 */
public class CountDownLatchDemo {

	public static void main(String[] args) {
		CountDownLatch countDownLatch = new CountDownLatch(3);//表示3个人协同工作
		CountDownLatchDemo countDownLatchDemo = new CountDownLatchDemo();
		Thread frist = new Thread(countDownLatchDemo.new Service("frist",4,countDownLatch));
		Thread second = new Thread(countDownLatchDemo.new Service("second",1,countDownLatch));
		Thread thrid = new Thread(countDownLatchDemo.new Service("thrid",2,countDownLatch));
		frist.setDaemon(true);
		frist.start();
		second.start();
		thrid.start();
		try {
			countDownLatch.await();
			System.out.println("协同线程全部执行完毕,启动应用!");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
	}
	
	/**
	 * 线程服务类
	 * @author sunhj
	 * 时间:2017-3-5上午11:06:33
	 * 公司:三三一网络科技
	 */
	class Service implements Runnable{
		/**
		 * 别名
		 */
		private final String name;
		/**
		 * 线程等待时间
		 */
	    private final int timeToStart;
	    /**
	     * 计数
	     */
	    private final CountDownLatch latch;

	    public Service(String name, int timeToStart, CountDownLatch latch){
	        this.name = name;
	        this.timeToStart = timeToStart;
	        this.latch = latch;
	    }
		@Override
		public void run() {
			try {
				Thread.sleep(timeToStart*1000);//推荐:TimeUnit.SECONDS.sleep(timeToStart);
				System.out.println(name+"执行完毕!");
				latch.countDown();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

Output:

second执行完毕!

thrid执行完毕!
frist执行完毕!
协同线程全部执行完毕,启动应用!

6.使用BlockingQueue实现生产-消费模式

大部分并发问题都可以使用producer-consumer生产-消费设计实现,而BlockingQueue是最好的实现方式,堵塞的队列不只是可以处理单个生产单个消费,也可以处理多个生产和消费。如下代码:

 

package com.thinkgem.jeesite.test;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * 多线程实现生产消费模式
 * @author sunhj
 * 时间:2017-3-5下午8:41:23
 * 公司:三三一网络科技
 */
public class ProducerConsumerPattern {
	
	public static void main(String[] args) {
		BlockingQueue blockingQueue = new LinkedBlockingQueue<>();
		ProducerConsumerPattern producerConsumerPattern = new ProducerConsumerPattern();
		Thread producer = new Thread(producerConsumerPattern.new Producer(blockingQueue));
		Thread consumer = new Thread(producerConsumerPattern.new Consumer(blockingQueue));
		producer.start();
		consumer.start();
	}
	
	/**
	 * 生产者
	 * @author sunhj
	 * 时间:2017-3-5下午8:47:04
	 * 公司:三三一网络科技
	 */
	class Producer implements Runnable{
		private final BlockingQueue sharedQueue;
	    public Producer(BlockingQueue sharedQueue) {
	        this.sharedQueue = sharedQueue;
	    }
		    
		@Override
		public void run() {
			for (int i = 0; i < 10; i++) {
				try{
					System.out.println("生产者:"+i);
					sharedQueue.put(i);
				}catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	/**
	 * 消费者
	 * @author sunhj
	 * 时间:2017-3-5下午8:45:58
	 * 公司:三三一网络科技
	 */
	class Consumer implements Runnable{
		private final BlockingQueue sharedQueue;
	    public Consumer (BlockingQueue sharedQueue) {
	        this.sharedQueue = sharedQueue;
	    }
		
		@Override
		public void run() {
			while(true){
	            try {
	                System.out.println("消费者: "+ sharedQueue.take());
	            } catch (InterruptedException e) {
	            	e.printStackTrace();
	            }
	        }
		}
		
	}
}

 

Output:

Produced: 0

Produced: 1

Consumed: 0

Produced: 2

Consumed: 1

Produced: 3

Consumed: 2

Produced: 4

Consumed: 3

Produced: 5

Consumed: 4

Produced: 6

Consumed: 5

Produced: 7

Consumed: 6

Produced: 8

Consumed: 7

Produced: 9

Consumed: 8

Consumed: 9

7.使用并发集合Collection而不是加了同步锁的集合

Java提供了 ConcurrentHashMap CopyOnWriteArrayList 和 CopyOnWriteArraySet以及BlockingQueue Deque and BlockingDeque五大并发集合,宁可使用这些集合,也不用使用Collections.synchronizedList之类加了同步锁的集合, CopyOnWriteArrayList 适合主要读很少写的场合,ConcurrentHashMap更是经常使用的并发集合

8.使用Semaphore创建有界

为了建立可靠的稳定的系统,对于数据库 文件系统和socket等资源必须有界bound,Semaphore是一个可以限制这些资源开销的选择,如果某个资源不可以,使用Semaphore可以最低代价堵塞线程等待:

package com.thinkgem.jeesite.test;

import java.util.concurrent.Semaphore;

/**
 * Semaphore是负责协调各个线程, 以保证它们能够正确、合理的使用公共资源。也是操作系统中用于控制进程同步互斥的量。
 * @author sunhj
 * 时间:2017-3-5下午9:09:47
 * 公司:三三一网络科技
 */
public class SemaphoreTest {
	Semaphore semaphore = new Semaphore(1);
	
	public static void main(String[] args) {
		final SemaphoreTest test = new SemaphoreTest();
		new Thread(){
			public void run() {
				test.mutualExclusion();
			};
		}.start();
		new Thread(){
			public void run() {
				test.mutualExclusion();
			};
		}.start();
	}
	
	private void mutualExclusion() {
        try {
        	semaphore.acquire();
            System.out.println(Thread.currentThread().getName() + "内部互斥区域!!");
            Thread.sleep(1000);
        } catch (InterruptedException ie) {
            ie.printStackTrace();
        } finally {
        	semaphore.release();
            System.out.println(Thread.currentThread().getName() + "互斥区域外!!");
        }
    } 
	
}


Output:
Thread-1内部互斥区域!!
Thread-1互斥区域外!!
Thread-0内部互斥区域!!
Thread-0互斥区域外!!

9.宁可使用同步代码块,也不使用加同步的方法

使用synchronized 同步代码块只会锁定一个对象,而不会将当前整个方法锁定;如果更改共同的变量或类的字段,首先选择原子性变量,然后使用volatile。如果你需要互斥锁,可以考虑使用ReentrantLock 

10.避免使用静态变量

静态变量在并发执行环境会制造很多问题,如果你必须使用静态变量,让它称为final 常量,如果用来保存集合Collection,那么考虑使用只读集合。

11.宁可使用锁,而不是synchronized 同步关键字

Lock锁接口是非常强大,粒度比较细,对于读写操作有不同的锁,这样能够容易扩展伸缩,而synchronized不会自动释放锁,如果你使用lock()上锁,你可以使用unlock解锁:

lock.lock();

try {

 //do something ...

} finally {

 lock.unlock();

 }

发表评论

全部评论:0条

houzhe11

JAVA从业者