在单线程程序中,每次只能做一件事情,后面的事情需要等待前面的事情完成后才可以进行,但是如果使用多线程程序,就会发生两个线程抢占资源的问题,如两个人同时说话,两个人同时过一个独木桥等,所以在多线程编程中需要放置这些资源访问的冲突。Java提供了线程同步的机制来防止资源访问的冲突。
线程安全
在实际开发中,使用多线程程序的情况很多,如银行排号系统,火车站售票系统等,这种多线程的程序通常会发生问题,以火车站售票系统为例,在代码中判断当前票数是否大于0,如果大于0则执行将该票出售给乘客的功能,但当两个线程同时访问这段代码时,假设只剩一张票,第一个线程将票售出,与此同时第二个线程也已经执行完成判断是否有票的操作,并得出结论票数大于0,于是它也执行售出操作,这样就会产生负数。所以在编写多线程程序时,应该考虑到线程安全问题。实质上线程安全问题来源于两个线程同时存取单一对象的数据。
从上面的例子中可以看出,最后打印出的票数为负数,这样就出现了问题。这是由于同时创建了4个线程,这4个线程执行run方法,在num变量为1时,四个线程都对num变量有储存功能,当线程1执行完,还没有做递减操作,就调用了sleep方法进入就绪状态,这使其他线程中都进入了run方法,发现num变量依然大于0,但此时线程1休眠时间已到,将num值递减,同时其他线程也都对num进行递减操作,就出现了负数。虽然这个例子中我们将num递减的操作放在sleep方法上面就可以解决问题。但是我们现实中会遇到很多相同的问题。那么这种问题怎么结局呢?
线程同步机制
基本上所有解决多线程资源冲突问题的方法都是采用给定时间只允许一个线程访问共享资源,这时就需要给共享资源上一道锁,也就是线程锁。就像你上公共厕所,不可能不锁门。一样的道理。
锁的作用是为了防止不同的线程在同一时间访问同一个代码块,如果在同一时间访问同一代码块,有可能出现“死锁”,“死锁”其实就是两个线程运行时都在等待对方的锁,从而造成了程序的停滞。
同步块
在Java中提供了同步机制,可以有效地防止资源冲突,同步机制使用synchronized关键字,使用该关键字包含的代码块称为同步块,也称为临界区,语法如下:
synchronized(Object){
}
通常将共享资源的操作放置在synchronized定的区域内,这样当其他线程也获取这个锁时,必须等待锁被释放时才能进入该区域。Object为任意一个对象,没个对象都存在一个标志位,并具有两个值,分别是0和1。一个线程运行到同步块时首先检查该对象的标志位,如果为0状态,表名此同步块中存在其他线程在运行,这时该线程处于就绪状态,知道处于同步块中的线程执行完同步块的代码为止,这时该对象的标志位被设置为1,该线程才能执行同步块中的代码,并将Object对象的标志位设置为0,防止其他线程执行同步块中的代码。
可以看到上面的代码正常运行,并且没有出现负数。
同步方法
同步方法就是在方法前面使用synchronized关键字修饰的方法,语法如下:
synchronized void f(){ }
当某个对象调用了同步方法时,该对象上的其他同步方法必须等待该同步方法执行完毕后才能被执行。必须将每个能访问共享资源的方法修饰为synchronized,否则就会报错。
我们把上面例子中的代码修改一下。
运行结果与之前的效果一样。这两种方法大家可以通过实际情况按需要选择。