synchronized线程同步探讨(购票问题为例)

先前的文章(java多线程)对java的多线程进行了一些总结,这里对synchronized线程同步进行一些进一步的研究。

以典型的购票问题为例,模拟购票,为了票数能够正确更新,线程需要同步,否则余票数量将有误,我们来看看synchronized的几种同步方案:

(1)代码块同步,正确方法:

这里有几个重要的地方,首先ticketCount要定义成static类型的,在多个Ticket对象之间共享,只初始化一次,否则每个Ticket对象都将ticketCount初始化为3,这就达不到模拟抢票的效果;其次synchronizd代码块,加锁的对象必须是同一个对象,否则还是无法线程同步,例如synchronized("")改成synchronized(this)就不能实现同步,因为锁加载不同对象(不同的Ticket对象),也没有实际意义;最后,这里执行线程时,当前的对象是Ticket对象,而不是thread对象,后面将会印证。


public class Ticket implements Runnable{

	private static  int ticketCount = 3;
	
	public static void main(String[] args) {
		int cntPerson = 5;
		while(cntPerson>0) {
			    StringBuilder threadName = new StringBuilder("person");
			    threadName.append(cntPerson);
			    
				Ticket ticket = new Ticket();
				Thread thread = new Thread(ticket, threadName.toString());
				thread.start();
				/*try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}*/
				cntPerson--;
		}

	}
	
	public  void run() {
		synchronized("") {
			if(ticketCount>0) {
				System.out.println(Thread.currentThread().getName()+": buy a ticket. The remaining tickets counts is "+ticketCount);
				ticketCount --;
			}
			else
				System.out.println(Thread.currentThread().getName()+": sold out. "+"The remaining tickets counts is "+ticketCount);
		}
	}
}

测试结果如下,正确同步:

如果将synchronized("")改成synchronized(this),再次测试,结果如下,可以看到同步失败,因为锁的对象不是同一个:

从测试结果我们也可以看到,线程执行的顺序并不是确定的, 这里说点题外话,如何让一个线程threadB在线程threadB运行之后运行呢,可以在A的run方法末尾调用join()方法加入B线程,即threadB.join(),这样既可达到目的。

(2) 使用同步方法,不能正确同步:

注意,这里并不是说synchronized不能够实现同步,而是示例总的方法不能实现同步,这是因为同步锁所的对象是this,不是对同一个对象加锁,因此无法同步。


public class Ticket implements Runnable{

	private static  int ticketCount = 3;
	
	public static void main(String[] args) {
		int cntPerson = 5;
		while(cntPerson>0) {
			    StringBuilder threadName = new StringBuilder("person");
			    threadName.append(cntPerson);
			    
				Ticket ticket = new Ticket();
				Thread thread = new Thread(ticket, threadName.toString());
				thread.start();
				/*try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}*/
				cntPerson--;
		}

	}
	
	public  synchronized void run() {
		if(ticketCount>0) {
			System.out.println(Thread.currentThread().getName()+": buy a ticket. The remaining tickets counts is "+ticketCount);
			ticketCount --;
		}
		else
			System.out.println(Thread.currentThread().getName()+": sold out. "+"The remaining tickets counts is "+ticketCount);

	}
}

 (3)静态同步方法,能够正确同步:

我们对示例(2)中的同步方法稍作改动,这里同步方法buyTicket采用静态方法(注意这里run不能直接为静态方法,否则报错),此时能够实现 同步,因为同步方法锁的对象是字节码,是竞争同一个锁。


public class Ticket implements Runnable{

	private static  int ticketCount = 3;
	
	public static void main(String[] args) {
		int cntPerson = 5;
		while(cntPerson>0) {
			    StringBuilder threadName = new StringBuilder("person");
			    threadName.append(cntPerson);
			    
				Ticket ticket = new Ticket();
				Thread thread = new Thread(ticket, threadName.toString());
				thread.start();
				/*try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}*/
				cntPerson--;
		}

	}
	
	public  synchronized void run() {
		Ticket.buyTicket();
	}
	
	private synchronized static void buyTicket() {
		if(ticketCount>0) {
			System.out.println(Thread.currentThread().getName()+": buy a ticket. The remaining tickets counts is "+ticketCount);
			ticketCount --;
		}
		else
			System.out.println(Thread.currentThread().getName()+": sold out. "+"The remaining tickets counts is "+ticketCount);

	}
	
}

(4)同步块,正确同步(只有一个实现Runaable的类的实例 )

对(1中的代码)进行修改。这里只有一个ticket对象,因此ticketCount并不需要是静态变量,因为只有一个对象使用;其次synchronized加锁时,使用this,能够同步,说明不同线程竞争的锁对象相同(由此可见,锁并不是加在Thread的对象上,而而是实现Runnable接口的类的对象,也就是Ticket的对象,将类名打印出来,可以印证,见运行结果)。

在一个ticket对象的情况下,run修改为synchronized方法同样可以同步成功,看起来很美好,但是需要指出的是这种一个ticket对象的方式,个人理解也仅仅是对比和理解上的意义,对于模拟实际的购票意义并不是很大,因为实际应用中不大可能只有一个ticket对象。


public class Ticket implements Runnable{

	private  int ticketCount = 3;
	
	public static void main(String[] args) {
		int cntPerson = 5;
		Ticket ticket = new Ticket();
		while(cntPerson>0) {
			    StringBuilder threadName = new StringBuilder("person");
			    threadName.append(cntPerson);
			    
				Thread thread = new Thread(ticket, threadName.toString());
				thread.start();
				/*try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}*/
				cntPerson--;
		}

	}
	
	public  void run() {
		synchronized(this) {
			String name = this.getClass().getName();
			System.out.println(name);
			if(ticketCount>0) {
				System.out.println(Thread.currentThread().getName()+": buy a ticket. The remaining tickets counts is "+ticketCount);
				ticketCount --;
			}
			else
				System.out.println(Thread.currentThread().getName()+": sold out. "+"The remaining tickets counts is "+ticketCount);
		}
		
	}
	
	
}

测试结果:

 

阅读更多
个人分类: java 多线程
上一篇SpringMVC实例及测试
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