并发编程(二):多线程设计模式

二. 并发编程设计模式

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方式解决了懒汉式的效率问题,只有第一次实例化的时候会同步执行,后续的读操作都可以并发操作
  • 缺点:

    1. 会引起空指针异常----由于程序的重排序引起

      1. 因为在第一个线程进来创建instance实例,先在堆内存开辟了一块内存区域,但是分为加载,连接,初始化三个阶段,可能在连接阶段的解析过程中还未对实例进行构造方法的初始化时,当里面的属性并没有构造完实例的属性还是null时就把instance创建了
      2. 当第二个线程进来时,判断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方式

    1. 可以保证懒加载机制
    2. 可以保证线程安全
    3. 可以保证高效率
    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中只会被加载一次,严格保证执行的顺序
  • 静态内部类和非静态内部类一样,都不会因为外部类的加载而加载,同时静态内部类的加载不需要依附外部类,只有在调用时才会主动加载

    1. 静态变量保证线程安全,并只加载一次
    2. 内部类保证了懒加载机制

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();
    }
}
  1. 枚举是线程安全的,且构造方法是私有的只会装载一次
  2. 枚举值默认是静态类常量(static),在类加载时会初始化静态常量,会为每个类常量调用一次构造函数
  3. 这里是内部枚举类,所以只会在调用的时候才会主动加载
  4. 所以枚举和内部类其实是一种机制

    1. 静态变量保证线程安全,并只加载一次
    2. 内部类保证了懒加载机制

2.2 多线程的休息室WaitSet

  • 所有对象都会有一个waitSet用来存放调用了该对象wait()方法进入block的线程

    1. 线程被notify()方法唤醒后,不一定立即执行,而是需要继续抢夺CPU的执行权,也就是锁

      1. 线程从waitSet中被唤醒的顺序不一定是FIFO
    2. wait() 调用者是Lock对象,是释放锁的;sleep() 调用者是线程自己,但不释放锁
    3. 线程被唤醒后,获取锁后仍然从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缓存不一致的问题

    1. 由于CPU的速度远大于内存的速度,所以CPU与内存中间存在着缓存用来提高执行的效率
    2. 且由于该线程只有读的操作,java做了优化,所以cache中的数据不会去同步内存中的数据
    3. 而多线程的操作都是对于当于当前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后引发了数据不一致的问题

    1. 在运算过程中,cache会将数据从主存中复制一份,CPU计算时就直接去cache中获取数据,再将cache中的数据刷新到主内存中去(但是当CPU对缓存的数据只有读的时候,cache的数据不会立即刷新到主存中)
    2. 解决方案

      1. 给数据总线加锁

        • 每次只有一个CPU可以对数据进行操作
      2. intel的高速缓存一致性协议(MESI)

        • 当CPU写入数据的时候,如果该变量被共享(也就是说,在其他CPU中也存在该变量的副本),就会发出一个信号,通知其他CPU该变量的缓存无效,需要重新到主内存获取
  • synchronized和volatile关键字

    1. synchronized关键字是解决多个线程写操作之间原子性的问题
    2. volatile关键字是解决读操作中缓存数据与主存数据不一致,线程之间不可见的问题

