两个线程频繁进行上下文切换引发的线程安全问题分析以及使用synchronized关键字的解决方案
Posted 杀手不太冷!
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了两个线程频繁进行上下文切换引发的线程安全问题分析以及使用synchronized关键字的解决方案相关的知识,希望对你有一定的参考价值。
文章目录
两个线程频繁进行上下文切换引发的线程安全问题分析以及使用synchronized关键字的解决方案
线程安全问题的例子
两个线程频繁进行上下文切换,如果这个过程中,这两个线程共用了同一个资源,这里是counter静态变量,那么就可能会出现线程安全问题,如下图:
以上的结果可能是正数,负数,零,为什么呢?因为Java中对静态变量的自增,自减并不是原子操作,要彻底理解,必须从字节码来进行分析。
例如对于i++而言(i为静态变量),实际会产生如下的JVM字节码指令:
getstatic i //获取静态变量i的值
iconst_1 //准备常量0
iadd //自增
putstatic i //将修改后的值存入静态变量i
而对应i–也是类似:
getstatic i //获取静态变量i的值
iconst_1 //准备常量0
isub //自减
putstatic //将修改后的值存入静态变量i
分析:假设刚开始时静态变量i的值是0,假设t1线程先得到cpu的分时时间片,执行了i++对应的前三个字节码指令,这时候t1线程的时间片用完了,那么这个时候t2线程就得到了cpu的时间片,假设也是执行了i–对应的前三个字节码指令,注意无论是i++对应的前三个字节码指令还是i–对应的前三个字节码指令,此时取得的i静态变量都是0,这个时候t2线程的时间片用完了,t1线程得到时间片,执行i++对应的第四条字节码,把i进行++操作,i从0变成1,然后把i的值存起来,这时候t1线程的时间片用光了,t2线程得到时间片,执行i–对应的第四条字节码,i从0变成-1,然后把i的值存起来,因此执行t1线程执行完i++操作,t2线程执行完i–操作,它的最终结果是i=-1,而不是i=0,这就是说明了,多个线程共享一个资源,会出现线程安全问题。
线程安全问题的解决
synchronized关键字不加在方法上
那么上述的线程安全问题需要怎么解决呢?
第一种解决方案,使用synchronized关键字,语法:
synchronized(对象)//这个对象就是一把锁,要求对象必须是唯一的
{
临界区
}
上述用的synchronized关键字解决线程安全问题的原理:给临界区加上一个锁,这个锁是唯一的,一个线程执行到这个临界区的时候,都会手里面握着一把锁,下一个线程如果也想要执行临界区内的代码,手里面必须也要握着一把同样的锁,但是如果上一个线程没有执行完的话,它就不会释放这把唯一的锁,所以后面的线程就不能执行这块临界区的代码,后面的线程会进入blocked阻塞状态,只有当前面一个线程临界区代码完毕,释放了唯一锁,这个时候后面的线程才能够获得这把锁去执行临界区的代码。
synchronized关键字的使用如下图:
synchronized实际是用对象锁保证了临界区内代码的原子性,比如上图中的例子中,counter++代码和counter–代码都对应着4条JVM字节码指令,synchronized可以保证这4条JVM字节码指令整体运行,不容分割,不会被线程切换打断。
synchronized关键字加到方法上
1.加到非静态方法上,如下:
class Test{
public synchronized void test(){
临界区
}
}
//上面等价于
class Test{
public void test(){
synchronized(this){
临界区
}
}
}
2.加到静态方法上,如下:
class Test{
public synchronized static void test(){
临界区
}
}
//等价于
class Test{
public static void test(){
synchronized(Test.class){
临界区
}
}
}
如果synchronized关键字应用到了许多个方法上,并且这些方法都是用的同一把锁,那么这些方法都是互斥的,什么意思呢?就是如果有多个线程同时调用这些方法,那么每次只能有一个线程调用其中的一个方法,如下图:
方法用的锁不同,那么它们不会互斥,如下图:
以上是关于两个线程频繁进行上下文切换引发的线程安全问题分析以及使用synchronized关键字的解决方案的主要内容,如果未能解决你的问题,请参考以下文章