java多线程之Single Threaded Execution模式

一、简介

所谓Single Threaded Execution模式,就是指“以一个线程执行”,就像一座独木桥同一时间内只允许一个人通过一样,该模式用于设置限制,以确保同一时间内只能让一个线程执行处理。

二、实例(无Single Threaded Execution)

首先,我们来看一下应该使用Single Threaded Execution模式却没有使用的程序,体会一下在多线程下无法正确执行的程序会引发什么现象。

  • 一次只允许一个人通过的门。
类名 说明
Main.java 创建门,并让三个人不断地通过的类
Gate.java 表示门的类,它会在人通过时记录其姓名和出生地
UserThread.java 表示人的类,人们将不断地通过门

1.Gate类,代码如下:

package com.viagra.Single_Threaded_Execution.Not_Haved;

/**
 * @Auther: viagra
 * @Date: 2019/11/19 11:11
 * @Description:
 */
public class Gate {
    protected int conuter = 0;
    protected String name = "Nobody";
    protected String address = "Nowhere";

    /**
     * 非安全线程
     * @param name
     * @param address
     */
    public void pass(String name, String address) {
        this.conuter++;
        this.name = name;
        this.address = address;
        check();
    }

    /**
     * toString返回
     * @return
     */
    public String toString() {
        return "No." + conuter + ": " + name + "," + address;
    }

    /**
     * 检查name和adress的首字母是否项目,不相同则为异常数据
     */
    private void check(){
        if(name.charAt(0) != address.charAt(0)){
            System.out.println("******* 不一致 ********:"+toString());
        }
    }
}

2.UserThread类,代码如下:

package com.viagra.Single_Threaded_Execution.Not_Haved;

/**
 * @Auther: viagra
 * @Date: 2019/11/19 11:13
 * @Description:
 */
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) {
        super();
        this.gate = gate;
        this.myname = myname;
        this.myaddress = myaddress;
    }

    public void run() {
        System.out.println(myname + " 开始:");
        while (true) {
            gate.pass(myname, myaddress);
        }
    }
}

3.Main类,三名通行者分别如下:

  • Steve Nash,出生于San Francisco;
  • Michael Jordan,出生于Miami;
  • Ronaldo,出生于Rome。

代码如下:

package com.viagra.Single_Threaded_Execution.Not_Haved;

/**
 * @Auther: viagra
 * @Date: 2019/11/19 11:14
 * @Description:
 */
public class Main {
    public static void main(String[] args) {
        System.out.println("测试Single Threaded Execution-Gate,hit CTRL+C to exit.");
        Gate gate = new Gate();
        new UserThread(gate, "Steve Nash", "San Francisco").start();
        new UserThread(gate, "Michael Jordan", "Miami").start();
        new UserThread(gate, "Ronaldo", "Rome").start();
    }
}

4.测试,运行Main中的主类,运行结果:*(出错了)*

测试Single Threaded Execution-Gate,hit CTRL+C to exit.Steve Nash 开始:Ronaldo 开始:Michael Jordan 开始:******* 不一致 ********:No.193689: Michael Jordan,Miami******* 不一致 ********:No.194489: Michael Jordan,Miami******* 不一致 ********:No.194959: Michael Jordan,Miami.........******* 不一致 ********:No.3190046: Ronaldo,Miami******* 不一致 ********:No.3190492: Ronaldo,Rome******* 不一致 ********:No.3191505: Michael Jordan,Miami******* 不一致 ********:No.3194400: Michael Jordan,Rome******* 不一致 ********:No.3194951: Michael Jordan,Miami******* 不一致 ********:No.3196153: Ronaldo,Miami******* 不一致 ********:No.3196968: Michael Jordan,Miami******* 不一致 ********:No.3198653: Michael Jordan,Rome

说明:在程序执行到193689行的时候(),******* 不一致 ********的标识就打印出来了,原因是因为Gate类是非线程安全的。

5.错误原因:

在执行程序的时候,pass()方法会被多个线程执行,pass方法包含下面4个语句:

  1. this.conuter++;
  2. this.name = name;
  3. this.address = address;
  4. check();

