多线程设计模式有很多模式,我用简约的几行伪代码表示吧,如下:
模式1:简单的synchronized+wait/notify
最简单的wait/notify的应用,get的时候,如果没有数据就锁住对象,使当前线程等待,当有数据put的时候,调用notify来唤醒等待的线程,释放对象。如下:
private queue;
public synchronized Object get(){
while(queue.size<=0){wait()}
return queue.remove();
}
public synchronized void put(Object obj){
queue.put(obj);
notifyAll();
}
模式2:synchronized+return
模式2不同于模式1,是get的时候直接返回,线程不等待。如下:
private queue;
public synchronized Object get(){
return queue.size<=0? null :queue.remove();
}
public synchronized void put(Object obj){
queue.put(obj);
}
模式3:synchronized+wait/notify+buffer
模式3是对模式1的优化,增加了一个“缓存”,当put的时候超过大小需要wait,防止无限制的put操作而导致溢出。
private queue;
private count=0;
private bufferSize=10;
public synchronized Object get(){
while(count<=0)wait();
count--;
return queue.remove();
}
public synchronized void put(Object obj){
while(count>=bufferSize) wait();
count++;
queue.put(obj);
notifyAll();
}
模式4:读写锁
模式5:new工作线程
模式5是在前几个模式的基础上新增加了工作任务线程,当有新请求过来的时候就新建工作线程,如下handle方法:
private queue;
private count=0;
private bufferSize=10;
private synchronized Object get(){
while(count<=0)wait();
count--;
return queue.remove();
}
public synchronized void put(Object obj){
while(count>=bufferSize) wait();
count++;
queue.put(obj);
notifyAll();
}
public void handle(){
new Thread(this.get()){ public void run(){
System.out.println("Do work..");}}.start();
}
模式6:用线程池来优化工作线程
模式5的坏处是每次一个新的工作,需要new一个新的线程来做,这样开销太大,于是引入的“线程池”,如下:
private queue;
private count=0;
private bufferSize=10;
private final max_wk=20;
private final WorkerThread[] pool=new WorkerThread[max_wk];
private synchronized Object get(){
while(count<=0)wait();
count--;
return queue.remove();
}
public synchronized void put(Object obj){
while(count>=bufferSize) wait();
count++;
queue.put(obj);
notifyAll();
}
public void startWrok(){
for(int i=0;i<pool.size;++i){
pool[i]=new WorkerThread(this);
pool[i].start();
}
}
class WorkerThread extends Thread{
private Channel channel;
WorkerThread (Channel channel)
{
this.channel=channel;
}
public void run()
{
while(true){
Object obj=channel.get();
obj.doWork();
}
}
}
模式7:终止线程
工作线程在做工作的时候先判断是否需要终止线程。
public void stopWork(){
stopWork=true;
interrupt();
}
public void run(){
try{
while(!stopWork){
do something...
}
}catch( InterruptedException e){
}
}
模式8:ThreadLocal模式
目前很多frame比较经典的保持线程安全的模式,他像一个保险箱一样保管着所有线程对象,他需要做点额外的工作,比如把线程对象set()和get()。
class RealWork
{
public void doSomething(){// 工作
...
}
}
class Work
{
private static final ThreadLocal local=new ThreadLocal();
public void doSomething(){
getReal().doSomething();
}
private void getReal(){ // 获取所处当前线程的资源
RealWork obj=local.get();
if(obj==null){
obj=new RealWork(Thread.currentThread().getName());
local.set(obj);
}
return obj;
}
}
附录:
transient Thread owner;
public boolean put(E o) {
if (o == null) throw new NullPointerException();
final AtomicInteger count = this.count;// 大小计数器
if (count.get() == capacity)
return false;
int c = -1;
if (compareAndSetState(0, 1))// 检测当前状态,如果没有线程写,那么保存当前线程
owner = Thread.currentThread();
else
acquire(1);// 否则,等待正在写入的线程
try {
if (count.get() < capacity) {
insert(o);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal(); //
}
} finally {
if (tryRelease(1)) {
Node h = head; // 等待线程链表
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
}
}
if (c == 0)
signalNotEmpty();
return c >= 0; // 精炼的写法
}
在 5.0 以前,锁定的功能是由 Synchronized 关键字来实现的,这样做存在几个问题:
- 每次只能对一个对象进行锁定。若需要锁定多个对象,编程就比较麻烦,一不小心就会出现死锁现象。
- 如果线程因拿不到锁定而进入等待状况,是没有办法将其打断的
ReentrantLock锁等待线程队列:
获得当前等待取锁线程:
public final Collection<Thread> getQueuedThreads() {
ArrayList<Thread> list = new ArrayList<Thread>();
for (Node p = tail; p != null; p = p.prev) {
Thread t = p.thread;
if (t != null)
list.add(t);
}
return list;
}
附录:
关于Daemon线程:
我是在偶然之间用到的Daemon线程,当时却没有发现什么特别之处,直到今天才意识到微妙且严重的差别,如下:
Any Java thread can be a daemon thread. Daemon threads are service providers for other threads running in the same process as the daemon thread. The run() method for a daemon thread is typically an infinite loop that waits for a service request.
When the only remaining threads in a process are daemon threads, the interpreter exits. This makes sense because when only daemon threads remain, there is no other thread for which a daemon thread can provide a service.
做过实验才明白,原来当只存在Daemon线程且没有用户线程的时候,JVM退出,以前写thread的时候还真没注意,实验如下:
public class DaemonThread extends Thread{
public void run(){
while(true){
System.out.println("I'm daemon");
}
}
public static void main(String args[]){
DaemonThread daemon=new DaemonThread();
daemon.setDaemon(true);
daemon.start();
}
}
可以看到JVM很快就退出了,虽然这里是一个while(true)循环。