- 浏览: 5180481 次
- 性别:
- 来自: 北京
文章分类
最新评论
-
silence19841230:
先拿走看看
SpringBoot2.0开发WebSocket应用完整示例 -
wallimn:
masuweng 写道发下源码下载地址吧!三个相关文件打了个包 ...
SpringBoot2.0开发WebSocket应用完整示例 -
masuweng:
发下源码下载地址吧!
SpringBoot2.0开发WebSocket应用完整示例 -
masuweng:
SpringBoot2.0开发WebSocket应用完整示例 -
wallimn:
水淼火 写道你好,我使用以后,图标不显示,应该怎么引用呢,谢谢 ...
前端框架iviewui使用示例之菜单+多Tab页布局
转自:http://josh-persistence.iteye.com/blog/1923191
由于很多优秀的Java Web容器或者是J2EE容器的涌现,作为一个java web程序员,很少或者不需要去处理线程的问题,因为服务器或者是框架(如Spring,Struts)等都帮我们处理好了。但当我们查看JDK的API的时候,我们总会看到一些类写着:线程安全或者线程不安全。最简单的例子,比如说StringBuilder这个类中,有这么一句:“将StringBuilder的实例用于多个线程是不安全的。如果需要这样的同步,则建议使用StringBuffer.
为了说明线程的不安全会带来什么问题,下面手动创建一个线程不安全的类,然后在多个线程中去测试使用这个类,看看有什么效果。
在这个类中的count方法是计算1一直加到10的和,并输出当前线程名,还有共享的对象(Count)的个数和数字的总和,我们期望的是每个线程都会输出55。
这里启动了10个线程,我们先看下输出的结果是不是我们预期的那样
----- Thread running 3 times
----- Thread running 8 times
Thread-8 : 110 Object Number: 1
----- Thread running 10 times
Thread-9 : 165 Object Number: 1
----- Thread running 7 times
----- Thread running 6 times
Thread-7 : 275 Object Number: 1
----- Thread running 5 times
----- Thread running 2 times
----- Thread running 4 times
----- Thread running 1 times
Thread-3 : 440 Object Number: 1
Thread-1 : 385 Object Number: 1
Thread-4 : 330 Object Number: 1
Thread-5 : 220 Object Number: 1
----- Thread running 9 times
Thread-2 : 55 Object Number: 1
Thread-6 : 550 Object Number: 1
Thread-0 : 495 Object Number: 1
我们看到只有一个线程(此处是Thread-2)线程输出的结果是我们期望的,而输出的每次都是累加的。为什么都是累加的呢?
根本的原因是我们创建的Count对象是线程共享的,一个线程改变了成员变量num的值,下一个线程正巧读到了修改后的num,所以会递增输出。
要说明线程同步问题首先要说明Java线程的两个特性,可见性和有序性。多个线程之间是不能直接传递数据交互的,它们之间的交互只能通过共享变量来实现。 拿上篇博文中的例子来说明,在多个线程之间共享了Count类的一个对象,这个对象是被创建在主内存(堆内存)中,每个线程都有自己的工作内存(线程 栈),工作内存存储了主内存Count对象的一个副本,当线程操作Count对象时,首先从主内存复制Count对象到工作内存中,然后执行代码 count.count(),改变了num值,最后用工作内存Count刷新主内存Count。当一个对象在多个内存中都存在副本时,如果一个内存修改了 共享变量,其它线程也应该能够看到被修改后的值,此为可见性。由上述可知,一个运算赋值操作并不是一个原子性操作,多 个线程执行时,CPU对线程的调度是随机的,我们不知道当前程序被执行到哪步就切换到了下一个线程,一个最经典的例子就是银行汇款问题,一个银行账户存款 100,这时一个人从该账户取10元,同时另一个人向该账户汇10元,那么余额应该还是100。那么此时可能发生这种情况,A线程负责取款,B线程负责汇 款,A从主内存读到100,B从主内存读到100,A执行减10操作,并将数据刷新到主内存,这时主内存数据100-10=90,而B内存执行加10操 作,并将数据刷新到主内存,最后主内存数据100+10=110,显然这是一个严重的问题,我们要保证A线程和B线程有序执行,先取款后汇款或者先汇款后 取款,此为有序性。
特别说明: 1. 10个线程,可能一开始都从主内存中读取到count对象的num的值都是1并放到各自的线程栈的工作内存中,但是当线程1执行完并刷新结果到主内存以后,线程2会在进行具体的操作之前,会去清楚自己的工作内存并重新从主内存中读取新的变量num的值。
2. 有序性可以简单的理解为,无论是A线程还是B线程先执行,都要保证有序,即A线程要么先执行完,再执行B线程,或者B线程先执行完,再执行A线程。即要么先取款,或者要么先存款。
3. 这一点大家一定要注意:特性1是可见性,这是多个线程共享同一个资源时,多个线程天然具有的特性,但是特性2 即有序性并不是天然具有的,而是我们要通过相关的API来解决的问题,我们往往要确保线程的执行是有序的,或者说是互斥的,即一个线程执行时,不允许另一个线程执行。如果你足够细心,那就会提出这样一个疑问,那么在上面的例子中,我们并没有用到线程相关的API,但最后的线程之间的输出结果是如此的有序(输出结果是很有规律的:55, 110,165, 220等后一个比前一个恰好大55的输出结果,如果上面的10个线程是随机执行的,那么输出结果肯定不是55, 110,165等. 因为有可能一个线程恰好加到53时,此时另一个线程开始执行,并从53开始逐渐的加1,而不是从我们期望的55逐渐加1,这是什么原因呢?这是因为CPU执行上面的10个线程都足够快,这是因为我们只是从1简单的加到10,等cpu时间片还没来得及执行下一个线程时,这个线程已经执行完了,所以看到的线程执行都是有序的,这个结果告诉我们的表现其实是不对的,因为线程的执行是随机的。要验证我们这个说法其实很简单,只需要将加大上面的for循环,加大for循环的执行时间,那么等其中一个线程没有执行完时,另一个线程可能就开始执行了,所以我们可以这样去修改:)
将for (int i = 1; i <= 10; i++) {num += i;}
改为:
for (int i = 1; i <= 1000; i++) {num += i;}, 下面是输出结果,从输出结果可以看出,此时的多个线程的执行不再是有序的,而是随机执行了。在下一篇博文中,我将通过一个例子,让你更加深入的体会到线程的随机执行性。
Result代码 收藏代码
----- Thread running 1 times
----- Thread running 10 times
Thread-0 : 1001000 Object Number: 1
----- Thread running 8 times
----- Thread running 9 times
Thread-9 : 2002000 Object Number: 1
----- Thread running 7 times
----- Thread running 6 times
Thread-5 : 3003000 Object Number: 1
----- Thread running 5 times
----- Thread running 4 times
----- Thread running 3 times
----- Thread running 2 times
Thread-2 : 4504500 Object Number: 1
Thread-3 : 4004000 Object Number: 1
Thread-4 : 3503500 Object Number: 1
Thread-8 : 2502500 Object Number: 1
Thread-7 : 1501500 Object Number: 1
Thread-6 : 1001000 Object Number: 1
Thread-1 : 5005000 Object Number: 1
如果想要得到我们的期望结果,即每个线程的输出结果都是55.怎么办?有几种解决方案:
1. 将Count类中num变成count方法的局部变量:
2. 将线程类成员变量拿到run方法中;
3. 每次启动一个线程使用不同的线程类,不推荐。
上述测试,我们发现,存在成员变量的类用于多线程时是不安全的,而变量定义在方法内是线程安全的。想想在使用struts1时,不推荐创建成员变量,因为 action是单例的,如果创建了成员变量,就会存在线程不安全的隐患,而struts2是每一次请求都会创建一个action,就不用考虑线程安全的问 题。
由于很多优秀的Java Web容器或者是J2EE容器的涌现,作为一个java web程序员,很少或者不需要去处理线程的问题,因为服务器或者是框架(如Spring,Struts)等都帮我们处理好了。但当我们查看JDK的API的时候,我们总会看到一些类写着:线程安全或者线程不安全。最简单的例子,比如说StringBuilder这个类中,有这么一句:“将StringBuilder的实例用于多个线程是不安全的。如果需要这样的同步,则建议使用StringBuffer.
为了说明线程的不安全会带来什么问题,下面手动创建一个线程不安全的类,然后在多个线程中去测试使用这个类,看看有什么效果。
/** * */ package com.wsheng.thread.simlesafe; /** * 在这个类中的Count方法是计算1一直加到10的和,并输出当前线程名和最后的总和, * 我们期望的结果应该是每一个线程都会输出55 * * @author Wang Sheng(Josh) * */ public class Count { private int num; // 我们想查看Count类的对象在内存中有几个, 以判断是否有资源共享和竞争 private int objectNum; public Count(int objectNum) { this.objectNum = objectNum; } public void count() { for (int i = 1; i <= 10; i++) { num += i; } // Object Number一值都是1,说明只有一个Count对象,保证多个线程共享一个Count对象。 System.out.println(Thread.currentThread().getName() + " : " + num + " Object Number: " + objectNum); } }
在这个类中的count方法是计算1一直加到10的和,并输出当前线程名,还有共享的对象(Count)的个数和数字的总和,我们期望的是每个线程都会输出55。
/** * */ package com.wsheng.thread.simlesafe; /** * @author Wang Sheng(Josh) * */ public class ThreadTest { public static void main(String[] args) { Runnable run = new Runnable() { int i = 1, j = 1; // 只会new一次,即10个线程共享1个对象 Count count = new Count(i++); public void run() { System.out.println(" ----- Thread running " + j++ + " times"); count.count(); } }; for (int i = 0; i < 10; i++) { new Thread(run).start(); } } }
这里启动了10个线程,我们先看下输出的结果是不是我们预期的那样
----- Thread running 3 times
----- Thread running 8 times
Thread-8 : 110 Object Number: 1
----- Thread running 10 times
Thread-9 : 165 Object Number: 1
----- Thread running 7 times
----- Thread running 6 times
Thread-7 : 275 Object Number: 1
----- Thread running 5 times
----- Thread running 2 times
----- Thread running 4 times
----- Thread running 1 times
Thread-3 : 440 Object Number: 1
Thread-1 : 385 Object Number: 1
Thread-4 : 330 Object Number: 1
Thread-5 : 220 Object Number: 1
----- Thread running 9 times
Thread-2 : 55 Object Number: 1
Thread-6 : 550 Object Number: 1
Thread-0 : 495 Object Number: 1
我们看到只有一个线程(此处是Thread-2)线程输出的结果是我们期望的,而输出的每次都是累加的。为什么都是累加的呢?
根本的原因是我们创建的Count对象是线程共享的,一个线程改变了成员变量num的值,下一个线程正巧读到了修改后的num,所以会递增输出。
要说明线程同步问题首先要说明Java线程的两个特性,可见性和有序性。多个线程之间是不能直接传递数据交互的,它们之间的交互只能通过共享变量来实现。 拿上篇博文中的例子来说明,在多个线程之间共享了Count类的一个对象,这个对象是被创建在主内存(堆内存)中,每个线程都有自己的工作内存(线程 栈),工作内存存储了主内存Count对象的一个副本,当线程操作Count对象时,首先从主内存复制Count对象到工作内存中,然后执行代码 count.count(),改变了num值,最后用工作内存Count刷新主内存Count。当一个对象在多个内存中都存在副本时,如果一个内存修改了 共享变量,其它线程也应该能够看到被修改后的值,此为可见性。由上述可知,一个运算赋值操作并不是一个原子性操作,多 个线程执行时,CPU对线程的调度是随机的,我们不知道当前程序被执行到哪步就切换到了下一个线程,一个最经典的例子就是银行汇款问题,一个银行账户存款 100,这时一个人从该账户取10元,同时另一个人向该账户汇10元,那么余额应该还是100。那么此时可能发生这种情况,A线程负责取款,B线程负责汇 款,A从主内存读到100,B从主内存读到100,A执行减10操作,并将数据刷新到主内存,这时主内存数据100-10=90,而B内存执行加10操 作,并将数据刷新到主内存,最后主内存数据100+10=110,显然这是一个严重的问题,我们要保证A线程和B线程有序执行,先取款后汇款或者先汇款后 取款,此为有序性。
特别说明: 1. 10个线程,可能一开始都从主内存中读取到count对象的num的值都是1并放到各自的线程栈的工作内存中,但是当线程1执行完并刷新结果到主内存以后,线程2会在进行具体的操作之前,会去清楚自己的工作内存并重新从主内存中读取新的变量num的值。
2. 有序性可以简单的理解为,无论是A线程还是B线程先执行,都要保证有序,即A线程要么先执行完,再执行B线程,或者B线程先执行完,再执行A线程。即要么先取款,或者要么先存款。
3. 这一点大家一定要注意:特性1是可见性,这是多个线程共享同一个资源时,多个线程天然具有的特性,但是特性2 即有序性并不是天然具有的,而是我们要通过相关的API来解决的问题,我们往往要确保线程的执行是有序的,或者说是互斥的,即一个线程执行时,不允许另一个线程执行。如果你足够细心,那就会提出这样一个疑问,那么在上面的例子中,我们并没有用到线程相关的API,但最后的线程之间的输出结果是如此的有序(输出结果是很有规律的:55, 110,165, 220等后一个比前一个恰好大55的输出结果,如果上面的10个线程是随机执行的,那么输出结果肯定不是55, 110,165等. 因为有可能一个线程恰好加到53时,此时另一个线程开始执行,并从53开始逐渐的加1,而不是从我们期望的55逐渐加1,这是什么原因呢?这是因为CPU执行上面的10个线程都足够快,这是因为我们只是从1简单的加到10,等cpu时间片还没来得及执行下一个线程时,这个线程已经执行完了,所以看到的线程执行都是有序的,这个结果告诉我们的表现其实是不对的,因为线程的执行是随机的。要验证我们这个说法其实很简单,只需要将加大上面的for循环,加大for循环的执行时间,那么等其中一个线程没有执行完时,另一个线程可能就开始执行了,所以我们可以这样去修改:)
将for (int i = 1; i <= 10; i++) {num += i;}
改为:
for (int i = 1; i <= 1000; i++) {num += i;}, 下面是输出结果,从输出结果可以看出,此时的多个线程的执行不再是有序的,而是随机执行了。在下一篇博文中,我将通过一个例子,让你更加深入的体会到线程的随机执行性。
Result代码 收藏代码
----- Thread running 1 times
----- Thread running 10 times
Thread-0 : 1001000 Object Number: 1
----- Thread running 8 times
----- Thread running 9 times
Thread-9 : 2002000 Object Number: 1
----- Thread running 7 times
----- Thread running 6 times
Thread-5 : 3003000 Object Number: 1
----- Thread running 5 times
----- Thread running 4 times
----- Thread running 3 times
----- Thread running 2 times
Thread-2 : 4504500 Object Number: 1
Thread-3 : 4004000 Object Number: 1
Thread-4 : 3503500 Object Number: 1
Thread-8 : 2502500 Object Number: 1
Thread-7 : 1501500 Object Number: 1
Thread-6 : 1001000 Object Number: 1
Thread-1 : 5005000 Object Number: 1
如果想要得到我们的期望结果,即每个线程的输出结果都是55.怎么办?有几种解决方案:
1. 将Count类中num变成count方法的局部变量:
/** * */ package com.wsheng.thread.simlesafe; /** * 在这个类中的Count方法是计算1一直加到10的和,并输出当前线程名和最后的总和, * 我们期望的结果应该是每一个线程都会输出55 * * @author Wang Sheng(Josh) * */ public class Count2 { // 我们想查看Count类的对象在内存中有几个, 以判断是否有资源共享和竞争 private int objectNum; public Count2(int objectNum) { this.objectNum = objectNum; } public void count() { int num = 0; for (int i = 1; i <= 10; i++) { num += i; } // Object Number一值都是1,说明只有一个Count对象,保证多个线程共享一个Count对象。 System.out.println(Thread.currentThread().getName() + " : " + num + " Object Number: " + objectNum); } }
2. 将线程类成员变量拿到run方法中;
/** * */ package com.wsheng.thread.simlesafe; /** * @author Wang Sheng(Josh) * */ public class ThreadTest3 { public static void main(String[] args) { Runnable run = new Runnable() { int i = 1, j = 1; public void run() { Count count = new Count(i++); System.out.println(" ----- Thread running " + j++ + " times"); count.count(); } }; for (int i = 0; i < 10; i++) { new Thread(run).start(); } } }很明显,这个方法会构造10个Count对象。
3. 每次启动一个线程使用不同的线程类,不推荐。
上述测试,我们发现,存在成员变量的类用于多线程时是不安全的,而变量定义在方法内是线程安全的。想想在使用struts1时,不推荐创建成员变量,因为 action是单例的,如果创建了成员变量,就会存在线程不安全的隐患,而struts2是每一次请求都会创建一个action,就不用考虑线程安全的问 题。
发表评论
-
gradle编译错误:Could not find method compile() for arguments
2020-09-19 10:50 18670编译(IDEA+Gradle)一个别人的工程,出现一个 ... -
netty心跳检查之UDP篇
2019-09-15 08:50 2492部分UDP通信场景中,需要客户端定期发送心跳信息,以获取终 ... -
解决tomcat部署两个SpringBoot应用提示InstanceAlreadyExistsException
2019-06-30 11:49 3493两个SpringBoot应用部署在一个Tomcat中,单独 ... -
Eclipse配置MyBatis代码自动化功能
2019-06-29 10:16 18441.安装插件 Eclipse中,Help->Ecli ... -
vue.js中使用qrcode生成二维码
2019-05-20 00:00 7695一、安装包 npm install qrcodejs2 --s ... -
MySQL插入数据报错: Incorrect string value: '\xFD\xDE'
2019-03-31 23:19 1285我MySQL数据库用的uft-8字符集,插入数据一直很正常 ... -
vue自定义组件并双向绑定属性
2019-03-08 22:46 3272做了两个子组件,原理基本一样,一个是使用原生的select ... -
vue-router简单示例
2019-03-05 00:32 1183写个基本完整、稍有借鉴意义的示例,防止自己忘记。 &l ... -
“联通充值系统繁忙”轻松应对
2019-02-06 11:03 4004大过年的,联通充个值一直报“充值系统繁忙”。昨天晚上试了几 ... -
electron.js数据库应用---导航菜单(element-ui+mysql)
2019-02-05 21:33 2403一、环境搭建 略, ... -
electron.js数据库应用---入门(mysql+element-ui)
2019-01-27 23:19 7555我的机器:Windows10,64 ... -
SpringMVC 在controller层中注入成员变量request,是否线程安全
2018-12-17 21:17 2780@RestController public class ... -
VueJS 组件参数名命名与组件属性转化
2018-12-03 00:00 2095转自:https://www.cnblogs.com/meiy ... -
vue-resource拦截器实现token发送及检验自动化
2018-11-16 22:38 3089用了很长时间vue-resource,最近思考$http发 ... -
element-ui试用手记
2018-10-29 20:25 1771element-ui、iviewui都以vue.js为基础 ... -
iviewui中表格控件中render的使用示例
2018-07-07 16:46 9802示例了如何在表格中显示按钮,如何将代码转化为文字。 i ... -
Tomcat错误“Alias name tomcat does not identify a key entry”解决
2018-07-05 21:39 6673申请到了阿里云的证书后,下载、按照说明生成jks格式证书、 ... -
阿里云免费证书“fileauth.txt内容配置错误”解决
2018-07-05 20:43 5348最近研究微信小程序开发,上阿里云申请了个证书,使用文件验证 ... -
springboot2.0跨域配置
2018-07-04 22:11 5299springboot2.0跨域配置: 一、代码 ... -
微信小程序使用code换openid的方法(JAVA、SpringBoot)
2018-07-01 21:52 10441微信小程序序的代码中提示,使用code换取openid,但 ...
相关推荐
在Java编程中,多线程是并发处理任务的关键技术,特别是在服务器端开发和高并发场景下。本篇文章将深入探讨“最简单的线程安全问题”,并结合相关源码和工具来帮助理解。线程安全问题通常涉及到多个线程对共享资源的...
### Java多线程知识点总结及企业真题解析 #### 一、知识点总结 ##### (1)多线程相关概念 1. **程序、进程和线程的区分**: - **程序**:为了完成特定的任务而编写的指令集合。它是静态的概念。 - **进程**:...
### Java多线程—解决单例模式中的懒汉式的线程安全问题 #### 一、单例设计模式的线程安全问题 ##### (1)饿汉式没有线程安全问题 **饿汉式**是一种非常典型的单例模式实现方式,其特点是在类加载时就完成了实例...
### Java多线程-避免同步机制带来的死锁问题及用Lock锁解决线程安全问题 #### 死锁 ##### 1. 说明 在多线程编程中,死锁是一种常见的问题,指的是两个或多个线程在执行过程中,因为竞争资源而造成的一种相互等待...
### Java多线程—线程间的通信 #### 一、线程间的通信 ##### (1)为什么要处理线程间的通信? 在多线程环境中,不同的线程可能需要协同工作来完成一项任务。例如,一个线程负责生产数据,另一个线程负责消费这些...
### Java多线程-JDK5.0新增线程创建方式 #### 一、新增方式1:实现Callable接口 ##### (1)介绍 自Java 5.0起,为提高线程管理的灵活性与效率,引入了`Callable`接口,这是一种全新的创建线程的方式。与传统的`...
在“多线程-day02”的学习资源中,我们深入探讨了Java内存模型以及多线程的特性与控制机制。 **Java内存模型** Java内存模型,也称为JVM内存模型,是Java程序员理解和掌握的基础知识,特别是在进行并发编程时。它...
Java多线程与线程安全实践-基于Http协议的断点续传.rar 是一个Java毕业设计项目,旨在探讨如何在Java中实现多线程和线程安全,以及如何基于Http协议实现断点续传功能。该项目提供了一个完整的源代码包,可以作为学习...
【Java多线程简单下载器】是一个初学者的编程作业,虽然代码可能较为混乱,但其核心功能已经实现,即通过多线程技术进行文件的下载。在Java中,多线程是并发处理的重要手段,它允许多个任务在同一时间执行,从而提高...
总结起来,Java多线程在实现订票系统时,需要注意线程安全问题,通过合理使用同步机制、Lock接口和原子类,可以在保证数据一致性的同时提高并发性能。在实际开发中,要根据业务场景选择合适的线程安全策略,以达到...
Java多线程是Java编程中的重要概念,它允许程序同时执行多个任务,提高了程序的效率和响应性。在Java中,实现多线程有两种主要方式:通过继承Thread类或者实现Runnable接口。本示例可能是一个Applet,Applet是Java小...
### Java多线程编程总结 #### 一、Java线程:概念与原理 1. **操作系统中线程和进程的概念** - 当前的操作系统通常为多任务操作系统,多线程是实现多任务的一种手段。 - **进程**:指内存中运行的应用程序,每个...
在多线程环境下,简单工厂模式可以用来创建线程安全的对象,避免因为多个线程同时创建对象导致的数据不一致问题。 2. **工厂方法模式**:工厂方法模式是简单工厂模式的进一步抽象和推广,它定义一个创建对象的接口...
这个"基于Java的源码-超简单Java多线程填表源码.zip"文件很可能包含一个简单的示例,演示如何在Java中使用多线程来填充表格数据。下面我们将深入探讨Java多线程的基础知识及其在实际应用中的使用。 1. **线程基础**...
Java多线程导出Excel是处理大数据量时的一种高效策略,尤其在面对千万级别的数据时。传统的Apache POI库在处理大规模数据时可能会遇到栈溢出(StackOverflowError)和内存溢出(OutOfMemoryError)等问题,因为这些...
### Java多线程编程经验 #### 一、Java线程:概念与原理 现代操作系统都是多任务操作系统,其中多线程是一种重要的实现多任务的方式。线程是进程内的一个执行单位,一个进程可以包含多个线程。例如,在Java应用...
### Java多线程文章系列知识点概述 #### 一、Java多线程编程详解 ##### 1. 理解多线程 - **定义**: 多线程是一种机制,允许程序中并行执行多个指令流,每个指令流称为一个线程。 - **特点**: - 线程拥有独立的...
综上所述,Java多线程编程不仅提供了强大的并发执行能力,同时也带来了复杂度更高的问题,需要开发者深入了解并妥善处理。掌握Java多线程编程的精髓,将为构建高性能、高可用性的应用打下坚实的基础。
下面是一个简单的Java多线程断点续传实现示例: ```java public class MultiThreadedDownload { private final int chunkSize; private final URL url; private final File outputFile; public ...
Java多线程知识点梳理: 1. Java线程基础知识 - 线程是程序中独立的、并发的执行路径。每个线程都有自己的堆栈、程序计数器和局部变量,但与分隔的进程不同,线程之间的隔离程度较小,它们共享内存、文件句柄等...