多线程系列之二:Single Thread Execution 模式

Posted inspred

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程系列之二: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角色的锁的顺序并不固定

六,临界区大小和性能
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类。 可以用来控制并发数量

Semaphore类的acquire方法用于确保存在可用资源

Semaphore类的release方法用于释放资源。

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个线程同时访问




























































以上是关于多线程系列之二:Single Thread Execution 模式的主要内容,如果未能解决你的问题,请参考以下文章

Android多线程分析之二:Thread的实现

java多线程同步以及线程间通信详解&消费者生产者模式&死锁&Thread.join()(多线程编程之二)

Java并发编程系列之二线程基础

Qt系列文章之二十八(基于QThread多线程概述)

Qt系列文章之二十八(基于QThread多线程概述)

JVM故障分析系列之四:jstack生成的Thread Dump日志线程状态