多线程系列之二: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.acquire和sp.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();
多线程系列之二:Single Thread Execution 模式的更多相关文章
- Single Thread Execution 能通过这座桥的只有一个人
		
直奔主题, Single Thread Execution也称作Critical Section(临界区),范例如下: public class SingleThreadGate { public s ...
 - Android多线程分析之二:Thread的实现
		
Android多线程分析之二:Thread的实现 罗朝辉 (http://www.cnblogs.com/kesalin/) CC 许可,转载请注明出处 在前文<Android多线程分析之一 ...
 - Java 设计模式系列(二十)状态模式
		
Java 设计模式系列(二十)状态模式 状态模式,又称状态对象模式(Pattern of Objects for States),状态模式是对象的行为模式.状态模式允许一个对象在其内部状态改变的时候改 ...
 - 多线程系列(二)之Thread类
		
在上一遍文章中讲到多线程基础,在此篇文章中我们来学习C#里面Thread类.Thread类是在.net framework1.0版本中推出的API.如果对线程的概念还不太清楚 的小伙伴请阅读我的上一遍 ...
 - C# 多线程系列(二)
		
传递数据给一个线程 通过函数或lambda表达式包一层进行传递. static void Main(string[] args) { Thread thread = new Thread(() =&g ...
 - 多线程:多线程设计模式(二):Future模式
		
一.什么是Future模型: 该模型是将异步请求和代理模式联合的模型产物.类似商品订单模型.见下图: 客户端发送一个长时间的请求,服务端不需等待该数据处理完成便立即返回一个伪造的代理数据(相当于商品订 ...
 - 多线程系列之十一:Two-Phase Termination模式
		
一,Two-Phase Termination模式 翻译过来就是:分两阶段终止 二,示例程序 public class CountupTread extends Thread { private lo ...
 - 多线程设计模式(二):Future模式
		
一.什么是Future模型: 该模型是将异步请求和代理模式联合的模型产物.类似商品订单模型.见下图: 客户端发送一个长时间的请求,服务端不需等待该数据处理完成便立即返回一个伪造的代理数据(相当于商品订 ...
 - 【Java多线程系列随笔二】BlockingQueue
		
前言: 在新增的Concurrent包中,BlockingQueue很好的解决了多线程中,如何高效安全“传输”数据的问题.通过这些高效并且线程安全的队列类,为我们快速搭建高质量的多线程程序带来极大的便 ...
 
随机推荐
- Kali 2.0 下 Metasploit 初始化配置
			
在kali 2.0中,命令行中直接输入msfconsole 提示不能连接到数据库 ,是由于postgresql 未启动.因此,需要开启postgresql,并且进行postgresql 的初始化配置. ...
 - Java strictfp
			
strictfp关键字 用于强制Java中的浮点计算(float或double)的精度符合IEEE 754标准. 不使用strictfp:浮点精度取决于目标平台的硬件,即CPU的浮点处理能力. 使用s ...
 - [福大软工] Z班 第4次成绩排行榜
			
作业要求 http://www.cnblogs.com/easteast/p/7511234.html 评分细则 (1)博客--15分,分数组成如下: 随笔开头,给出结队两个同学的学号.PS:结对成员 ...
 - python 基础操作--数据类型
			
一.变量 1.定义:将运算的中间结果暂存到内存,以便后续程序调用. 2.命名规则 1.变量由字母.数字.下划线搭配组合而成: 2.不可以用数字开头,也不能全都是数字: 3.不能是python 关键字, ...
 - 【大数据技术】HBase介绍
			
1.HBase简介1.1 Hbase是什么HBase是一种构建在HDFS之上的分布式.面向列.多版本.非关系型的数据库,是Google Bigtable 的开源实现. 在需要实时读写.随机访问超大规模 ...
 - centos7下kubernetes(5。部署kubernetes  dashboard)
			
基于WEB的dashboard,用户可以用kubernetes dashboard部署容器话的应用,监控应用的状态,执行故障排查任务以及管理kubernetes各种资源. 在kubernetes da ...
 - [matlab] 17.网格矩阵
			
生成网格矩阵,并且根据条件筛选,重新赋值为0,1二值图像 clear all;close all; %生成二值图 index= randperm(2500,1000); %生成10个不重复随机指标 Z ...
 - 微信硬件平台(七) 设备控制控制面板-网页sokect-mqtt长连接
			
给微信硬件设备添加我们自己的控制面板. 主要问题: 1 要保证长连接,这样面板可以实时交互阴间设备,http一次性的连接模式通信不行. 面板必须是网页化的,网页就可以操作交互.不用APP和小程序. 2 ...
 - PHP 3 函数
			
PHP 的真正力量来自它的函数:它拥有超过 1000 个内建的函数. PHP 用户定义函数 除了内建的 PHP 函数,我们可以创建我们自己的函数. 函数是可以在程序中重复使用的语句块. 页面加载时函数 ...
 - Linux如何查看端口状态
			
netstat命令各个参数说明如下: -t : 指明显示TCP端口 -u : 指明显示UDP端口 -l : 仅显示监听套接字(所谓套接字就是使应用程序能够读写与收发通讯协议(protocol)与资料的 ...