JAVA多线程核心技术 1.2.3 非线程安全 解析
来源:互联网 发布:长城宽带 端口转发 编辑:程序博客网 时间:2024/06/18 05:22
在看《JAVA多线程核心技术》 1.2.3线程安全 发现了一个有趣的例子,在这里和大家分享一下,同时对运行结果进行解析和扩展,帮助新手朋友理解。同时请大神朋友不吝赐教。
话不多说,先来撸两行代码
一、非线程安全
首先一个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 blocke.printStackTrace();}}}
接着我们定义两个类,用于模拟用户登录
package chapter1.section2;public class ALogin extends Thread{@Overridepublic void run(){LoginServlet.doPost("a", "aa");}}
package chapter1.section2;public class BLogin extends Thread{@Overridepublic 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=bbusername=b password=aa
下面我们来分析一下,到底发生了什么事情。这里注意线程a和b操作的usernameRef、passwordRef为同一资源。
usernameRef = “a” a a休眠1秒 a b获取资源usernameRef
usernameRef = “b”b b获取资源passwordRef
passwordRef = “bb”bbb 输出 username=b password=bbbbba醒来,a获取资源passwordRef
passwordRef = “aa” baa输出 username=b password=aa baa
这里需要注意的是,线程并不是顺序执行的,而是随机的,之所以上面的例子每次运行结果一致,主要原因在于
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=aausername=b password=bb
usernameRef = “a” a b获取资源usernameRef
usernameRef = “b”b a获取资源passwordRef
passwordRef = “aa” baa输出 username=b password=aa baa b获取资源passwordRef
passwordRef = “bb”bbb 输出 username=b password=bbbbb
username=a password=aausername=b password=bb
usernameRef = “a” a a获取资源passwordRef
passwordRef = “aa” aaa输出 username=a password=aa aaa b获取资源usernameRef
usernameRef = “b”baa b获取资源passwordRef
passwordRef = “bb”bbb 输出 username=b password=bbbbb
当然还有其他可能,这里不再赘述。
观察输出结果,你可以发现,它并不是想我们期待的那样,两两一对的出现,而是无序的不规律的。这恰恰说明了多线程的随机性。
username=b password=aausername=b password=bbusername=a password=aausername=b password=bbusername=a password=aausername=b password=bbusername=a password=aausername=b password=bbusername=a password=aausername=b password=bbusername=a password=aausername=b password=bbusername=a password=aausername=b password=bbusername=a password=aausername=b password=bbusername=a password=aausername=b password=bbusername=a password=aausername=b password=bbusername=a password=aausername=b password=bbusername=a password=aausername=b password=bbusername=a password=aausername=b password=bbusername=a password=aausername=b password=bbusername=b password=bbusername=a password=aausername=a password=aausername=b password=bbusername=a password=aausername=b password=bbusername=a password=aausername=b password=bbusername=a password=aausername=b password=bbusername=a password=aausername=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 blocke.printStackTrace();}}}
usernameRef = “a” a a休眠1秒b请求执行doPost方法时,
因为a还没有执行完毕,所以b无法进入a a获取资源passwordRef
passwordRef = “aa” aaa输出 username=a password=aa aaaa执行结束b获取资源usernameRef
usernameRef = “b”baa b获取资源passwordRef
passwordRef = “bb”bbb 输出 username=b password=bbbbb
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 blocke.printStackTrace();}}}
package chapter1.section2;public class ALogin extends Thread{@Overridepublic void run(){new LoginServlet().doPost("a", "aa");}}
package chapter1.section2;public class BLogin extends Thread{@Overridepublic 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主线程来控制,而不是交给“线程规划器”来管理,失去了线程的意义。阅读全文
2 0
- JAVA多线程核心技术 1.2.3 非线程安全 解析
- 【java】【多线程】线程安全与线程非安全【1】
- Java 非线程安全
- Java多线程之~~~线程安全容器的非阻塞容器
- Java多线程编程7--SimpleDateFormat非线程安全处理
- 《java多线程编程核心技术》读书笔记3:线程间的通信
- java多线程编程核心技术3-线程间通信
- Java多线程线程安全
- Java多线程:线程安全和非线程安全的集合对象
- Java线程安全和非线程安全
- Java线程安全和非线程安全
- Java线程安全和非线程安全
- Java线程安全和非线程安全
- Java线程安全和非线程安全
- Java线程安全和非线程安全
- Java线程安全和非线程安全
- Java线程安全和非线程安全
- Java线程安全和非线程安全
- mac下node安装提示command not found
- 解决问题的记录【一】TCP连接过多导致新连接创建失败
- 用数组模拟栈结构,实现分隔符"{}"、"()"、"[]"的左右匹配检查
- android kotlin 一键配置 附anko
- 填制模具充程模拟Siemens.NX.11.0.Easy.Fill.Advanced.v5_20170720
- JAVA多线程核心技术 1.2.3 非线程安全 解析
- zigbee术语理解
- windows使用tracert测试分析访问网站的路由节点
- Java变量基础概念
- zookeeper集群安装和配置
- markdown的语法得留着
- 设计模式之观察者模式
- Windows下安装redis插件
- Mongo进阶--存储原理