同步关键词synchronized
当多个线程共享一个数据时,如果处理不当,很容易出现线程的安全隐患,比如丢失修
改、不可重复读、读脏数据等,所以多线程编程时经常要解决线程同步问题。Java的多线程
机制中提供了关键字synchronized来声明同步方法。一个类中任何方法都可以设计成为
synchronized 方法以防止多线程的数据崩溃。当某个对象用synchronized 修饰时,表明该对象在任何时刻都只能由一个线程访问。当一个线程进入synchronized 方法后,能保证在任何其他线程访问这个方法之前完成自己的执行。如果一个线程试图访问一个已经启动的
synchronized 方法,则这个线程必须等待,直到已启动线程执行完毕,再释放这个synchronized 方法。
声明synchronized方法的一般格式:
[ modifier] synchronized returnType methodName ([parameterList] )
{/*方法体*/}
synchronized 关键字除了可以放在方法声明中表示整个方法为同步方法外,还可以放在
段代码的前面限制它的执行,如:
[ modifier] returnType methodName( [ parameterList])
synchronized(this){/*代码*/}
另外,如果synchronized 用在类声明中,则表明该类中的所有方法都是syechronized的。
Java使用锁机制保证同步代码块或方法的“原子性”,即保持整体性,不可分隔。
Java中每个对象都有一个内置锁,只有当对象具有同步代码时,内置锁才会起作用。当
进入一个同步方法时,线程自动获得方法所在类的当前实例(this)相关的锁,即给this 对象
加锁。因为一个对象只有一个锁,加锁成功才能够执行同步方法,加锁不成功的线程进入阻
塞状态(在锁池等待)。当同步代码块或同步方法执行完毕后,同步对象上的锁就被解除在锁池等待的线程就有机会获取该对象的锁。
被加锁的对象也被称为“同步监视器”,它负责监视执行同步代码的线程是否具有它的锁。
注意:锁不属于线程,而是属于对象,一个线程可以拥有多个对象的锁,而只有同一个对象的锁之间才会存在互斥。
例如,我们在超市售货系统中,代码会先判断某种商品库存数量是否大于0,如果大于
0就出售。但当两个线程同时访问这段代时,如果某种商品只有1件库存了,则第一个线程将该商品卖出,第二个线程也已经执行完判断是否有库存的操作,于是也出售,这样商品就重复出售了,这就是丢失修改的情况。为了避免这种情况发生,在编写多线程程序时,应该使用同步机制。
模拟商品销售功能,示例代码如下:
public class Main {
public static void main(String[] args) {
Goods runGoods=new Goods();
Thread th1=new Thread(runGoods,"窗口1");
Thread th2=new Thread(runGoods,"窗口2");
Thread th3=new Thread(runGoods,"窗口3");
Thread th4=new Thread(runGoods,"窗口4");
th1.start();
th2.start();
th3.start();
th4.start();
}
}
public class Goods implements Runnable {
private int goodsCount=5; //商品库存量,多个线程都访问该共享变量
public void sellGoods(){
if(goodsCount>0){
System.out.println(Thread.currentThread().getName()+"正在出售第"+(5-goodsCount+1)+"件库存商品,"+"还剩"+(--goodsCount)+"库存");
}
else {
System.out.println("该商品已经买完!");
}
}
public void run(){
while(goodsCount>0){ //线程不断循环出售商品
sellGoods();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果:
窗口2正在出售第1件库存商品,还剩4库存
窗口1正在出售第1件库存商品,还剩3库存
窗口4正在出售第4件库存商品,还剩1库存
窗口3正在出售第3件库存商品,还剩2库存
窗口1正在出售第5件库存商品,还剩0库存
窗口3正在出售第5件库存商品,还剩-1库存
窗口4正在出售第5件库存商品,还剩0库存
从运行结果来看,这些线程同时访问共享数据,没有进行同步控制。可以通过对方法同步或对代码块进行同步来改进。
1.对方法进行同步,在定义方法时上加上关键词synchronized:
public synchronized void sellGoods(){
if(goodsCount>0){
System.out.println(Thread.currentThread().getName()+"正在出售第"+(5-goodsCount+1)+"件库存商品,"+"还剩"+(--goodsCount)+"库存");
}
else {
System.out.println("该商品已经买完!");
}
}
2.对代码块进行同步,在需要同步的代码块前加上关键词synchronized(其中this是指类自身):
public void sellGoods(){
synchronized (this) {
if (goodsCount > 0) {
System.out.println(Thread.currentThread().getName() + "正在出售第" + (5 - goodsCount + 1) + "件库存商品," + "还剩" + (--goodsCount) + "库存");
} else {
System.out.println("该商品已经买完!");
}
}
}
同步以后,商品不会被重复出售。
运行结果:
窗口1正在出售第1件库存商品,还剩4库存
窗口3正在出售第2件库存商品,还剩3库存
窗口4正在出售第3件库存商品,还剩2库存
窗口2正在出售第4件库存商品,还剩1库存
窗口4正在出售第5件库存商品,还剩0库存