zoukankan      html  css  js  c++  java
  • 《图解Java多线程设计模式》之二:Single Thread Execution 模式

    一,什么是SingleThreadExecution模式?
    同一时间内只能让一个线程执行处理

    二,例子

    1.不安全的情况

    用程序模拟 三个人频繁地通过一个只允许一个人经过的门。当人通过时,统计人数便会增加,并记录通行者的姓名和地址

    门:

    public class Gate {
    
        private int counter = 0;
        private String name = "nobody";
        private String address = "nowhere";
        public void pass(String name ,String address){
            this.counter++;
            this.name = name;
            this.address = address;
            check();
        }
    
        public String toString(){
            return "NO."+counter+":"+name+", "+address;
        }
    
        private void check() {
            if (name.charAt(0) != address.charAt(0)){
                System.out.println("*****borken******"+toString());
            }
        }
    
    }

    通行者:

    public class UserThread extends Thread{
        private final Gate gate;
        private final String myName;
        private final String myAddress;
    
        public UserThread(Gate gate, String myName,String myAddress){
            this.gate = gate;
            this.myName = myName;
            this.myAddress = myAddress;
        }
    
        @Override
        public void run() {
            System.out.println(myName+" is ready....");
         //频繁通过门
    while (true){ gate.pass(myName,myAddress); } } }

    创建三个通过门的人

    public class Test {
    
        public static void main(String[] args) {
            Gate gate = new Gate();
            new UserThread(gate,"aaa","aa").start();
            new UserThread(gate,"bbb","bb").start();
            new UserThread(gate,"ccc","cc").start();
        }
    }

    运行结果:

    ccc is ready....
    *****borken******NO.4717:aaa, bb
    分析:

    当人通过门时,会记录人的名字和人的地址。但从这行结果看,这和我们预期的结果不一样。

    2.安全的例子

    上面的问题就出现在同一时刻不只有一个人通过(可能多个人同时通过这个门),导致记录人的名字和地址时就会出现混乱。如何解决呢? 其实,我们只要确保某一时刻只能有一个人通过这个门,问题就解决了。我们可以考虑使用Single Thread Execution模式。

    线程安全的门:

    public class SafeGate {
        private int counter = 0;
        private String name = "nobody";
        private String address = "nowhere";
        public synchronized void pass(String name ,String address){
            this.counter++;
            this.name = name;
            this.address = address;
            check();
        }
    
        public synchronized String toString(){
            return "NO."+counter+":"+name+", "+address;
        }
    
        private void check() {
            if (name.charAt(0) != address.charAt(0)){
                System.out.println("*****borken******"+toString());
            }
        }
    }

    其他的不用改变。

    运行结果:

    aaa is ready....
    bbb is ready....
    ccc is ready....

    3.synchronized的作用
    synchronized方法能够确保该方法同时只能由一个线程执行

    三,SingleThreadExecution模式中的登场角色
    SharedResource资源:可以被多个线程访问的类,包含很多方法,分为两类
    安全方法:多个线程同时访问也没有关系
    不安全方法:多个线程访问出现问题,必须加以保护
    SingleThreadExecution模式会保护不安全的方法,使其同时只能由一个线程访问
    临界区:只允许单个线程执行的程序范围

    四,什么时候使用SingleThreadExecution模式?
    1.多线程时
    2.多个线程访问时:当ShareResource角色的实例有可能被多个线程同时访问时
    3.状态有可能发生变化时:ShareResource角色的状态发生变化
    4.需要确保安全性时:

    五,生存性与死锁
    1.在使用SingleThreadExecution模式时,会存在发生死锁的危险
    2.死锁是指两个线程分别持有着锁,并相互等待对方释放锁的现象。
    3.发生死锁条件:
    存在多个SharedResource角色
    线程在持有某个SharedResource角色的锁的同时,还去获取其他SharedResource角色的锁
    获取SharedResource角色的锁的顺序并不固定

    解决:只要破坏上面条件中的一个,就可以防止死锁发生了。

    4.死锁的例子:

    比如,有两个人A和B一起吃一份意大利面,但是桌子上只有一把勺子和一把叉子。吃面必须要同时用勺子和叉子。

    假如A先拿到了勺子,而这时B拿到了叉子。这是会出现什么情况?

    拿到勺子的A一直等着B放下叉子

    拿到叉子的B一直等着A放下勺子

    而他们会一直僵持下去,对应到程序中,就是两个线程分别持有锁,并等待对方释放锁。导致程序无法运行,这就是死锁

    5.用代码来实现:

    /**
     * 用餐工具
     */
    public class Tool {
    
        private final String name;
        public Tool(String name){
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "["+name+"]";
        }
    }
    public class EaterThread extends Thread {
        private String name;
        private final Tool lefthand;
    
        private final Tool righthand;
    
        public EaterThread(String name,Tool lefthand,Tool right){
            this.name = name;
            this.lefthand = lefthand;
            this.righthand = right;
        }
    
        @Override
        public void run() {
            while (true){
                eat();
            }
        }
    
        public void eat(){
            synchronized (lefthand){
                System.out.println(name+" takes up "+lefthand+" (left).");
                synchronized (righthand){
                    System.out.println(name+ " takes up "+righthand +" (right).");
                    System.out.println(name+ " is eating now.....");
                    System.out.println(name +"puts down "+righthand+" (right).");
                }
                System.out.println(name+ "puts down "+lefthand+" (left).");
            }
        }
    }
    public class Test {
        public static void main(String[] args) {
            System.out.println("test.....");
            //创建吃饭的工具
            Tool spoon = new Tool("Spoon");
            Tool fork = new Tool("Fork");
            //两个人抢夺工具吃饭
            new EaterThread("Alice",spoon,fork).start();
            new EaterThread("Bobby",fork,spoon).start();
        }
    }

    运行结果: 两个人分别持有锁,互相等待对方释放锁

    Bobby takes up [Fork] (left).
    Alice takes up [Spoon] (left).

    6.解决:

    方法一:A和B以相同的顺序拿餐具

    public class Test {
        public static void main(String[] args) {
            System.out.println("test.....");
            //创建吃饭的工具
            Tool spoon = new Tool("Spoon");
            Tool fork = new Tool("Fork");
            //两个人抢夺工具吃饭,相同的顺序拿餐具
            new EaterThread("Alice",spoon,fork).start();
            new EaterThread("Bobby",spoon,fork).start();
        }
    }

    方法二: 勺子和叉子成对拿取,那就只用一把锁就可以了。

    public class Tool {
    
        private final String name;
        public Tool(String name){
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "["+name+"]";
        }
    }
    public class Pair {
        private final Tool lefthand;
        private final Tool righthand;
        public Pair(Tool lefthand, Tool righthand) {
            this.lefthand = lefthand;
            this.righthand = righthand;
        }
    
        @Override
        public String toString() {
            return "Pair{" +
                    "lefthand=" + lefthand +
                    ", righthand=" + righthand +
                    '}';
        }
    }
    public class EaterThread extends Thread {
        
        private String name;
        private final Pair pair;
    
        public EaterThread(String name,Pair pair){
            this.name = name;
            this.pair = pair;
    
        }
        @Override
        public void run() {
            while (true){
                eat();
            }
        }
        public void eat(){
            synchronized (pair){
                System.out.println(name+" takes up "+pair+" .");
                System.out.println(name+ " is eating now.....");
    
                System.out.println(name+ "puts down "+pair+" .");
            }
        }
    }
    public class Test {
        public static void main(String[] args) {
            System.out.println("test.....");
            //创建吃饭的工具
            Tool spoon = new Tool("Spoon");
            Tool fork = new Tool("Fork");
            Pair pair = new Pair(spoon,fork);
            //两个人抢夺工具吃饭
            new EaterThread("Alice",pair).start();
            new EaterThread("Bobby",pair).start();
        }
    }

    六,临界区大小和性能
    SingleThreadExecution模式降低性能的原因:
    1.获取锁花费时间
    进入synchronized方法时,线程需要获取对象的锁,这个处理花费时间。若SharedResource角色的数量减少了,那么获取锁的数量减少,花费时间就会减少
    2.线程冲突引起的等待
    当线程A执行临界区的代码时,其他线程想要进入临界区的线程会阻塞,这称之为线程冲突。若尽可能缩小临界区的范围,可以减少线程冲突的概率。

    七,Before/After模式
    1.synchronized语法
    synchronized(this){
        ....
    }
    上面的语法可以看作在 { 处获取锁,在 } 处释放锁。
    2.显示处理锁
    void method(){
        lock();//加锁
         ...
        unlock();//解锁
    }

    缺点:如果在lock方法和unlock方法之间存在return,那么锁就无法释放了。当lock和unlock之间抛出异常,锁也无法释放。而synchronized无论是return
        还是抛出异常,都一定能够释放锁。
    3.解决:
    我们想在调用lock()后,无论执行什么操作,unlock()都会被调用,我们可以使用finally来处理
    void method(){
        lock();
        try{
            ...
        }finally{
            unlock();
        }
    }

    finally的这种用法是Before/After模式(事前/事后模式)的实现方法之一。

    八,思考
    使用synchronized时思考:
    synchronized在保护什么
    其他地方也妥善保护了吗
    以什么单位保护
    使用哪个锁保护
    2.原子操作
    从多线程观点来看,这个synchronized方法执行的操作是不可分割的操作,可以看成是原子操作

    九,计数信号量和Semaphore类

    Single Thread Execution模式用于确保某个区域只能由一个线程执行。如果我们想让某个区域最多能由N个线程执行,该如何实现?

    java.util.concurrent包提供了表示计数信号量的Semaphore类。 可以用来控制并发数量

    public class SemaphoreTest {
        public static void main(String[] args) {
            ExecutorService executorService = Executors.newCachedThreadPool();
            final Semaphore sp = new Semaphore(3);
            for (int i = 0; i < 10; i++) {
                Runnable runnable = new Runnable() {
                    @Override
                    public void run() {
                        try {
                            //拿信号灯
                            sp.acquire();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("线程"+Thread.currentThread().getName()+
                        "进入,当前已经有"+(3-sp.availablePermits())+"个并发");
                        try {
                            Thread.sleep((long) (Math.random()*10000));
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //处理业务
                        try {
                            System.out.println("处理业务中.......");
    
                        }finally {
                            System.out.println("线程"+Thread.currentThread().getName()+"即将离开");
                            //释放信号灯
                            sp.release();
                        }
                        System.out.println("线程"+Thread.currentThread().getName()+
                                "已离开,当前有"+(3-sp.availablePermits()+"个并发"));
                    }
                };
                executorService.execute(runnable);
            }
        }
    }

    Semaphore sp = new Semaphore(1);只有一个信号灯时,作用相当于synchronized
    
    
    sp.acquiresp.release中间的代码最多允许N个线程同时访问
     


    十,Mutex

    Mutex是Mutual Exclusion(互斥)的缩写,使用Mutex类可以完成synchronized的功能。像Mutex类这样执行互斥处理的机制称为Mutex。

     1.自己实现一个Mutex类

    public class UserThread extends Thread{
        
        private final MutexGate gate;
        private final String myName;
        private final String myAddress;
    
    
            public UserThread(MutexGate gate, String myName,String myAddress){
            this.gate = gate;
            this.myName = myName;
            this.myAddress = myAddress;
        }
    
    
        @Override
        public void run() {
            System.out.println(myName+" is ready....");
            while (true){
                gate.pass(myName,myAddress);
            }
        }
    }
    public class MutexGate {
    
        private int counter =0;
        private String name = "Nobody";
        private String adress = "Nowhere";
        private final Mutex mutex = new Mutex();
    
        public void pass(String name,String address){
            mutex.lock();
            try {
                this.counter++;
                this.name = name;
                this.adress = address;
    
            }finally {
                mutex.unlock();
            }
        }
    
        public String toString(){
            String s = null;
            mutex.lock();
            try {
                s = "NO."+counter+": "+name+", "+adress;
            }finally {
                mutex.unlock();
            }
            return s;
        }
    
        public void check(){
            if(name.charAt(0)!= adress.charAt(0)){
                System.out.println("*******broken+++++"+toString());
            }
        }
    
    }
    public final class Mutex {
    
        private boolean busy = false;
        public synchronized void lock(){
            while (busy){
                try {
                    wait();
                }catch (InterruptedException e){
    
                }
            }
        }
    
        public synchronized void unlock(){
            busy = false;
            notifyAll();
        }
    }
    public class Test {
    
        public static void main(String[] args) {
           
            MutexGate gate = new MutexGate();
            new UserThread(gate,"aaa","aa").start();
            new UserThread(gate,"bbb","bb").start();
            new UserThread(gate,"ccc","cc").start();
        }
    }

     2.问题:

    从Mutex类中可以分析:

    (1,一个线程不能重入

    假如某一个线程连续调用两次lock方法,当第二次调用时,由于busy字段已经变为true,所以会执行wait。相当于自己把自己所在了外面

    (2,任何人都可以unlock

    即使线程自己没有调用lock方法,也能调用unlock方法。相当于不是自己上的锁,自己也可以打开一样

    3.改良Mutex

    public final class MutexPlus {
        private long locks = 0;//记录当前锁的个数 (锁的个数 = lock的调用次数 - unlock的调用次数)
        private Thread owner = null;//记录当前线程 (把调用lock方法的线程赋值给owner)
        public synchronized void lock(){
            Thread me = Thread.currentThread();
            while (locks>0 && owner != me){
                try {
                    wait();
                }catch (InterruptedException e){
                    
                }
            }
            assert locks ==0 || owner ==me;//断言,显式地表达此处肯定可以成立的条件
            owner = me;
            locks++;
        }
        
        public synchronized void unlock(){
            Thread me = Thread.currentThread();
            if (locks ==0 || owner != me){
                return;
            }
            assert locks > 0 || owner == me;
            locks--;
            if (locks == 0){
                owner =null;
                notifyAll();
            }
        }
        
    }

    4.使用java并法包提供的类

    Lock lock = new ReentrantLock();
            lock.lock();
            //......
            lock.unlock();




  • 相关阅读:
    .NET Core采用的全新配置系统[4]: “Options模式”下各种类型的Options对象是如何绑定的?
    .NET Core采用的全新配置系统[3]: “Options模式”下的配置是如何绑定为Options对象
    .NET Core采用的全新配置系统[2]: 配置模型设计详解
    .NET Core采用的全新配置系统[1]: 读取配置数据
    ASP.NET Core 运行原理解剖[5]:Authentication
    kubernetes-dashboard(1.8.3)部署与踩坑
    使用kubeadm搭建Kubernetes(1.10.2)集群(国内环境)
    Docker初体验
    ASP.NET Core Logging in Elasticsearch with Kibana
    ASP.NET Core 认证与授权[7]:动态授权
  • 原文地址:https://www.cnblogs.com/inspred/p/9373922.html
Copyright © 2011-2022 走看看