线程安全概念初探(Java)

一 线程

线程是进程中实施调度和分派的基本单位。一个进程可以有多个线程,但至少有一个线程;而一个线程只能在一个进程的地址空间内活动。内存资源分配给进程,同一个进程的所有线程共享该进程所有资源。而CPU分配给线程,即真正在处理器运行的是线程。

二 线程安全

多线程可以带来更好的性能,但是线程也不是越多越好,多线程往往会带来许多意想不到的问题,比如因为线程之间的调度和切换会浪费CPU的时间以及下面要讨论到的线程安全问题。线程安全是指在多线程环境下,程序可以始终执行正确的行为,符合预期的逻辑。

Talk is cheap:

package thread01;

public class ThreadExp {
	private static  int numCount;
	private static class ThreadText extends Thread{
		public void run() {
			for(int i=0;i<100;i++) {
				numCount++;
				try {
					Thread.sleep(1);
				}catch(InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
	public static void main(String[] args)throws InterruptedException {
		ThreadText t1=new ThreadText();
		ThreadText t2=new ThreadText();
		t1.start();
		t2.start();
		t1.join();
		t2.join();
		System.out.println(numCount);
	}

}

执行结果1:                    执行结果2:             执行结果3:

定义一个int型的numCount变量,通过extends实现一个ThreadText类,new出t1,t2两个对象并开启start( ),每个线程执行对numCount进行100次循环累加,最后打印numCount的值。由结果可以看出事实上每次结果不尽相同,为什么?

numCount++的指令在实际执行的过程中不是原子性的,而是要分为读、改、写三步来进行;即先从内存中读出numCount的值,然后执行+1操作,再将结果写回内存中,如下图所示。

 

上图中线程1执行了两次自加操作,而线程2执行了一次自加操作,但是numCount却从0变成了2。当线程1读取numCount的值为0完成后,此时切换到了线程2执行,线程2同样读取到了numCount的值为0,而后进行改和写操作,count的值变为了1;此时线程又切回了线程1,但是线程1中numCount的值依然是线程2修改前的0。即线程2修改了numCount的值,但是这种修改对线程1不可见,导致了程序出现了线程不安全的问题,没有符合我们预期的逻辑。因此导致线程不安全的原因可以归结为以下三点:

  • 原子性:一个或者多个操作在 CPU 执行的过程中被中断

  • 可见性:一个线程对共享变量的修改,另外一个线程不能立刻看到

  • 有序性:程序执行的顺序没有按照代码的先后顺序执行

对于有序性,为什么程序执行的顺序会和代码的执行顺序不一致呢?java平台包括两种编译器:静态编译器(javac)和动态编译器(jit:just in time)。静态编译器是将.java文件编译成.class文件,之后便可以解释执行。动态编译器是将.class文件编译成机器码,之后再由jvm运行。问题一般会出现在动态编译器上,因为动态编译器为了程序的整体性能会对指令进行重排序,虽然重排序可以提升程序的性能,但是重排序之后会导致源代码中指定的内存访问顺序与实际的执行顺序不一样,就会出现线程不安全的问题。

三 线程安全级别

  • 不可变:不变的对象绝对是线程安全的,不需要线程同步,比如String、Integer、Long,都是 final 类型的类,任何一个线程都改变不了它们的值;
  • 绝对线程安全:在任何环境下,调用者都不需要额外的同步措施;比如 Concurrent集合、CopyOnWriteArrayList Random 、ConcurrentHashMap、atomic,当然为了达到这种程度,要付出额外的代价,比如性能;
  • 相对线程安全:对象的部分方法可以无条件安全使用,但是有些方法需要外部同步,需要Collections.synchronized;有条件线程安全的最常见的例子是遍历由 Hashtable 或者 Vector 或者返回的迭代器。通常意义上所说的线程安全,大多数情况下,都能够正常运行;比如 Vector ,add、remove 方法都是原子操作,但是如果有两个线程同时操作,一个线程遍历,另外一个线程执行 remove 操作的话,就可能会抛出异常。
  • 非线程安全:对象本身不提供线程安全机制,但是通过外部同步,可以在并发环境使用, 如ArrayList和HashMap。在开发过程中,调用者需要考虑同步问题。比如List ,Map,都是非线程安全的。
  • 线程对立:使外部进行了同步调用,也不能保证线程安全,这种情况非常少,如System.runFinalizersOnExit( )

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值