对于name和address来说,几个线程会分别相互操作,获胜的一个线程会先写入值,而此时,对于name和address的值而言,其结果就不得而知了。

三、实例(有Single Threaded Execution)

我们将Gate类修改为线程安全的类,Main和UserThread方法不用做修改,使用synchronized关键字进行修饰。

1.Gate类,代码如下:

package com.viagra.Single_Threaded_Execution.Haved.Lesson1;

/**
 * @Auther: viagra
 * @Date: 2019/11/19 11:18
 * @Description:
 */
public class Gate {
    private int conuter = 0;
    private String name = "Nobody";
    private String address = "Nowhere";

    /**
     * 加上synchronized
     * @param name
     * @param address
     */
    public synchronized void pass(String name, String address){
        this.conuter++;
        this.name = name;
        this.address = address;
        check();
    }

    /**
     * 加上synchronized
     * toString返回
     * @return
     */
    public synchronized String toString() {
        return "No." + conuter + ": " + name + "," + address;
    }

    /**
     * 检查name和adress的首字母是否项目,不相同则为异常数据
     */
    private void check(){
        if(name.charAt(0) != address.charAt(0)){
            System.out.println("******* 不一致 ********:"+toString());
        }
    }
}

2.运行程序,结果如下:

测试Single Threaded Execution-Gate,hit CTRL+C to exit.
Steve Nash 开始:
Ronaldo 开始:
Michael Jordan 开始:

无论等多久,都不会显示“******* 不一致 ********”的信息,说明通过加上synchronized后,方法的安全性会很高。

3.synchronized的作用

synchronized方法能够确保方法同时只能有一个线程执行,也就是说,在线程Steve Nash执行pass方法的时候,线程Michael Jordan和Ronaldo就无法再执行pass方法,只能等线程Steve Nash执行完pass方法后,释放锁以后,其他的线程才能开始执行pass方法。

四、Single Threaded Execution模式中的登场角色

  • SharedResource(共享资源)

在上面的实例中,由Gate扮演这SharedResource角色。SharedResource角色是可被多个线程访问的类,包含的方法有很多,但这些方法主要分为以下二类:

  • safeMethod:安全的,多个线程同时调用也不会发生问题。

  • unsafeMethod:非安全的,多个线程同时调用会发生问题。

java多线程之Single Threaded Execution模式_第1张图片

  • 何时使用
  1. 多线程时;
  2. 多个线程访问时;
  3. 状态有可能发生变化时;
  4. 需要确保安全时。
  • 临界区的大小和性能

一般情况下,Single Threaded Execution模式会降低程序性能,原因有二个:

  1. *获取锁花费时间;*
  2. *线程冲突引起的等待。*

五、关于synchronized

不管是synchronized方法,还是synchronized代码块,都是由“{”,“}”括起来的代码块。

synchronized void method(){	...}
synchronized (obj){	...}

无论是哪种,都可以看作在“{”处获取锁,在“}”处释放锁。下面我们比较一下使用synchronized的代码与显示处理锁的代码,假设存在一个获取锁的lock方法和一个释放锁的unlock方法。

void method(){	lock();	...	unlock();}

是不是认为“这虽然看起来有点复杂,但和使用synchronized的代码也没有什么大的区别”?其实区别很大,如果在lock方法和unlock方法之间存在return,那么锁就无法被释放。

不仅return的时候锁无法被释放,在遇到异常处理的时候,锁依旧无法被释放。如果doMethod抛出异常时,锁无法被释放。

void method(){	lock();	doMethod();	unlock();}

如果想让一个防范能够同时成对执行lock()和unlock(),那么可以使用finally来实现:

void method() {
        lock();
        try {
        } catch (Exception e) {		...} finally {
            unlock();
        }
    }

不管是return还是异常处理,还是其他操作,finally部分都会被调用,这是Java规范。

  • synchronized在保护什么?

synchronized就像是门上的锁,当看到门上的锁时,我们还应该确认其他的门和窗户是不是都锁好了,只要是访问多个线程共享的字段方法,就需要使用synchronized进行保护。

线程要执行synchronized的实例方法,必须获取this的锁,而能够获得一个实例的说的线程只有一个,正是因为这种唯一性,才能够使synchronized来实现Single Threaded Execution模式。

