`
Josh_Persistence
  • 浏览: 1631853 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类

Java多线程 - (一) 最简单的线程安全问题

阅读更多

       由于很多优秀的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;}, 下面是输出结果,从输出结果可以看出,此时的多个线程的执行不再是有序的,而是随机执行了。在下一篇博文中,我将通过一个例子,让你更加深入的体会到线程的随机执行性。

 

 ----- 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,就不用考虑线程安全的问 题。

 

 

1
4
分享到:
评论
1 楼 houyujiangjun 2013-08-12  
基础的基础   还是感谢分享.

相关推荐

    java多线程核心技术

    结合大量实例,全面讲解Java多线程编程中的并发访问、线程间通信、锁等最难突破的核心技术与应用实践 Java多线程无处不在,如服务器、数据库、应用。多线程可以有效提升计算和处理效率,大大提升吞吐量和可伸缩性,...

    java 多线程服务器

    java 实现绘制指针时钟 和多线程服务器java 实现绘制指针时钟 和多线程服务器java 实现绘制指针时钟 和多线程服务器java 实现绘制指针时钟 和多线程服务器java 实现绘制指针时钟 和多线程服务器java 实现绘制指针...

    线程 JAVA java线程 java线程第3版 java线程第2版第3版合集

    这是一件好事,因为如果没有线程,那么除了最简单的applet之外,几乎不可能编写出任何程序。如果你想使用Java,就必须学习线程。 本书的新版本展示了如何利用Java线程工具的全部优势,并介绍了JDK 2线程接口中的...

    java项目史上最简单的多线程使用方法(demo)

    java项目史上最简单的多线程使用方法(demo),下载下来一看就明白企业实际项目中如何使用多线程提高程序效率,导入idea或者eclipse中,修改以下数据库配置即可直接使用

    java源码包---java 源码 大量 实例

     Tcp服务端与客户端的JAVA实例源代码,一个简单的Java TCP服务器端程序,别外还有一个客户端的程序,两者互相配合可以开发出超多的网络程序,这是最基础的部分。 递归遍历矩阵 1个目标文件,简单! 多人聊天室 3...

    Java面试题-面向对象、多线程.pdf

    1、面向对象的特征有哪些方面? 答:面向对象的特征主要有以下几个方面: ...总共90多道题目,包含面向对象、算法、多线程等面试题及详解 大厂面试题集,纯人工手写,分享不易,有问题敬请谅解 。。。。。。。。

    java多线程自增效率比较及原理解析

    在多线程环境下,对于自增操作需要考虑线程安全问题,常见的解决方法包括使用synchronized关键字、AtomicInteger、LongAdder和LongAccumulator等。本文给出了使用这些方法实现自增的代码演示,并通过多线程测试比较...

    concurrent 多线程 教材

    00 IBM developerWorks 中国 : Java 多线程与并发编程专题 02 Java 程序中的多线程 03 编写多线程的 Java 应用程序 04 如果我是国王:关于解决 Java编程语言线程问题的建议 (2) 05 构建Java并发模型框架 (2) 06...

    Java 详细介绍-适合初级学员

    Java是一种简单的,面向对象的,分布式的,解释型的,健壮安全的,结构中立的,可移植的,性能优异、多线程的动态语言。  当1995年SUN推出Java语言之后,全世界的目光都被这个神奇的语言所吸引。那么Java到底有何...

    多线程java时钟,计科最实用

    一个简单 seconds=MyTimer.intHour*60*60+MyTimer.intMinute*60+MyTimer.intSecond; seconds+=a;//a为秒自加 repaint();} else { seconds = calendar.get(Calendar.HOUR) * 60 * 60 + calendar.get...

    Java案例开发锦集

    案例4 利用Java API发送E-mail 案例5 从Mail Server删除一条消息 案例6 在Java程序中实现FTP的功能 案例7 一个最简单的聊天程序 案例8 代理服务器的实现 第十章 Java综合实例 案例1 用Java...

    RexCrawler:一个极简的 Java 多线程爬虫 API

    一个用于 Java 的简约多线程爬虫 API。 分散其简约的结构,此爬虫基于 Java ForkJoin 框架,该框架允许 RexCrawler 通过将其本地工作负载分区来按需创建其他守护进程。 这种方法的一个关键好处是更好地控制粒度和更...

    Java面试宝典-经典

    50、多线程有几种实现方法?同步有几种实现方法? 33 51、启动一个线程是用run()还是start()? . 33 52、当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法? 33 53、线程的基本概念...

    java-servlet-api.doc

    JavaServletAPI提供了一个简单的接口,通过这个接口,Servlet引擎可以有效地跟踪用户的会话。 建立Session 因为HTTP是一个请求-响应协议,一个会话在客户机加入之前会被认为是一个新的会话。加入的意思是返回会话...

    java concurrent source code

    结合大量实例,全面讲解Java多线程编程中的并发访问、线程间通信、锁等最难突破的核心技术与应用实践 封底 Java多线程无处不在,如服务器、数据库、应用。多线程可以有效提升计算和处理效率,大大提升吞吐量和可...

    java简单的生产者消费者代码(线程)

    java线程之简单的生产者消费者代码,没有太多注释。这个是store部分,生产者消费者自己可以简单写一个。

    java线程.pdf

    很多教材都是对java线程简单的说明而且放在最后,本书详细的说明的java的用法!学习java线程的必备材料。

    最简单的JAVA双端udp 聊天通信 注释的很好,简单易懂

    我基于教材写的,程序很小,很适合学习,有用到udp通信,还多线程,两台电脑如果在一个局域网内,同时打开这个小程序,输入对方ip或者计算机名,就可以实现文字聊天,或者本机打开一个窗口,然后在destination处输入...

    Java-RU-MENG-JIAOCHEN.rar_网络 性能

    Java具有面向对象、分布式和多线程等先进高级计算机语言的特点,同时它还因可移植、安全性能高和网络移动性等逐渐成为一种行业标准。对于初次接触计算机编程语言的人来说,Java语言简单易学,不需要长时间的培训就...

Global site tag (gtag.js) - Google Analytics