互斥问题
在计算机中很多资源都是有限的,这种有限资源叫做临界资源。多个进程争抢同一个临界资源,抢到了可以运行,没抢到就无法运行。互斥就是争夺临界资源进程的间接制约关系。 例如多个打印线程争夺一台打印机资源,进程间就形成了互斥。
在一些同时运行的线程需要共享数据时,每个线程就必须要考虑与他一起共享数据的线程的状态与行为,否则的话就不能保证共享数据的一致性,从而也就不能保证程序的正确性
我们观察以下Share类
public class Share{
int index = 0;
char[] data = new char[6];
public void push(char c)
{
data[index] = c;
index++;
}
public char pop()
{
index--;
return data[index];
}
}
当有同时有两个线程A和B使用了Share类的一个实例时,在某一时刻,A要往堆栈里push数据,而B则要从堆栈中pop数据:
在该事件发生前,堆栈中有两个字符
data = | a | c | | | | | index = 2
A执行到push中的第一条语句data[index] = ‘r’ 时
data = | a | c | r | | | | index = 2
A还没有执行index++语句,A被B中断,B执行pop()方法,返回了 ‘c’
data = | a | c | r | | | | index = 1
随后A继续执行index++语句
data = | a | c | r | | | | index = 2
最后的结果是A再次往堆栈里push数据的时候,r会被覆盖,相当于r最终也没有被添加到堆栈中去
产生这种问题的原因是对共享资源访问的不完整。
解决方法
为了解决这种问题,需要寻找一种机制来保证对共享数据操作的完整性,这种完整性称为共享数据操作的同步,共享数据叫做条件变量
在Java语言中,引入了“对象互斥锁”的概念(又称为监视器、管程)来实现不同线程对共享数据操作的同步。 “对象互斥锁”阻止多个线程同时访问同一个条件变量
在Java语言中,有两种方法可以实现“对象互斥锁”:
(1)用关键字volatile来声明一个共享数据(变量)
(2)用关键字synchronized来声明一个操作共享数据的方法或一段代码
一般情况下,都使用synchronized关键字在方法的层次上实现对共享资源操作的同步,很少使用volatile关键字声明共享变量
每个 synchronized 方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。如果一个程序内有两个或以上的方法使用synchronized标志,则它们在同一个“对象互斥锁”管理之下
如以下Share类,添加了互斥锁后,就不会出现之前的状况了
public class Share{
int index = 0;
char[] data = new char[6];
public synchronized void push(char c)
{
data[index] = c;
index++;
}
public synchronized char pop()
{
index--;
return data[index];
}
}