二. 并发编程设计模式
2.1 单例模式
单例模式的饿汉式是没有线程问题的,但是懒加载的懒汉式就会有线程安全的问题
2.1.1 饿汉式
public class SingletionObject{ private static final SingletonObject singletion = new SingletonObject(); private SingletonObject(){ //empty } public SingletionObject getInstance(){ return this.singleton; } }
2.1.2 懒汉式
- 为了使用懒加载机制,改进为懒汉式
- 懒汉式解决了懒加载机制的问题,synchronized关键字解决了线程安全的问题
但是使用懒汉式使得每一次读操作都需要执行同步方法,调用过程串行化,大大的降低了执行的效率
public class SingletonObjedt{ private static final SingletonObject singleton; private SingletonObject(){ //empty } public synchronized static SingletonObject getInstance(){ if(this.singleton == null){ this.singleton = new SingletonObject(); } return SingletonObjedt.singleton; } }
2.1.3 double check
public class SingletonObject{ private static final SingletonObject singleton; private SingletonObject(){ //empty } public static SingletonObject getInstance(){ if(this.singleton == null){ synchronized (SingletonObject.class){ if(this.singleton == null){ this.singleton = new SingletonObject(); } } } return SingletonObject.singleton; } }
优点:
- double check方式解决了懒汉式的效率问题,只有第一次实例化的时候会同步执行,后续的读操作都可以并发操作
缺点:
会引起空指针异常----由于程序的重排序引起
- 因为在第一个线程进来创建instance实例,先在堆内存开辟了一块内存区域,但是分为加载,连接,初始化三个阶段,可能在连接阶段的解析过程中还未对实例进行构造方法的初始化时,当里面的属性并没有构造完实例的属性还是null时就把instance创建了
- 当第二个线程进来时,判断instance实例不为null,但是属性并未初始化,所以当调用对象中的属性时,会出现空指针异常
改进:使用volatile关键字
public class SingletonObject{ private static volatile final SingletonObejct singleton; private SingleObject(){ //empty } public static SingletonObject getInstance(){ if(this.singleton == null){ synchronized (SingletonObject.class){ if(this.singleton == null){ this.singleton = new SingletonObject(); } } } return SingletonObject.singleton; } }
2.1.4 InstanceHolder方式
使用InstanceHolder方式
- 可以保证懒加载机制
- 可以保证线程安全
- 可以保证高效率
public class SingletonObject{ private SingletonObject(){ //empty } private class InstanceHolder{ private static final SingletonObject singleton = new SingletonObject(); } public static SingletonObject getInstance(){ return InstanceHolder.singleton; } }
- 类的加载过程分为三个阶段:加载---连接---初始化,而static代码块和变量在jvm中只会被加载一次,严格保证执行的顺序
静态内部类和非静态内部类一样,都不会因为外部类的加载而加载,同时静态内部类的加载不需要依附外部类,只有在调用时才会主动加载
- 静态变量保证线程安全,并只加载一次
- 内部类保证了懒加载机制
2.1.5 枚举方式
public class SinlgetonObject(){ private SingletonObject(){ //empty } private enum Singleton{ //public static final Singleton INSTANCE = new Singleton(); INSTANCE; private final SingletonObject singleton; Singleton(){ this.singleton = new SingletonObject(); } public SingletonObject getInstance(){ return this.singleton; } } public static SingletonObject getInstance(){ return Singleton.INTANCE.getInstance(); } }
- 枚举是线程安全的,且构造方法是私有的只会装载一次
- 枚举值默认是静态类常量(static),在类加载时会初始化静态常量,会为每个类常量调用一次构造函数
- 这里是内部枚举类,所以只会在调用的时候才会主动加载
所以枚举和内部类其实是一种机制
- 静态变量保证线程安全,并只加载一次
- 内部类保证了懒加载机制
2.2 多线程的休息室WaitSet
所有对象都会有一个waitSet用来存放调用了该对象wait()方法进入block的线程
线程被notify()方法唤醒后,不一定立即执行,而是需要继续抢夺CPU的执行权,也就是锁
- 线程从waitSet中被唤醒的顺序不一定是FIFO
- wait() 调用者是Lock对象,是释放锁的;sleep() 调用者是线程自己,但不释放锁
- 线程被唤醒后,获取锁后仍然从block的地方继续执行
2.3 Volatile关键字
2.3.1 读写案例
当共享变量添加volatile关键字
public class VolitaleTest{ private volatile static int INIT_VALUE = 0; private final static int MAX_VALUE = 5; public static void main(String[] args){ new Thread(() -> { int localValue = INIT_VALUE; while(localValue < MAX_VALUE){ if(localValue != INIT_VALUE){ Optional.of("the value read is" + INIT_VALUE) .ifPresent(System.out::println); localValue = INIT_VALUE; } } }).start(); new Thread(() -> { int localValue = INIT_VALUE; while(localValue < MAX_VALUE){ Optional.of("the value updated is" + ++localValue) .ifPresent(System.out::println); INIT_VALUE = localValue; try{ Thread.sleep(1000L); }catch(InterruptedException e){ e.printStackTrace(); } } }).start(); } } -------------------------------------------------------------------- the value updated is 1 the value read is 1 the value updated is 2 the value read is 2 the value updated is 3 the value read is 3 the value updated is 4 the value read is 4 the value updated is 5 the value read is 5
当共享变量未添加volatile关键字时----由于线程之间不可见性引起
public class VolitaleTest{ private static int INIT_VALUE = 0; private final static int MAX_VALUE = 5; public static void main(String[] args){ new Thread(() -> { int localValue = INIT_VALUE; while(localValue < MAX_VALUE){ if(localValue != INIT_VALUE){ Optional.of("the value read is" + INIT_VALUE) .ifPresent(System.out::println); localValue = INIT_VALUE; } } }).start(); new Thread(() -> { int localValue = INIT_VALUE; while(localValue < MAX_VALUE){ Optional.of("the value updated is" + ++localValue) .ifPresent(System.out::println); INIT_VALUE = localValue; try{ Thread.sleep(1000L); }catch(InterruptedException e){ e.printStackTrace(): } } }) } } -------------------------------------------------------------------- the value updated is 1 the value updated is 2 the value updated is 3 the value updated is 4 the value updated is 5
原因:java内存模型以及CPU缓存不一致的问题
- 由于CPU的速度远大于内存的速度,所以CPU与内存中间存在着缓存用来提高执行的效率
- 且由于该线程只有读的操作,java做了优化,所以cache中的数据不会去同步内存中的数据
- 而多线程的操作都是对于当于当前CPU的缓存进行操作,并不是对于主内存的直接操作,从而会存在各个CPU所操作的同一份数据不同步的情况
注意:
如果在多线程任务中,调用同步方法,或某些native方法(如Thread.sleep),会刷新线程内存数据,多线程任务也会根据开关的新值,断开任务。
private static boolean closed = false; public static void main(String[] args){ new Thread(() -> { int i = 0; while(!closed){ i++; method1(i); } System.out.println("Thread over..."); }).start(); try{ Thread.sleep(1000L); }catch(InterruptedException e){ e.printStackTrace(); } closed = true; System.out.println("Main over..."); } public void method1(int i){ //empty } public void method2(int i){ System.out.println(i); //println方法为同步方法,会更新线程内存数据 }
2.3.2 双写案例
当多个线程都有写的操作时,各自的cache就会去同步内存中的数据
public class DoubleWriteVolatileTest{ private volatile static int INIT_VALUE = 0; private final static int MAX_VALUE = 100; public static void main(String[] args){ new Thead(() -> { while(INIT_VALUE < MAX_VALUE){ Optional.of("T1 -> " + (++INIT_VALUE)) .ifPresent(System.out::println); try{ Thread.sleep(10L); }catch(InterruptedException e){ e.printStackTrace(); } } }).start(); new Thead(() -> { while(INIT_VALUE < MAX_VALUE){ Optional.of("T2 -> " + (++INIT_VALUE)) .ifPresent(System.out::println); try{ Thread.sleep(10L); }catch(InterruptedException e){ e.printStackTrace(); } } }).start(); } } ------------------------------------- T1 -> 1 T2 -> 2 T1 -> 3 T2 -> 3 T1 -> 4 T2 -> 5 T1 -> 6 T2 -> 7 .......
- 由于多个线程都有写的操作,所以cache中的数据会去同步内存中的数据,但是volatile并不能保证线程安全性,所以仍然会出现重复的数据
2.3.3 cpu以及cpu缓存结构
高速缓存和主存的交互:从而引入cache后引发了数据不一致的问题
- 在运算过程中,cache会将数据从主存中复制一份,CPU计算时就直接去cache中获取数据,再将cache中的数据刷新到主内存中去(但是当CPU对缓存的数据只有读的时候,cache的数据不会立即刷新到主存中)
解决方案
给数据总线加锁
- 每次只有一个CPU可以对数据进行操作
intel的高速缓存一致性协议(MESI)
- 当CPU写入数据的时候,如果该变量被共享(也就是说,在其他CPU中也存在该变量的副本),就会发出一个信号,通知其他CPU该变量的缓存无效,需要重新到主内存获取
synchronized和volatile关键字
- synchronized关键字是解决多个线程写操作之间原子性的问题
- volatile关键字是解决读操作中缓存数据与主存数据不一致,线程之间不可见的问题
2.3.4 并发编程的三大特性
并发编程的三大特性
- 原子性:是指一个操作或多个操作是不可中断的,即使是多个线程一起执行的时候,一个操作或多个操作一旦开始就不会被其他线程干扰
- 可见性:是指当一个线程修改了某一共享变量,其他线程是否能够立即知道这个修改(对串行来说,可见性问题是不存在的)
- 有序性:在并发时候,程序的执行可能会出现乱序,给人的直观感觉就是:写在前面的代码会在后面执行,有序性问题的原因就是程序在执行过程中会进行指令重排序,只要保证直接结果一致即可,即最终一致性
volatile关键字:一旦一个共享变量被volatile修饰,具备两层语义,保证了不同线程间的可见性
- 强制对缓存的修改操作立即写入主存
- 当某个CPU修改其缓存的共享变量时,其他线程中该共享变量将会失效,需要从主存中读取最新的数据
volatile关键字禁止重排序,也就保证了有序性,但是并未保证原子性
INIT_VALUE++; 1. Read from main memory INIT_VALUE -> 10; 2. INIT_VALUE = 10 + 1;
2.4 观察者模式
2.4.1 观察者设计模式
Subject
public class Subject{ private List
observers = new ArrayList<>(); private int State; public int getState(){ return this.state; } public void setState(int state){ if(this.state == state){ return; } this.state = state; notifyAll(); } public void register(Observer observer){ observers.add(obserber); } private void notifyAll(){ observers.stream().forEach(Observer::update); } } Observer
public abstract class Observer{ protect Subject subject; public Observer(Subject subject){ this.subject = subject; this.subject.register(this); } public abstract void update(); }
Observer实现
public class ObserverOctalImplement extends Observer{ public OctalObserver(Subject subject){ super(subject); } @Override public void update(){ Optional.of("Binary String" + Integer.toOctalString(subject.getState())) .ifPresent(System.out::println); } }
Test
public class Client{ public static void main(String[] args){ Subject subject = new Subject(1); new ObserverOctalImplement(subject); subject.setState(2); } }
2.4.2 观察线程的生命周期
可被观察的Runnable接口抽象类
public abstract class ObservableRunnable implement Runnable{ private final LifeCycleListener listener; public ObserableRunnable(final LifeCycleListener listener){ this.listener = listener; } public void notifyChange(final RunnableEvent event){ listener.onEvent(event); } public static class RunnableEvent{ private final RunnableState state; private final Thread thread; private final Throwable cause; public RunnableEvent(RunnableState state,Thread thread,Throwable cause){ this.state = state; this.thread = thread; this.cause = cause; } } public enum RunnableState{ RUNNING,ERROR,DONE; } }
Listener
public interface LifeCycleListener{ void onEvent(ObservableRunnable.RunnableEvent event); }
Observer
public class ThreadListenerObserver implement LifeCycleListener{ private void concurrentQuery(List
ids){ if(Collections.isEmpty(ids)){ return ; } ids.stream().forEach(id -> new Thread(new ObservableRunnable(this){ @Override public void run(){ try{ notifyChange(new RunnableEvent(RunnableState, RUNNING,Thread.currentThread,null)); System.out.println("query for the id" + id); Thread.sleep(1000L); notifyChange(new RunnableEvent(RunnableState, DONE,Thread.currentThread,null)); }catch(InterruptedException e){ notifyChange(new RunnableEvent(RunnableState, ERROR,Thread.currentThread,e)); } } },id).start()); } @Override public void OnEvent(ObservableRunnable.RunnableEvent event){ System.out.println(event.getThead().getName() + event.getState()); if(event.getCause() != null){ System.out.println(event.getCause()); } } } Test
public class ThreadLifeCycleClient{ public static void main(String[] args){ new ThreadLifeCycleObserver().concurrentQuery(Array.asList("1","2")); } }
2.5 单线程执行模式
public static void main(String[] args){ Gate gate = new Gate(); new Thread(() -> { @Override public void run(){ gate.pass("Baobao","Beijing"); } }).start(); new Thread(() -> { @Override public void run(){ gate.pass("ShangShang","Shanghai"); } }).start(); } ------------------------------------------------------------ public class Gate{ private int count = 0; private String name; private String address; public synchronized void pass(String name,String address){ this.name = name; this.address = address; verify(); } public void verify(){ if(this.name.charAt(0) != this.address.charAt(0)){ System.out.println("Broken...." + toString()); } } public String toString(){ return "No." + this.count + ":" + this.name + ":" + this.address; } }
- 在写方法pass()上添加synchronized关键字,否则多线程进行并发写时会出现线程安全问题
但是在读toString()方法的过程中,可能会出现正在进行写的操作
- 解决方案:读写分离锁设计模式
2.6 读写锁分离锁模式
读写分离:允许并行化读操作
- read read : 并行化
- read write : 串行化
- write write : 串行化
ReadWriteLock
public class ReadWriteLock{ private int waitingReader = 0; private int readingReader = 0; private int waitingWriter = 0; private int writingWriter = 0; private ThreadLocal tl = new TheadLocal(); private boolean preferWriter = true; public ReadWriteLock(){ this(true); } public ReadWriteLock(boolean preferWriter){ this.preferWriter = preferWriter; } //读锁 public synchronized void readLock(){ this.waitingReader++; try{ while(writingWriter > 0 || (preferWriter && waitingWriter > 0)){ this.wait(); } this.readingReader++; tl.set(Thread.currentThread().getName()); }catch(InterruptedException e){ e.printStackTrace(); }finally{ this.waitingReader--; } } //读解锁 public synchronized void readUnlock(){ if(tl.get() != null){ this.readingReader--; tl.remove(); this.notifyAll(); } } //写锁 public synchronized void writeLock(){ this.waitingWriter++; try{ while(readingReader > 0 || writingWriter > 0){ this.wait(); } this.writingWriter++: tl.set(Thread.currentThread().getName()); }catch(InterruptedException e){ e.printStackTrace(); }finally{ this.waitingWriter--; } } //写解锁 public synchronized void writeUnlock(){ if(tl.get() != null){ this.writingWriter--; tl.remove(); this.notifyAll(); } } }
SharedData
public class ShareData{ private final char[] buffer; private final ReadWriteLock lock = new ReadWriteLock(); public ShareData(int size){ this.buffer = new char[size]; for(int i=0;i < size;i++){ this.buffer[i] = "*"; } } public char[] read(){ try{ lock.readLock(); return this.doRead(); }catch(InterruptedException e){ e.printStackTrace(); }finally{ lock.readUnlock(); } } public void write(char c){ try{ lock.writeLock(); this.doWrite(); }catch(InterruptedException e){ e.printStackTrace(); }finally{ lock.writeUnlock(); } } private char[] doRead(){ char[] newBuffer = new char[buffer.size]; for(int i=0;i < buffer.length;i++){ newBuffer[i] = buffer[i]; } return newBuffer; } private void doWrite(char c){ for(int i=0;i < buffer.length;i++){ buffer[i] = c; } } }
ReaderWorker
public class ReaderWorker extends Thread{ private final SharedData data; public ReaderWorker(SharedData data){ this.data = data; } @Override public void run(){ try{ while(true){ char[] readBuffer = data.read(); System.out.println(Thread.currentThread().getName() + readBuffer); } }catch(InterruptedException e){ e.printStackTrace(); } } }
WriterWorker
public class WriterWorker extends Thread{ private static final Random random = new Random(System.currentTimeMillis()); private final SharedData data; private final String filter; private int index = 0; public WriterWorker(SharedData data,String filter){ this.data = data; this.filter = filter; } @Override public void run(){ try{ while(true){ char c = nextChar(); data.write(c); Thread.sleep(1000L); } }catch(InterruptedException e){ e.printStackTrace } } public char nextChar(){ char c = filter.charAt(index); index++; if(index >= filter.length){ index = 0 } return c; } }
Test
public static void main(String[] args){ final SharedData data = new SharedData(4); new ReaderWorker(data).start(); new ReaderWorker(data).start(); new ReaderWorker(data).start(); new ReaderWorker(data).start(); new WriterWorker(data,"abcd").start(); new WriterWorker(data,"ABCD").start(); }
2.7 不可变对象模式---immutable
不可变对象设计模式可以起到无锁的方式
不可变对象(如String时线程是安全的,也是不可变的,任何属性都不能被修改)
- 其中StringBuffer是线程安全的,但是它是不可变的类,而StringBuilder是线程不安全的,多线程操作要加锁
不可变对象原则
- immutable对象的状态在创建之后就不能发生改变,任何对它的改变都应该产生一个新的对象
- immutable类的所有的属性都应该是final的
- 对象必须被正确的创建,比如:对象引用在对象创建过程中不能泄露(leak)
- 对象应该是final的,以此来限制子类继承父类,以避免子类改变了父类的immutable的特性
- 不提供对成员的改变方法,比如setXxx
确保所有的方法不会被重载,手段有两种
- 使用final Class(强不可变类)
- 将所有类方法加上final(弱不可变类),
如果某一个类成员不是原始变量(primitive)或者不可变类,必须通过深克隆方法,来确保类的不可变
- 比如final修饰的List对象,其地址是不可变的,但是对象是可以修改的,必须使用克隆暴露出去
public class ImmutableTest{ private final int age; private final String name; private final List
list; public Immutable(int age,String name){ this.age = age; this.name = name; list = new ArrayList<>(); } public int getAge(){ return this.age; } public String getName(){ return this.name; } public List getList(){ return Collections.unmodifiableList(list); } }
2.8 Future模式
思想:
去蛋糕店买蛋糕,但是蛋糕要现做,所以就领一张凭据(Future),然后去做其他的事
- 等自己事情忙完再过来凭据拿蛋糕----轮询结果
- 蛋糕做完就直接送到指定地址-----异步回调
Future---凭据接口
public interface Future
{ T get(); } Future实现
public class AsynFuture
implements Future { private volatile boolean done = false; private T result; @Override public T get() throw InterruptedException { synchronized (this){ while(!done){ this.wait(); } } return result; } private void Done(T result){ this.result = result; this.done = ture; this.notifyAll(); } } FutureTask---任务接口
public FutureTask{ T call(); }
Service
public class FutureService{ //轮询 public
Future submit(final FutureTask task) throw InterruptedException{ AsynFuture asynFuture = new AsynFuture(); new Thread(() -> { T result = task.call(); asynFuture.done(result); }).start(); return asynFuture; } //异步回调 public Future submit(final FutureTask task,final Consumer consumer) throw InterruptedException{ AsynFuture asynFuture = new AsynFuture(): new Thread(() -> { T result = task.call(); asynFuture.done(result); consumer.accept(result); }).start(); return asynFuture; } } Test
public static void main(String[] args){ FutureService service = new FutureService(): //轮询 Future
future = service.submit(() -> { try{ Thread.sleep(10_000L); }catch(InterruptedException e){ e.printStackTrace(); } return "Finished..." }); System.out.println("=============="); Thread.sleep(5_000L); System.out.println(future.get()); //回调 service.submit(() -> { try{ Thread.sleep(10_000L); }catch(InterruptedException e){ e.printStackTrace(); } return "Finished..." } , System.out::println); System.out.println("=============="); Thread.sleep(5_000L); }
2.9 确保挂起模式(Guarded Suspension)
思想:
- 线程正在执行任务,后续又来了新的任务,将新任务先存入队列中,等线程执行完后再取新任务
Queue
public class RequestQueue{ private final LinkedList
queue = new LinkedList (); public void setRequest(Request request){ synchronized (this){ queue.addLast(request); this.notifyAll(); } } public Request getRequest(){ synchronized (this){ while(queue.size() <= 0){ try{ this.wait(); }catch(InterruptedException e){ return null; } } return queue.removeFirst(); } } } ClientThread
public class ClientThread extends Thread{ private final RequestQueue queue; private final Random random; private final String value; public ClientThread(RequestQueue queue, String value){ this.queue = queue; this.value = value; this.random = new Random(System.currentTimeMillis()); } @Override public void run(){ for(int i=0;i < 10;i++){ System.out.println("Client -> request" + value); queue.setRequest(new Request(value)); try{ Thread.sleep(random.nextInt(1000)); }catch(InterruptedException e){ e.printStackTrace(); } } } }
ServerThread
public class ServerClient extends Thread{ private final RequestQueue queue; private volatile boolean closed = false; public ServerClient(RequestQueue queue){ this.queue = queue; } @Override public void run(){ while(!closed){ Request request = queue.getRequest(); if(request == null){ System.out.println("receive empty request..."); continue; } System.out.println("Server ->" + request.getValue()); try{ Thread.sleep(1000L); }catch(InterruptedException e){ e.printStackTrace(); } } } public void close(){ this.closed = ture; this.interrupt(); } }
Test
public class SuspensionTest{ public static void main(String[] args){ final RequestQueue queue = new RequestQueue(); new ClientThread(queue,"A1").start(); ServerThread server = new ServerThread(queue); server.start(); Thread.sleep(10_000L); server.close(); } }
2.10 Balking模式
思想:
- 当不想使用线程间的通讯---wait(),notifyAll()
轮询数据,根据数据是否发生变化来判断是否执行任务
- 线程A更新数据,线程B轮询查询数据是否被更新,然后做出相应的操作
public class BalkingData{ private final String fileName; private String content; private volatile boolean changed; public BalkingData(String fileName,String content){ this.fileName = fileName; this.content = content; this.changed = true; } public synchronized void change(String newContent){ this.content = newContent; this.changed = true; } public synchronized void save() throw IOException{ if(!changed){ return; } doSave(); this.changed = false; } private void doSave() throw IOException{ System.out.println(Thread.currentThread().getName() + "Saving"); try(Writer writer = new FileWriter(fileName,true)){ writer.write(content); } } }
使用偏向锁改进---此方式为线程通讯方式
public class BalkingData{ private String fileName; private volatile LinkedList
contents; private ReadWriteLock lock = new ReadWriteLock(); public BalkingData(String fileName,String content){ this.fileName = fileName; this.contents = new LinkedList<>(); contents.addLast(content); } public void change(String newContent){ lock.readLock(); this.contents.addLast(newContent); lock.readUnlock(); } public void save() throw IOException{ lock.writeLock(); if(contents.size() <= 0){ return; } doSave(); lock.writeUnlock(); } private void doSave() throw IOException{ System.out.println(Thread.currentThread().getName() + "Saving"); try(Writer writer = new FileWriter(fileName,true)){ writer.write(contents.removeFirst()); } } }
2.11 上下文模式
2.11.1 ThreadLocal
- ThreadLocal可以存储变量,但是创建和读取变量只允许当前线程使用,如果两个线程要使用ThreadLocal,他们彼此是隔离开来的
- 其存储结构是使用ThreadLocal.ThreadLocalMap来存储数据,并且将ThreadLocalMap存放到当前Thread对象中,通过当前线程对象来获取当前线程的ThreadLocalMap
2.11.2 上下文设计模式
- 当线程后一个阶段要使用到前一个阶段的结果数据时,使用ThreadLocal作为单个线程数据共享的仓库
Context
public class Context{ private String name; private String cardId; public void setName(String name){ this.name = name; } public String getName(){ return this.name; } public void setCardId(String cardId){ this.cardId = cardId; } public String getCardId(){ return this.cardId; } }
ActionContext
public class ActionContext{ private final ThreadLocal
tl = new ThreadLocal (){ @Override protected Context initialValue(){ return new Context(); } }; private ActionContext(){ //empty... } private class InstanceHolder{ private static final ActionContext actionContext; = new ActionContext(); } public static ActionContext getInstance(){ return InstanceHolder.actionContext; } public Context getContext(){ return this.tl.get(); } } QueryFromDBAction
public class QueryFromDBAction{ public void execute(){ try{ Thread.sleep(1000L); String name = "AAA - " + Thread.currentThread().getName(); ActionContext.getActionContext().getContext().setName(name); }catch(InterruptedException e){ e.printStackTrace(); } } }
QueryFromHttpAction
public class QueryFromHttpAction{ public void execute(){ Context context = ActionContext.getActionContext().getContext(); String cardId = getCardId(context.getName()); context.setCardId(cardId); } public String getCardId(String name){ return "111 - " + Thread.currentThread().getName(); } }
Test
public static void main(String[] args){ QueryFromDBAction db = new QueryFromDBAction(); QueryFromHttpAction http = new QuryFromHttpAction(); Instream.range(1,5).forEach(i -> { new Thread(() -> { Context context = ActionContext.getActionContext().getContext() db.execute(); System.out.println("db query finished..."); http.execute(); System.out.println("http query finished..."); System.out.println("result" + context.getName() + "-" + context.getCardId); }).start(); }); }
2.11.3 单例模式和静态方法
单例模式和静态方法的区别
- 静态方法性能更好,在编译期就已经绑定好
- 单例模式可以延迟初始化,如果需要加载比较重的对象,用单例模式会更好
- 单例模式可以被继承,方法可以被重写,而静态方法不能
- 单例模式适合存状态信息需要改变的需求,静态方法适合仅仅提供全局方法的需求
静态方法
- 在java中静态方法可以被继承,但是不能被重写
- 如果子类中也含有一个返回类型,方法名,参数列表与之相同的静态方法,那么该子类实际上是将父类的同名方法进行隐藏,而非重写
父类引用指向子类对象时,只会调用父类的静态方法,所以他们的行为也并不具有多态性
- Father father = new Son(); father.staticMethod();// Father staticMethod...
2.12 生产-消费模式
Queue
public class MessageQueue{ private final LinkedList
queue; private final int DEFAULT_MAX = 100; private final int limit; public MessageQueue(){ this(DEFAULT_MAX); } public MessageQueue(int limit){ this.limit = limit; queue = new LinkedList (); } //生产... public void put(final Message message){ synchronized(queue){ while(queue.size() > limit){ try{ queue.wait(); }catch(InterruptedException e){ e.printStackTrace(); } } queue.addLast(message); queue.notifyAll(); } } //消费... public Message get(){ synchronized(queue){ while(queue.isEmpty()){ try{ queue.wait(); }catch(InterruptedException e){ e.printStackTrace(); } } Message message = queue.removeFirst(); queue.notifyAll(); return message; } } public int getMaxLimit(){ return this.limit; } public int getMessageSize(){ synchronized(queue){ return queue.size(); } } } ProducerThread
public class ProducerThread extends Thread{ private final MessageQueue queue; private final AtomicInteger counter = new AtomicInteger(0); private final Random random = new Random(System.currentTimeMillis()); public ProducerThread(MessageQueue queue,int seq){ super("Producer-" + seq); this.queue = queue; } @Override public void run(){ while(true){ try{ Message message = new Message("Message" + counter.getAndIncrement()); queue.put(message); System.out.println(Thread.currentThread().getName() + "put Message"
Thread.sleep(random.next(1000L)); }catch(InterruptedExcetpion e){ break; } } }
}
* ConsumerThread
public class ConsumerThread extends Thread{
private final MessageQueue queue; private final AtomicInteger counter = new AtomicInteger(0); private final Random random = new Random(System.currentTimeMillis()); public ConsumerThread(MessageQueue queue,int seq){ super("Consumer - " + seq); this.queue = queue; } @Override public void run(){ while(true){ try{ Message message = queue.get(); System.out.println(Thread.currentThread().getName() + "get Message" + message.getData()); Thread.sleep(1000L); }catch(InterruptedException e){ break; } } }
}
* Test
public class Test{
public static void main(String[] args){ MessageQueue queue = new MessageQueue(); new ProducerThread(queue,1).start(); new ProducerThread(queue,2).start(); new ProducerThread(queue,3).start(); new ConsumerThread(queue,1).start(); new ConsumerThread(queue,2).start(); }
}
2.13 Count Down模式
- CountDown设计模式可以用来替代join();
CustomCountDownLatch
public class CustomCountDownLatch{ private final int total; private int counter = 0; public CustomCountDownLatch(int total){ this.total = total; } public void down(){ synchronized(this){ this.counter++; this.notifyAll(); } } public void wait(){ synchronized(this){ if(counter != total){ try{ this.wait(); }catch(InterruptedException e){ e.printStackTrace(); } } } } }
Test
public class Test{ private static final Random random = new Random(System.currentTimeMillis()); public static void main(String[] args){ CustomCountDownLatch countDownLatch = new CustomCountDownLatch(5); IntStream.rangeClosed(1,5).forEach(i -> { new Thread(() - { System.out.println(Thread.currentThread().getName() + "is working"); try{ Thread.sleep(random.next(1000)); }catch(InterruptedException e){ e.printStackTrace(); } countDownLatch.down(); },String.valueOf(i)).start(); }); countDownLatch.wait(); System.out.println("多线程任务全部结束,开始第二阶段任务"); System.out.println("----------------"); System.out.println("Finished...") } }
2.14 Thread-per-message模式
- 每一个请求都开启一个线程去执行
MessageHandler
public class MessageHandler{ private final static Random random = new Random(System.currentTimeMills()); private final static Executor executor = Executor.newFixedThreadPool(5); public void request(Message message){ executor.submit(() -> { String value = message.getValue(); try{ Thread.sleep(random.nextInt(1000)); System.out.println("The message will be finished"); }catch(InterruptedException e){ e.printStackTrace(); } }); } }
Test
public static void main(String[] args){ final MessageHandler handler = new MessageHandler(); IntStream.rangeClosed(1,10).forEach( i -> handler.request(new Message(String.valueOf(i)))); }
2.15 Two Phrase Termination模式
我们把线程平常处理的状态称为"作业中",当我们希望结束这个线程的时候,则发出"终止请求",接着这个线程不会马上结束,需要进行必须的刷新工作,这个被称为"终止处理中"
- 从"作业中"变成"终止处理中"是第一个阶段
"终止操作中"是第二个阶段
- "终止处理中"的状态不会进行平常的操作,虽然线程还在进行,但是进行的是终止处理,直到终止处理结束后,才真正结束线程
开始 ---> 作业中 ---> [终止要求] ---> 终止处理中 ----> 结束
- 也就是说第一阶段执行终止方法后还会继续执行终止处理的操作,直到第二阶段的处理操作结束才算真正的结束,而第二阶段的操作一般放在finally中
AppServer
public class AppServer extends Thread{ private final int port; private static final int DEFAULT_PORT = 12345; private volatile boolean closed = false; private List
RequestHandler handlers = new ArrayList<>(); private ServerSocket server ; public AppServer(){ this(DEFAULT_PORT); } public AppServer(int port){ this.port = port; } @Override public void run(){ try{ this.server = new ServerSocket(port); while(!closed){ Socket client = server.accept(); RequestHandler handler = new RequestHadler(client); executor.submit(handler); this.handlers.add(handler); }catch(IOException e){ throw new RuntimeException(e); }finally{ this.dispose(); } } } private void dispose(){ this.handlers.stream().forEach(RequestHandler::stop); this.executor.shutDown(); } public void shutDown(){ this.closed = true; this.interrupt(); this.server.close() } } RequestHandler
public class RequestHandler implement Runnable{ private final Socket socket; private volatile boolean running = true; public RequestHandler(Socket socket){ this.socket = socket; } @Override public void run(){ try(InputStream intputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(inputStream)) BufferedWriter bw = new BuuferedWriter(new OutputStreamWriter(outputStream))){ while(running){ String message = br.readLine(); if(message == null){ break; } System.out.println("Come from client -" + message); PrintWriter.write("echo" + message); printWiter.flush(); } }catch(IOException e){ this.running = ture; }finally{ this.stop(); } } public void stop(){ if(!running){ return; } this.running = false; try{ this.socket.close() }catch(IOException e){ // } } }
Test
public static void main(String[] args){ AppServer server = new AppServer(12345); server.start(); try{ Thread.sleep(30_000L) }catch(InterruptedException e){ e.printStackTrace(); } server.shutDown(); }
2.16 Work Thread 模式
思想
- 流水线工人使用一个队列来存储货物,一边向队列输入,一边向队列输出
Channel
public class Channel{ private final static int MAX_REQUEST = 100; private final Request[] requestQueue; private int head; private int tail; private int count; private final WorkThread[] threads; public Channel(int thread){ this.requestQueue = new Request[MAX_REQUEST]; this.head = 0; this.tail = 0; this.count = 0; this.threads = new WorkThread[thread]; init(); } public void init(){ for(int i=0;i < threads.length;i++){ threads[i] = new WorkThread(i,this); } } public void startWork(){ Arrays.asList(threads).forEach(WorkThread::start); } public synchronized void put(Request request){ while(count > requestQueue.length){ try{ this.wait(); }catch(InterruptedException e){ e.printStackTrace(); } } this.requestQueue[tail] = request; this.tail = (tail + 1) % requestQueue.length; this.count++; this.notifyAll(); } public synchronized Request get(){ while(count <= 0){ try{ this.wait(); }catch(InterruptedException e){ e.printStackTrace(): } } Request request = this.requestQueue[head]; this.head = (head + 1) % this.requestQueue.length; this.count--; this.notifyAll(); return request; } }
Request
public class Request{ private final String name; private final int age; public Request(String name,int age){ this.name = name; this.age = age; } public void execute(){ System.out.println(Thread.currentThread().getName()
}
}
* TransportWorker
public class TransportWoker extends Thread{
private final Channel channel; private static final Random random = new Random(System.currenTimeMillis()); public TransportWork(String name,Channe channel){ super(name); this.channel = channel; } @Override public void run(){ try{ for(int i=0;true;i++){ Request request = new Request(this.getName,i); this.channel.put(request); Thread.sleep(1000L); } }catch(InterruptedException e){ e.printStackTrace(); } }
}
* DoWorker
public class DoWork extends Thread{
private final Channel channel; private final static Random random = new Random(System.currentTimeMillis()); public DoWork(String name,Channel channel){ super(name); this.channel = channel; } @Override public void run(){ while(true){ channel.take().execute(); } }
}
* Test
public static void main(String[] args){
final Channel channle = new Channel(5); channel.startWorker(); new TransportWorker("A1",channel).start(); new TransportWorker("A2",channel).start(); new TransportWorker("A3",channel).start();
}
2.17 Active Object模式
Active Object
- 对象分为主动对象和被动对象,主动对象内部包含一个线程,可以自动完成动作或改变状态,而一般的被动对象只能通过其他对象的调用才能有所作为
- 在多线程程序中,经常把一个线程封装到主动对象里面
特点
- 主动对象是内部拥有自己的控制线程的对象,为了简化异步调用的复杂性,这个模式分离了方法的执行和调用
- 使用这个模式,一个对象中无论是否有独立的线程,客户从外部访问它时,感觉时一样的
- 主动对象模式隔离的方法执行和方法调用的过程,提高了并行性,对内部拥有控制线程的主动对象,降低了异步访问的复杂性
好处
- 增强了程序的并行性,降低了同步的复杂性,客户线程和异步调用操作并行执行,同步的复杂性由调度者独立处理
- 让多个耗时的操作并行执行,只要软件和硬件支持,可以让多个活动的对象彼此不干扰地同时运行
- 方法的执行和调用的顺序可以不一致,方法的调用是异步调用,而方法的执行决定如何调用
坏处
- 性能过多的消耗
- 增加调试的难度
思想
- 当我们main线程调用System.gc()时,其实是开辟了一个新的线程去执行gc()任务
- 当我们使用Runnable和Future去执行方法时,只能去执行单一的一个方法
- 当我们想异步的执行一系列的方法时,我们可以将方法的指令存入到队列中进行顺序异步执行
ActiveObject
public interface ActiveObject{ Result makeString(int count,char fillChar); void displayString(String text); }
Servant
class Servant implement ActiveObject{ @Override public Result makeString(int count,char fillChar){ char[] buf = new char(count); for(int i=0;i
MethodRequest:对应ActiveObject每一个方法
public abstract class MethodRequest{ private final Servant servant; protected final FutureResult futureResult; public MethodRequest(Servant servant,FutureResult futureResult){ this.servant = servant; this.futureResult = futureResult; } public abstract void execute(); }
MakeStringRequest
public class MakeStringRequest extends MethodRequest{ private final int count; private final char fillChar; public MakeStringRequest(Servant servant,FutureResult futureResult ,int count,char fillChar){ super(servant,futureResult); this.count = count; this.fillChar = fillChar; } @Override public void execute(){ Result result = servant.makeString(count,fillChar); futureResult.setResult(result); } }
DisplayStringRequest
public class DisplayStringRequest extends MethodRequest{ private final String text; public DisplayStringRequest(Servant servant,String text){ super(servant,null); this.text = text; } @Override public void execute(){ this.servant.displayString(text); } }
Request
public interface Request{ Object getResultValue(); }
RealResult
public class RealResult implement Result{ private final Object resultValue; public RealResult(Object resultValue){ this.resultValue = resultValue; } @Override public Object getResultValue(){ return this.resultValue; } }
FutureResult
public class FutureResult implement Result{ prviate Result result; private boolean ready = false; public synchronized void setResult(Result result){ this.result = result; this.ready = true; this.notifyAll(); } @Override public Object getResultValue(){ while(!ready){ try{ this.wait(); }catch(InterruptedException e){ e.printStackTrace(); } } return this.result.getResultValue(); } }
ActivationQueue
public class ActivationQueue{ private final static int MAX_METHOD_REQUEST_QUEUE_SIZE = 100; private final LinkedList
methodQueue; public ActivationQueue(){ this.methodQueue = new LinkedList<>(); } public synchronized void put(MethodRequest request){ while(methodQueue.size() > MAX_METHOD_REQUEST_QUEUE_SIZE){ try{ this.wait(); }catch(InterruptedException e){ e.printStackTrace(); } } this.methodQueue.addLast(request); this.notifyAll(); } public synchronized MethodRequest get(){ while(methodQueue.isEmpty()){ try{ this.wait(); }catch(InterruptedException e){ e.printStackTrace(); } } MethodRequest methodRequest = methodQueue.removeFirst(); this.notifyAll(); return methodRequest; } } ScheduleThread
public class ScheduleThread extends Thread{ private final ActivationQueue queue; public ScheduleThread(ActivationQueue queue){ this.queue = queue; } public void invoke(MethodRequest request){ this.queue.put(request); } @Override public void run(){ while(true){ this.queue.get().execute(); } } }
ActiveObjectProxy
public class ActiveObjectProxy implements ActivaObject{ private final ScheduleThread scheduleThread; private final Servant servant; public ActiveObjectProxy(ScheduleThread scheduleThread,Servant servant){ this.scheduleThread = scheduleThread; this.servant = servant; } @Override public Result makeString(int count,char fillChar){ FutureResult future = new FutureResult(); schedulerThread.invoke(new MakeStringRequest(servant,future,count,fillChar)); return future; } @Override punlic void displayString(String text){ schedulerThread.invoke(new DisplayStringRequest(servant,text)); } }
ActivaObjectFactory
public final class AcitveObjectFactory{ private ActiveObjectFactory(){} public static ActiveObject getInstance(){ Servant servant = new Servant(); ActivationQueue queue = new ActivationQueue(); ScheduleThread scheduleThread = new ScheduleThread(queue); ActiveObjectProxy proxy = new ActiveObjectProxy(servant,scheduleThread) scheduleThread.start(); return proxy; } }
DisplayClient
public class DisplayClient extends Thread{ private final ActiveObject activeObject; public DisplayClient(String name,ActiveObject activeObject){ super(name); this.activeObject = activeObject; } @Override public void run(){ try{ for(int i=0;true;i++){ String text = Thread.currentThread().getName() + "-" + i; activeObject.displayString(text); Thread.sleep(500L); } }catch(InterruptedException e){ e.printStackTrace(); } } }
MakeStringClient
public class MakeStringClient extends Thread{ private final ActiveObject activeObject; private final char fillChar; public MakeStringClient(ActiveObject activeObject,String name){ super(name); this.activeObject = activeObject; this.fillChar = name.charAt(0); } @Override public void run(){ try{ for(int i=0;true;i++){ Result result = activeObject.makeString(i+1,fillChar); Thread.sleep(500L); String resultValue = result.getResultValue(); System.out.println(Thread.currentThread().getName() +"-"+ resultValue) } }catch(InterruptedException e){ e.printStackTrace(); } } }
Test
public static void main(String[] args){ //System.gc(); ActiveObject activeObject = ActiveObjectFactory.getInstance(); new MakeStringClient(activeObject,"A1").start(); new MakeStringClient(activeObject,"B1").start(); new DisplayClient("aaa",activeObject).start(); }