并发设计-使用线程的经验

并发三大定律:

Amdahl 定律:即使你有10个老婆,也不能一个月把孩子生下来。

Gustafson 定律:当你有10个老婆,就会要生更多的孩子。

Sun-Ni 定律:你要设法让每个老婆都在干活,别让她们闲着。

(这里就不一一说明每个定律的意思了,换个简单易懂的方式)

1:无论何种方式,启动一个线程,就要给它一个名字!这对排错诊断系统监控有帮助。否则诊断问题时,无法直观知道某个线程的用途。如:

Thread thread = new Thread() {
public void run() {
// do xxx
}
};
thread.setName("thread name");
thread.start();

2:要响应线程中断

Thread thread = new Thread("interrupt test") {
		public void run() {
		for (;;) {
		try {
		doXXX();
		} catch (InterruptedException e) {
		break;
		} catch (Exception e) {
		// handle Exception
		}
		}
		}
		};
		thread.start();

程序应该对线程中断作出恰当的响应。

3.ThreadLocal

顾名思义它是local variable(线程局部变量)。它的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。从线程的角度看,就好像每一个线程都完全拥有该变量。

注意:使用ThreadLocal,一般都是声明在静态变量中,如果不断的创建ThreadLocal而且没有调用其remove方法,将会导致内存泄露。

了解了ThreadLocal的内在原理发现,ThreadLocal事实上是与线程绑定的一个变量,如此就会出现一个问题:假设没有将ThreadLocal内的变量删除(remove)或替换,它的生命周期将会与线程共存。因此,ThreadLocal的一个非常大的“坑”就是当使用不当时,导致使用者不知道它的作用域范围。大家可能觉得线程结束后ThreadLocal应该就回收了。假设线程真的注销了确实是这种,可是事实有可能并不是如此。比如在线程池中对线程管理都是採用线程复用的方法(Web容器通常也会採用线程池)。在线程池中线程非常难结束甚至于永远不会结束。这将意味着线程持续的时间将不可预測,甚至与JVM的生命周期一致。那么对应的ThreadLocal变量的生命周期也将不可预测。

4.任务的提交者和执行者

为了方便并发执行任务,出现了一种专门用来执行任务的实现,也就是Executor。
由此,任务提交者不需要再创建管理线程,使用更方便,也减少了开销。

java.util.concurrent.Executors是Executor的工厂类,通过Executors可以创建你所需要的Executor。

5.阻塞队列

blockingQ.put(object);//如果队列满则阻塞

for (;;) {
blockingQ.take(); // 如果队列空则阻塞
}

阻塞队列,是一种常用的并发数据结构,常用于生产者-消费者模式。
在Java中,有三种常用的阻塞队列:

ArrayBlockingQueue
LinkedBlockingQueue
SynchronousQueue

6.实现一个简单的阻塞队列

class BlockingQ {
private Object notEmpty = new Object();
private Queue<Object> linkedList = new LinkedList<Object>();
public Object take() throws InterruptedException {
synchronized (notEmpty) {
if (linkedList.size() == 0) {
notEmpty.wait();//要执行wait操作,必须先取得该对象的锁。执行wait操作之后,锁会释放。
//被唤醒之前,需要先获得锁。
}
return linkedList.poll();
}
}
public void offer(Object object) {
synchronized (notEmpty) {
if (linkedList.size() == 0) {
notEmpty.notifyAll();//要执行notify和notifyAll操作,都必须先取得该对象的锁。
}
linkedList.add(object);
}
}
}

未取得锁就直接执行wait、notfiy、notifyAll会抛异常。

7.锁的使用

1.使用支持CAS的数据结构,避免使用锁,如:
AtomicXXX、ConcurrentMap、CopyOnWriteList、ConcurrentLinkedQueue

2.一定要使用锁的时候,注意获得锁的顺序,相反顺序获得锁,就容易产生死锁。

3.死锁经常是无法完全避免的,鸵鸟策略被很多基础框架所采用。

4.通过Dump线程的StackTrace,例如linux下执行命令 kill -3 <pid>,或者jstack –l <pid>,
或者使用Jconsole连接上去查看线程的StackTrace,由此来诊断死锁问题。

5.外部锁常被忽视而导致死锁,例如数据库的锁
 

猜你喜欢

转载自blog.csdn.net/weixin_42759988/article/details/89223110
今日推荐