`
suhuanzheng7784877
  • 浏览: 702319 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
Ff8d036b-05a9-33b5-828a-2633bb68b7e6
读金庸故事,品程序人生
浏览量:47699
社区版块
存档分类
最新评论

Java基础复习笔记03面试、笔试、开发中我们不太注意的陷阱之多线程

阅读更多

1.       什么样的对象存在线程安全问题

当我们刚学Java的时候不会考虑多线程的问题,在自己的IDE环境下运行成功了就行了,不会考虑并发使用此程序的时候会出现什么情况。等做程序员一段时间后发现自己编写的程序确实存在,多线程安全问题。之后走火入魔似地给自己写的方法加上synchronized。其实我们有时候没搞懂什么情况下会出现线程安全的问题。如果我们开发的是单机版的C/S应用系统,客户的计算机就是软件产品的服务器,那么会不会有并发现象呢?只要不是恶意恶搞你的软件,一般一个客户就对应着一个服务,这个时候不存在并发访问的问题,所以一般情况下不会考虑多线程的问题。如果在基于B/SWeb系统中,可能服务器是一台机器,世界各地的客户都来访问你开发的系统,为这些世界各地的客户提供服务,这个时候可能会出现线程安全的问题。为什么说可能,而不是一定呢?如果你的所有的类和类之间的调用都是通过new一个新的对象为之服务(极端情况下还真有),那么new实际上是在服务器内存中新开辟一块内存空间承载对象,那么这种极端情况是不存在线程安全问题的,只要您的服务器内存极大——1TB内存,每日客户量在2万人左右,估计运行1个月应该差不多没什么问题。不过考虑成本,一般没这样高级的服务器让这种极端的程序运行吧。那么最多的就是类之间的调用不是永远的new一个新的出来,而是为了节省资源,使用对象缓存池或者干脆整个应用软件调用比较频繁的类就使用一个单例对象就得了,每次使用这个对象都是原来已有的,不必在内存新建一个,这样垃圾回收器的工作量也不会那么大。而恰恰是这个时候,这个复用的对象就会出现线程安全的问题。所以在开发Web系统的时候大家一定要小心自己编写的类给别人调用的时候是否存在线程安全的问题。

如下程序:

package sy;

class Run implements Runnable {

	Integer numIhread = new Integer(0);

	int num;
	String numStr = "";

	public Run() {

	}

	@Override
	public void run() {

		// synchronized (numStr) {
		for (int i = 0; i < 100; i++) {
			numStr = "" + i;
			System.out.println(Thread.currentThread().getName() + ":num=" + num
					+ "---numStr:" + numStr + "======numIhread:"
					+ numIhread);
			numIhread++;

		}
		// }

	}

}

public class RunClass {

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		Run run1 = new Run();
		// Run run2 = new Run();
		new Thread(run1).start();
		new Thread(run1).start();
	}
}
 运行之后,控制台线程冲突效果如下

Thread-0:num=0---numStr:0======numIhread:0
Thread-1:num=0---numStr:0======numIhread:0
Thread-0:num=0---numStr:1======numIhread:1
Thread-1:num=0---numStr:1======numIhread:1
Thread-1:num=0---numStr:2======numIhread:2
Thread-1:num=0---numStr:3======numIhread:3
Thread-1:num=0---numStr:4======numIhread:4
Thread-1:num=0---numStr:5======numIhread:5
Thread-1:num=0---numStr:6======numIhread:6
Thread-1:num=0---numStr:7======numIhread:7
Thread-0:num=0---numStr:2======numIhread:8

 Thread-0numIhread=1下次访问的时候一下子变成了numIhread=8,谁干的?这都是Thread-1影响的。

改进程序如下

 

package sy;

class Run implements Runnable {

	ThreadLocal<Integer> numIhread = new ThreadLocal<Integer>();

	int num;
	String numStr = "";

	public Run() {

	}

	@Override
	public void run() {

		// synchronized (numStr) {
		for (int i = 0; i < 100; i++) {
			numStr = "" + i;
			System.out.println(Thread.currentThread().getName() + ":num=" + num
					+ "---numStr:" + numStr + "======numIhread:"
					+ numIhread.get());
			num++;
			if (numIhread.get() == null) {
				numIhread.set(0);
			}
			int numTemp = numIhread.get();
			numIhread.set(++numTemp);

		}
		// }

	}

}

public class RunClass {

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		Run run1 = new Run();
		// Run run2 = new Run();
		new Thread(run1).start();
		new Thread(run1).start();
	}
}

 运行后效果片断如下

Thread-0:num=0---numStr:0======numIhread:null

Thread-1:num=0---numStr:0======numIhread:null

Thread-1:num=2---numStr:1======numIhread:1

Thread-1:num=3---numStr:2======numIhread:2

Thread-1:num=4---numStr:3======numIhread:3

Thread-1:num=5---numStr:4======numIhread:4

Thread-1:num=6---numStr:5======numIhread:5

Thread-1:num=7---numStr:6======numIhread:6

Thread-1:num=8---numStr:7======numIhread:7

Thread-1:num=9---numStr:8======numIhread:8

Thread-1:num=10---numStr:9======numIhread:9

Thread-1:num=11---numStr:10======numIhread:10

Thread-1:num=12---numStr:11======numIhread:11

Thread-1:num=13---numStr:12======numIhread:12

Thread-1:num=14---numStr:13======numIhread:13

Thread-1:num=15---numStr:14======numIhread:14

Thread-1:num=16---numStr:15======numIhread:15

Thread-1:num=17---numStr:16======numIhread:16

Thread-1:num=18---numStr:17======numIhread:17

Thread-1:num=19---numStr:18======numIhread:18

Thread-1:num=20---numStr:19======numIhread:19

Thread-1:num=21---numStr:20======numIhread:20

Thread-1:num=22---numStr:21======numIhread:21

Thread-1:num=23---numStr:22======numIhread:22

Thread-0:num=2---numStr:1======numIhread:1

Thread-0:num=25---numStr:2======numIhread:2

Thread-1:num=24---numStr:23======numIhread:23

 注意红色字体和蓝色字体,2个线程互不侵犯,拿到的numThread是第一个线程的变量副本,因此互不干扰,你走你的阳关道,我走我的独木桥。

2.       synchronizedThreadLocal的区别

其实网上已经很多资料介绍这2个的区别了,synchronized表示阻塞,是一种用时间换取空间的做法,而ThreadLocal呢是用资源空间换取时间的做法。synchronized表示一旦某个线程过来了,使用了此修饰后的方法或者代码块,那么其他任何线程不允许访问,直到此方法或者代码块都走完了,其他那些排队的线程再来执行,也就是说再某一个时刻,这个方法的权柄完全由单独一个线程把持着,别人别想越雷池一步,谁让你不早点来的,等老子使用完了,你再用吧。这就好比合租房子的厕所,厕所是个资源,一旦被人使用了,不好意思,其他人排队等着吧。而ThreadLocal呢,相当于大家上的公用厕所,而且只要是地球空间允许(对应于内存空间),那么将是无限个坑位,嘻嘻,爽吧,想上就上,而且互不打扰,你拉你的,我拉我的,而且大家的坑位都是从同一个模板构造出来的,不会出现不同的人使用不同样式的坑位,比如残疾人、老年人。一视同仁!ThreadLocal底层代码使用一个静态的内部类ThreadLocalMap存储副本变量,好让另一个线程使用(都是爷,它谁也不敢怠慢)。

3.       线程的启动

至于线程的调用,还有一点就是只要是线程,无论是继承自Thread类的还是实现类Runnable接口的,都必须使用start()方法作为启动线程的标识。如果不调用start()方法,那么他不能算是以线程为单元执行。

4.       静态同步方法

在静态方法前用synchronized修饰实际上是将整个静态类对象的该方法上了锁,也就是说锁定对象不是实例对象,而是类对象,所有的实例对象都是有内存中的仅有的一个类对象得来的。如果这个静态方法没有访问(或者说改变更贴切一些)任何的静态成员变量,那么其实进行加锁限定除了降低时间效率外没太大的作用。

package trap;

public class ThreadTrap implements Runnable {

	static int num;

	public synchronized static void test() {
		int sum = 0;
		while (sum < 100) {
			System.out
					.println(Thread.currentThread().getName() + "  sum:"+sum);
			sum++;
			try {
				Thread.sleep(1);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			if (sum == 50) {
				break;
			}
		}
	}

	@Override
	public void run() {
		test();
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		ThreadTrap run1 = new ThreadTrap();
		// Run run2 = new Run();
		new Thread(run1).start();
		new Thread(run1).start();
	}

}

 同步的静态方法中实际上并没有访问静态变量。那么其实加与不加synchronized差别不是很大,加入这个静态方法法十分消耗时间,那么另一个线程会等待很长时间才能执行此方法。那么这个时候加上synchronized还真不行,这就是典型的占着皇帝的宝座,不为老百姓办事!还不让别人坐,结果很显然,只能造反(系统崩溃),才能解决问题。

分享到:
评论
2 楼 guojch 2011-10-12  
1 楼 薛立明 2011-08-01  

相关推荐

Global site tag (gtag.js) - Google Analytics