先前的文章(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);
}
}
}
测试结果: