JAVA多线程核心技术 1.2.3 非线程安全 解析
2017-07-31 12:59
232 查看
在看《JAVA多线程核心技术》 1.2.3线程安全 发现了一个有趣的例子,在这里和大家分享一下,同时对运行结果进行解析和扩展,帮助新手朋友理解。同时请大神朋友不吝赐教。
话不多说,先来撸两行代码
一、非线程安全
首先一个LoginServlet类,此类用于模拟用户登录。注意此类中的 变量和方法全部使用static关键字,代表变量和方法全都是静态的,java只会分配一个资源,多实例需要共享资源,这就是祸根所在,正式如此,引发了 非线程安全。
接着我们定义两个类,用于模拟用户登录
最后定义一个测试类
显然我们希望当 a 用户登录时,输出用户信息 username=a password=aa ,b用户登录时,输出用户信息 username=b password=bb
但实际结果时什么样呢?
下面我们来分析一下,到底发生了什么事情。这里注意线程a和b操作的usernameRef、passwordRef为同一资源。
这里需要注意的是,线程并不是顺序执行的,而是随机的,之所以上面的例子每次运行结果一致,主要原因在于
二、随机性
修改LoginServlet去掉a线程的休眠
这里为了方便测试我们修改一下Run类
输出结果包含几种
当然还有其他可能,这里不再赘述。
观察输出结果,你可以发现,它并不是想我们期待的那样,两两一对的出现,而是无序的不规律的。这恰恰说明了多线程的随机性。
三、线程安全
解决以上问题有三种方案:
1.添加 synchronized
修改LoginServlet 为doPost 方法添加 synchronized 。这样当一个线程正在执行此方法时,另一个线程不能进入。
package chapter1.section2;
public class LoginServlet {
private static String usernameRef;
private static String passwordRef;
synchronized public static void doPost(String username,String password){
try {
usernameRef = username;
if("a".equals(username)){
Thread.sleep(5000);
}
passwordRef = password;
System.out.println("username="+usernameRef+" password="+password);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
2.修改变量和方法,去除static关键字。使用对象调用方法
package chapter1.section2;
public class LoginServlet {
private String usernameRef;
private String passwordRef;
public void doPost(String username,String password){
try {
usernameRef = username;
if("a".equals(username)){
Thread.sleep(5000);
}
passwordRef = password;
System.out.println("username="+usernameRef+" password="+password);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
此时a、b线程请求的资源完全独立,不存在线程交互。
3.同步执行线程
这里只需要将线程启动的start方法换成run方法即可
package chapter1.section2;
public class Run {
public static void main(String[] args) {
ALogin a = new ALogin();
a.run();
BLogin b = new BLogin();
b.run();
}
}
比较一下start和run方法的区别,就明白了其中的道理。
使用run方法,线程改为同步执行,而不是异步执行。此时线程对象是由main主线程来控制,而不是交给“线程规划器”来管理,失去了线程的意义。
话不多说,先来撸两行代码
一、非线程安全
首先一个LoginServlet类,此类用于模拟用户登录。注意此类中的 变量和方法全部使用static关键字,代表变量和方法全都是静态的,java只会分配一个资源,多实例需要共享资源,这就是祸根所在,正式如此,引发了 非线程安全。
package chapter1.section2; public class LoginServlet { private static String usernameRef; private static String passwordRef; public static void doPost(String username, String password) { try { usernameRef = username; if ("a".equals(username)) { Thread.sleep(1000); } passwordRef = password; System.out.println("username=" + usernameRef + " password=" + password); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
接着我们定义两个类,用于模拟用户登录
package chapter1.section2; public class ALogin extends Thread{ @Override public void run(){ LoginServlet.doPost("a", "aa"); } }
package chapter1.section2; public class BLogin extends Thread{ @Override public void run(){ LoginServlet.doPost("b", "bb"); } }
最后定义一个测试类
package chapter1.section2; public class Run { public static void main(String[] args) { ALogin a = new ALogin(); a.start(); BLogin b = new BLogin(); b.start(); } }
显然我们希望当 a 用户登录时,输出用户信息 username=a password=aa ,b用户登录时,输出用户信息 username=b password=bb
但实际结果时什么样呢?
username=b password=bb username=b password=aa
下面我们来分析一下,到底发生了什么事情。这里注意线程a和b操作的usernameRef、passwordRef为同一资源。
a线程 | b线程 | usernameRef | passwordRef |
a获取资源usernameRef usernameRef = “a” | a | ||
a休眠1秒 | a | ||
b获取资源usernameRef usernameRef = “b” | b | ||
b获取资源passwordRef passwordRef = “bb” | b | bb | |
输出 username=b password=bb | b | bb | |
a醒来,a获取资源passwordRef passwordRef = “aa” | b | aa | |
输出 username=b password=aa | b | aa |
if ("a".equals(username)) { Thread.sleep(1000); }a线程进入休眠,让出了资源。系统将资源分配给了b线程。下面我们将a的休眠去掉,来看一下线程的随机性。
二、随机性
修改LoginServlet去掉a线程的休眠
package chapter1.section2; public class LoginServlet { private static String usernameRef; private static String passwordRef; public static void doPost(String username, String password) { usernameRef = username; passwordRef = password; System.out.println("username=" + usernameRef + " password=" + password); } }
这里为了方便测试我们修改一下Run类
package chapter1.section2; public class Run { public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 20; i++) { run(); System.out.println(); Thread.sleep(10000); } } public static void run() { ALogin a = new ALogin(); a.start(); BLogin b = new BLogin(); b.start(); } }
输出结果包含几种
username=b password=aa username=b password=bb
a线程 | b线程 | usernameRef | passwordRef |
a获取资源usernameRef usernameRef = “a” | a | ||
b获取资源usernameRef usernameRef = “b” | b | ||
a获取资源passwordRef passwordRef = “aa” | b | aa | |
输出 username=b password=aa | b | aa | |
b获取资源passwordRef passwordRef = “bb” | b | bb | |
输出 username=b password=bb | b | bb |
username=a password=aa username=b password=bb
a线程 | b线程 | usernameRef | passwordRef |
a获取资源usernameRef usernameRef = “a” | a | ||
a获取资源passwordRef passwordRef = “aa” | a | aa | |
输出 username=a password=aa | a | aa | |
b获取资源usernameRef usernameRef = “b” | b | aa | |
b获取资源passwordRef passwordRef = “bb” | b | bb | |
输出 username=b password=bb | b | bb |
观察输出结果,你可以发现,它并不是想我们期待的那样,两两一对的出现,而是无序的不规律的。这恰恰说明了多线程的随机性。
username=b password=aa username=b password=bb
username=a password=aa username=b password=bb
username=a password=aa username=b password=bb
username=a password=aa username=b password=bb
username=a password=aa username=b password=bb
username=a password=aa username=b password=bb
username=a password=aa username=b password=bb
username=a password=aa
username=b password=bb
username=a password=aa username=b password=bb
username=a password=aa username=b password=bb
username=a password=aa username=b password=bb
username=a password=aa username=b password=bb
username=a password=aa username=b password=bb
username=a password=aa
username=b password=bb
username=b password=bb
username=a password=aa
username=a password=aa username=b password=bb
username=a password=aa username=b password=bb
username=a password=aa username=b password=bb
username=a password=aa
username=b password=bb
username=a password=aa username=b password=bb
三、线程安全
解决以上问题有三种方案:
1.添加 synchronized
修改LoginServlet 为doPost 方法添加 synchronized 。这样当一个线程正在执行此方法时,另一个线程不能进入。
package chapter1.section2;
public class LoginServlet {
private static String usernameRef;
private static String passwordRef;
synchronized public static void doPost(String username,String password){
try {
usernameRef = username;
if("a".equals(username)){
Thread.sleep(5000);
}
passwordRef = password;
System.out.println("username="+usernameRef+" password="+password);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
a线程 | b线程 | usernameRef | passwordRef |
a获取资源usernameRef usernameRef = “a” | a | ||
a休眠1秒 | b请求执行doPost方法时, 因为a还没有执行完毕,所以b无法进入 | a | |
a获取资源passwordRef passwordRef = “aa” | a | aa | |
输出 username=a password=aa | a | aa | |
a执行结束 | b获取资源usernameRef usernameRef = “b” | b | aa |
b获取资源passwordRef passwordRef = “bb” | b | bb | |
输出 username=b password=bb | b | bb |
package chapter1.section2;
public class LoginServlet {
private String usernameRef;
private String passwordRef;
public void doPost(String username,String password){
try {
usernameRef = username;
if("a".equals(username)){
Thread.sleep(5000);
}
passwordRef = password;
System.out.println("username="+usernameRef+" password="+password);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
package chapter1.section2; public class ALogin extends Thread{ @Override public void run(){ new LoginServlet().doPost("a", "aa"); } }
package chapter1.section2; public class BLogin extends Thread{ @Override public void run(){ new LoginServlet().doPost("b", "bb"); } }
此时a、b线程请求的资源完全独立,不存在线程交互。
3.同步执行线程
这里只需要将线程启动的start方法换成run方法即可
package chapter1.section2;
public class Run {
public static void main(String[] args) {
ALogin a = new ALogin();
a.run();
BLogin b = new BLogin();
b.run();
}
}
比较一下start和run方法的区别,就明白了其中的道理。
使用run方法,线程改为同步执行,而不是异步执行。此时线程对象是由main主线程来控制,而不是交给“线程规划器”来管理,失去了线程的意义。
相关文章推荐
- 小博老师解析Java核心技术点 ——表单令牌(一)
- 小博老师解析Java核心技术 ——动态解析Jar的运用
- 小博老师解析Java核心技术 ——JSTL核心标签库
- 《Java多线程编程核心技术》(三)线程通信
- LBS核心技术解析(引子)
- Hadoop MapReduce核心技术浅析-----RPC框架解析
- 小博老师解析JavaWeb核心技术 ——AJAX第一弹
- 史上最全!阿里智能人机交互的核心技术解析
- 小博老师解析Java核心技术 ——AJAX第三弹
- 《Java多线程编程核心技术》读后感(六)
- 小博老师解析Java核心技术 ——JSwing键盘监听事件
- Linux01-企业核心技术之逻辑卷LVM深入解析和实战36
- 小博老师解析Java核心技术 ——AJAX第三弹
- 小博老师解析Java核心技术 ——JSwing窗体布局
- 小博老师解析Java核心技术 ——JSwing文本型控件
- 小博老师解析Java核心技术点——文件下载技术
- 小博老师解析Java核心技术 ——JSwing文本域和滚轴控件
- 小博老师解析Java核心技术 ——JSwing窗体状态监听事件