所谓Single Threaded Execution模式,就是指“以一个线程执行”,就像一座独木桥同一时间内只允许一个人通过一样,该模式用于设置限制,以确保同一时间内只能让一个线程执行处理。
首先,我们来看一下应该使用Single Threaded Execution模式却没有使用的程序,体会一下在多线程下无法正确执行的程序会引发什么现象。
类名 | 说明 |
---|---|
Main.java | 创建门,并让三个人不断地通过的类 |
Gate.java | 表示门的类,它会在人通过时记录其姓名和出生地 |
UserThread.java | 表示人的类,人们将不断地通过门 |
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());
}
}
}
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);
}
}
}
代码如下:
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();
}
}
测试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类是非线程安全的。
在执行程序的时候,pass()方法会被多个线程执行,pass方法包含下面4个语句:
对于name和address来说,几个线程会分别相互操作,获胜的一个线程会先写入值,而此时,对于name和address的值而言,其结果就不得而知了。
我们将Gate类修改为线程安全的类,Main和UserThread方法不用做修改,使用synchronized
关键字进行修饰。
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());
}
}
}
测试Single Threaded Execution-Gate,hit CTRL+C to exit.
Steve Nash 开始:
Ronaldo 开始:
Michael Jordan 开始:
无论等多久,都不会显示“******* 不一致 ********”的信息,说明通过加上synchronized后,方法的安全性会很高。
synchronized方法能够确保方法同时只能有一个线程执行,也就是说,在线程Steve Nash执行pass方法的时候,线程Michael Jordan和Ronaldo就无法再执行pass方法,只能等线程Steve Nash执行完pass方法后,释放锁以后,其他的线程才能开始执行pass方法。
在上面的实例中,由Gate扮演这SharedResource角色。SharedResource角色是可被多个线程访问的类,包含的方法有很多,但这些方法主要分为以下二类:
safeMethod:安全的,多个线程同时调用也不会发生问题。
unsafeMethod:非安全的,多个线程同时调用会发生问题。
一般情况下,Single Threaded Execution模式会降低程序性能,原因有二个:
不管是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的实例方法,必须获取this的锁,而能够获得一个实例的说的线程只有一个,正是因为这种唯一性,才能够使synchronized来实现Single Threaded Execution模式。
Single Threaded Execution模式用户确保某个区域“只能由一个线程”执行,下面通过这种模式扩展,以确保某个区域“最多只能由N个线程”执行,就要用计数信号量来控制线程量。
//使用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();
}
}
}
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
在上面的实例程序中,不在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 开始:
代码案例