六、java.util.concurrent包和计数信号量

1.计数信号量和Semaphore类

Single Threaded Execution模式用户确保某个区域“只能由一个线程”执行,下面通过这种模式扩展,以确保某个区域“最多只能由N个线程”执行,就要用计数信号量来控制线程量。

2.实例,代码如下:

//使用Semaphore类的示例程序
public class SemaphoreMain {
    public static void main(String[] args) {
        //设置3个资源
        BoundedResource boundedResource = new BoundedResource(3);
        //10个线程调用资源
        for (int i = 0; i < 10; i++) {
            new UserThread(boundedResource).start();
        }
    }
 
}
 
//日志
class Log {
    public static void println(String s) {
        System.out.println(Thread.currentThread().getName() + "==>:" + s);
    }
}
 
//资源个数有限
class BoundedResource {
    private final Semaphore semaphore;
    private final int permits;
    private final static Random random = new Random(314159);
 
    public BoundedResource(int permits) {
        this.semaphore = new Semaphore(permits);
        this.permits = permits;
    }
 
    //使用资源
    public void use() throws InterruptedException {
        semaphore.acquire();
        try {
            doUse();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            semaphore.release();
        }
    }
 
    //实际使用资源
    protected void doUse() throws InterruptedException {
        Log.println("BEGIN: used = " + (permits - semaphore.availablePermits()));
        Thread.sleep(random.nextInt(500));
        Log.println("END: used = " + (permits - semaphore.availablePermits()));
    }
}
 
class UserThread extends Thread {
    private final static Random random = new Random(26535);
    private final BoundedResource boundedResource;
 
    public UserThread(BoundedResource boundedResource) {
        this.boundedResource = boundedResource;
    }
 
    public void run() {
        try {
            while (true) {
                boundedResource.use();
                Thread.sleep(random.nextInt(3000));
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

3.执行结果:

Thread-0==>:BEGIN: used = 1Thread-1==>:BEGIN: used = 3Thread-0==>:END: used = 3Thread-3==>:BEGIN: used = 3Thread-3==>:END: used = 3Thread-5==>:BEGIN: used = 3Thread-1==>:END: used = 3Thread-6==>:BEGIN: used = 3Thread-5==>:END: used = 3Thread-4==>:BEGIN: used = 3Thread-4==>:END: used = 3Thread-8==>:BEGIN: used = 3Thread-7==>:END: used = 3Thread-2==>:BEGIN: used = 3Thread-6==>:END: used = 3Thread-9==>:BEGIN: used = 3Thread-2==>:END: used = 3Thread-9==>:END: used = 2

七、Lock类

在上面的实例程序中,不在Gate类中使用synchronized,该如果实现Single Threaded Execution模式?

其实可以是使用Mutes类,像Mutes类这样执行互斥处理的机制通常成为Mutex。

代码如下:

package com.viagra.Single_Threaded_Execution.Haved.Lesson3;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @Auther: viagra
 * @Date: 2019/11/19 12:26
 * @Description:
 */
public class Gate {
    private int conuter = 0;
    private String name = "Nobody";
    private String address = "Nowhere";
    private final ReentrantLock mutex = new ReentrantLock();;

    /**
     * 加上Mutex
     * @param name
     * @param address
     */
    public void pass(String name, String address){
        mutex.lock();
        try{
            this.conuter++;
            this.name = name;
            this.address = address;
            check();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            mutex.unlock();
        }
    }


    /**
     * 加上Mutex
     * toString返回
     * @return
     */
    public String toString() {
        String s = null;
        mutex.lock();
        try{
            s = "No." + conuter + ": " + name + "," + address;
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            mutex.unlock();
        }
        return s;
    }

    /**
     * 检查name和adress的首字母是否项目,不相同则为异常数据
     */
    private void check(){
        if(name.charAt(0) != address.charAt(0)){
            System.out.println("******* 不一致 ********:"+toString());
        }
    }
}

执行结果:()

测试Single Threaded Execution-Gate,hit CTRL+C to exit.Michael Jordan 开始:Steve Nash 开始:Ronaldo 开始:

代码案例

你可能感兴趣的:(java,并发模型,多线程)