2.3.4 并发编程的三大特性

  1. 并发编程的三大特性

    1. 原子性:是指一个操作或多个操作是不可中断的,即使是多个线程一起执行的时候,一个操作或多个操作一旦开始就不会被其他线程干扰
    2. 可见性:是指当一个线程修改了某一共享变量,其他线程是否能够立即知道这个修改(对串行来说,可见性问题是不存在的)
    3. 有序性:在并发时候,程序的执行可能会出现乱序,给人的直观感觉就是:写在前面的代码会在后面执行,有序性问题的原因就是程序在执行过程中会进行指令重排序,只要保证直接结果一致即可,即最终一致性
  2. volatile关键字:一旦一个共享变量被volatile修饰,具备两层语义,保证了不同线程间的可见性

    1. 强制对缓存的修改操作立即写入主存
    2. 当某个CPU修改其缓存的共享变量时,其他线程中该共享变量将会失效,需要从主存中读取最新的数据
  3. 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 读写锁分离锁模式

  • 读写分离:允许并行化读操作

    1. read read : 并行化
    2. read write : 串行化
    3. 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是线程不安全的,多线程操作要加锁
  • 不可变对象原则

    1. immutable对象的状态在创建之后就不能发生改变,任何对它的改变都应该产生一个新的对象
    2. immutable类的所有的属性都应该是final的
    3. 对象必须被正确的创建,比如:对象引用在对象创建过程中不能泄露(leak)
    4. 对象应该是final的,以此来限制子类继承父类,以避免子类改变了父类的immutable的特性
    5. 不提供对成员的改变方法,比如setXxx
    6. 确保所有的方法不会被重载,手段有两种

      1. 使用final Class(强不可变类)
      2. 将所有类方法加上final(弱不可变类),
    7. 如果某一个类成员不是原始变量(primitive)或者不可变类,必须通过深克隆方法,来确保类的不可变

      1. 比如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),然后去做其他的事

      1. 等自己事情忙完再过来凭据拿蛋糕----轮询结果
      2. 蛋糕做完就直接送到指定地址-----异步回调
  • 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 单例模式和静态方法

  1. 单例模式和静态方法的区别

    1. 静态方法性能更好,在编译期就已经绑定好
    2. 单例模式可以延迟初始化,如果需要加载比较重的对象,用单例模式会更好
    3. 单例模式可以被继承,方法可以被重写,而静态方法不能
    4. 单例模式适合存状态信息需要改变的需求,静态方法适合仅仅提供全局方法的需求
  2. 静态方法

    1. 在java中静态方法可以被继承,但是不能被重写
    2. 如果子类中也含有一个返回类型,方法名,参数列表与之相同的静态方法,那么该子类实际上是将父类的同名方法进行隐藏,而非重写
    3. 父类引用指向子类对象时,只会调用父类的静态方法,所以他们的行为也并不具有多态性

      • 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模式

  • 我们把线程平常处理的状态称为"作业中",当我们希望结束这个线程的时候,则发出"终止请求",接着这个线程不会马上结束,需要进行必须的刷新工作,这个被称为"终止处理中"

    1. 从"作业中"变成"终止处理中"是第一个阶段
    2. "终止操作中"是第二个阶段

      • "终止处理中"的状态不会进行平常的操作,虽然线程还在进行,但是进行的是终止处理,直到终止处理结束后,才真正结束线程
  • 开始 ---> 作业中 ---> [终止要求] ---> 终止处理中 ----> 结束

    • 也就是说第一阶段执行终止方法后还会继续执行终止处理的操作,直到第二阶段的处理操作结束才算真正的结束,而第二阶段的操作一般放在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模式

  1. Active Object

    • 对象分为主动对象和被动对象,主动对象内部包含一个线程,可以自动完成动作或改变状态,而一般的被动对象只能通过其他对象的调用才能有所作为
    • 在多线程程序中,经常把一个线程封装到主动对象里面
  2. 特点

    1. 主动对象是内部拥有自己的控制线程的对象,为了简化异步调用的复杂性,这个模式分离了方法的执行和调用
    2. 使用这个模式,一个对象中无论是否有独立的线程,客户从外部访问它时,感觉时一样的
    3. 主动对象模式隔离的方法执行和方法调用的过程,提高了并行性,对内部拥有控制线程的主动对象,降低了异步访问的复杂性
  3. 好处

    1. 增强了程序的并行性,降低了同步的复杂性,客户线程和异步调用操作并行执行,同步的复杂性由调度者独立处理
    2. 让多个耗时的操作并行执行,只要软件和硬件支持,可以让多个活动的对象彼此不干扰地同时运行
    3. 方法的执行和调用的顺序可以不一致,方法的调用是异步调用,而方法的执行决定如何调用
  4. 坏处

    1. 性能过多的消耗
    2. 增加调试的难度
  5. 思想

    1. 当我们main线程调用System.gc()时,其实是开辟了一个新的线程去执行gc()任务
    2. 当我们使用Runnable和Future去执行方法时,只能去执行单一的一个方法
    3. 当我们想异步的执行一系列的方法时,我们可以将方法的指令存入到队列中进行顺序异步执行
  • 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();
    }

你可能感兴趣的:(并发编程,java)