多线程基础(三)-多线程并发安全问题
当多个线程并发操作同一资源时,由于线程切换实际不可控会导致操作逻辑执行顺序出现混乱,严重时会导致系统瘫痪。例如下面的代码
public class SyncDemo {
public static void main(String[] args) {
Table table = new Table();
Thread t1 = new Thread(){
public void run(){
while(true){
int bean = table.getMice();
Thread.yield();
System.out.println(getName()+":"+mice);
}
}
};
Thread t2 = new Thread(){
public void run(){
while(true){
int bean = table.getMice();
Thread.yield();
System.out.println(getName()+":"+mice);
}
}
};
t1.start();
t2.start();
}
}
class Table{
private int mice = 20;
public int getMice(){
if(mice == 0){
throw new RuntimeException("没有饭了!"); //①
}
/*
* 当一个线程执行到Thread的静态方法yield后,该线程会主动放弃本次CPU时间片,
* 回到RUNNABLE状态,等待线程调度再次分配时间片
* 注:线程没有方法可以主动获取时间片。但可以主动放弃时间片
*/
Thread.yield(); //暂停当前正在执行的线程对象,并执行其他线程。
return mice--;
}
}
以上代码存在并发安全问题,若mice==0时线程1进行到①时CPU时间用尽,那么线程2也将进入if语句,最后进行mice–。最终线程1也将进行mice–,会导致mice变为负数,程序也进入了死循环。
此处,使用 synchronized 来解决多线程安全问题。
以上代码可以改为:
public synchronized int getMice(){
if(mice == 0){
throw new RuntimeException("没有饭了!");
}
Thread.yield();
return mice--;
}
当一个方法被synchronized修饰后,该方法变成同步方法。
即:多个线程无法同时在该方法内部运行。
将并发操作改为同步操作可以解决并发安全问题。
在方法上使用synchronized,那么同步监视器对象就是当前方法所属对象。即:方法中看到的this
有效的缩小同步范围可以在保证并发安全的前提下尽可能的提高并发效率
同步块可以更准确的控制需要同步运行的代码片段
语法:
synchronized(同步监视器对象){
需要同步运行的代码片段
}
使用同步块时需要注意,必须保证多个线程看到的同步监视器对象是[同一个]时,这些线程运行里面的代码才是同步的。静态方法若使用synchronized修饰后,那么该方法一定具有同步效果。静态方法所使用的同步监视器对象为其方法所属类的类对象。
互斥锁
当synchronized修饰多段代码,但是它们的同步监视器对象是同一个时,这些代码之间就是互斥的。 即:多个线程不能同时运行这些方法