用一个卖票的例子来示范多线程操作一个对象时,同步锁的使用。
先新建一个SaleRunnable类实现Runnable接口,用于表示“卖票”过程:
class SaleRunnable implements Runnable {
private int ticket = 20;
@Override
public void run() {
while(ticket > 0) {
Log.d("Test", Thread.currentThread().getName() + "卖出了第" + (20-ticket+1) + "张票");
ticket--;
}
}
}
如上,一共有20张票,每卖出一张票,则打印卖出此票的线程名和票的序号。这个代码里没有加锁。
然后在主程序创建三个线程,传入同一个SaleRunnable对象,三个线程名分别为老王,老张,老李:
SaleRunnable r = new SaleRunnable();
Thread t1 = new Thread(r,"老王");
Thread t2 = new Thread(r,"老张");
Thread t3 = new Thread(r,"老李");
然后调用线程的start()方法启动三个线程:
t1.start();
t2.start();
t3.start();
运行结果如下:
08-26 12:55:56.011 28300-28315/? D/Test: 老王卖出了第1张票
08-26 12:55:56.011 28300-28315/? D/Test: 老王卖出了第2张票
08-26 12:55:56.011 28300-28315/? D/Test: 老王卖出了第3张票
08-26 12:55:56.011 28300-28315/? D/Test: 老王卖出了第4张票
08-26 12:55:56.011 28300-28315/? D/Test: 老王卖出了第5张票
08-26 12:55:56.011 28300-28315/? D/Test: 老王卖出了第6张票
08-26 12:55:56.011 28300-28315/? D/Test: 老王卖出了第7张票
08-26 12:55:56.011 28300-28315/? D/Test: 老王卖出了第8张票
08-26 12:55:56.011 28300-28315/? D/Test: 老王卖出了第9张票
08-26 12:55:56.011 28300-28315/? D/Test: 老王卖出了第10张票
08-26 12:55:56.011 28300-28315/? D/Test: 老王卖出了第11张票
08-26 12:55:56.011 28300-28316/? D/Test: 老张卖出了第12张票
08-26 12:55:56.011 28300-28316/? D/Test: 老张卖出了第13张票
08-26 12:55:56.011 28300-28316/? D/Test: 老张卖出了第14张票
08-26 12:55:56.011 28300-28316/? D/Test: 老张卖出了第15张票
08-26 12:55:56.011 28300-28316/? D/Test: 老张卖出了第16张票
08-26 12:55:56.011 28300-28316/? D/Test: 老张卖出了第17张票
08-26 12:55:56.011 28300-28316/? D/Test: 老张卖出了第18张票
08-26 12:55:56.011 28300-28316/? D/Test: 老张卖出了第19张票
08-26 12:55:56.011 28300-28316/? D/Test: 老张卖出了第20张票
08-26 12:55:56.011 28300-28315/? D/Test: 老王卖出了第12张票
可见其结果是乱的,重复运行多次,结果不一,但是都没实现正常的卖票效果。为什么呢,因为三个线程在并行操作同一个对象,所以会出现老张的线程和老王的线程都卖出了第12张票的情况,但是这显然是不对的。
怎么办呢,修改下SaleRunnable类,加上锁试试:
class SaleRunnable implements Runnable {
private int ticket = 20;
@Override
public void run() {
while(ticket > 0) {
synchronized(this) {//把锁加在这里行不行呢?
Log.d("Test", Thread.currentThread().getName() + "卖出了第" + (20 - ticket + 1) + "张票");
ticket--;
}
}
}
}
如上面所示,把锁加在判断条件里面,运行结果:
08-26 13:15:08.621 15256-15271/? D/Test: 老王卖出了第1张票
08-26 13:15:08.621 15256-15271/? D/Test: 老王卖出了第2张票
08-26 13:15:08.621 15256-15271/? D/Test: 老王卖出了第3张票
08-26 13:15:08.621 15256-15271/? D/Test: 老王卖出了第4张票
08-26 13:15:08.621 15256-15271/? D/Test: 老王卖出了第5张票
08-26 13:15:08.621 15256-15271/? D/Test: 老王卖出了第6张票
08-26 13:15:08.621 15256-15271/? D/Test: 老王卖出了第7张票
08-26 13:15:08.621 15256-15271/? D/Test: 老王卖出了第8张票
08-26 13:15:08.621 15256-15271/? D/Test: 老王卖出了第9张票
08-26 13:15:08.621 15256-15271/? D/Test: 老王卖出了第10张票
08-26 13:15:08.621 15256-15271/? D/Test: 老王卖出了第11张票
08-26 13:15:08.621 15256-15271/? D/Test: 老王卖出了第12张票
08-26 13:15:08.621 15256-15271/? D/Test: 老王卖出了第13张票
08-26 13:15:08.621 15256-15271/? D/Test: 老王卖出了第14张票
08-26 13:15:08.621 15256-15271/? D/Test: 老王卖出了第15张票
08-26 13:15:08.621 15256-15271/? D/Test: 老王卖出了第16张票
08-26 13:15:08.621 15256-15271/? D/Test: 老王卖出了第17张票
08-26 13:15:08.621 15256-15271/? D/Test: 老王卖出了第18张票
08-26 13:15:08.621 15256-15271/? D/Test: 老王卖出了第19张票
08-26 13:15:08.621 15256-15271/? D/Test: 老王卖出了第20张票
08-26 13:15:08.631 15256-15273/? D/Test: 老李卖出了第21张票
08-26 13:15:08.631 15256-15272/? D/Test: 老张卖出了第22张票
结果看,虽然卖票顺序对了,但卖多了也是不正常的,再改下:
class SaleRunnable implements Runnable {
private int ticket = 20;
@Override
public void run() {
synchronized(this){//把锁加在循环判断外面,行不行呢?
while(ticket > 0) {
Log.d("Test", Thread.currentThread().getName() + "卖出了第" + (20 - ticket + 1) + "张票");
ticket--;
}
}
}
}
运行结果:
08-26 13:31:41.061 31687-31724/? D/Test: 老王卖出了第1张票
08-26 13:31:41.061 31687-31724/? D/Test: 老王卖出了第2张票
08-26 13:31:41.061 31687-31724/? D/Test: 老王卖出了第3张票
08-26 13:31:41.061 31687-31724/? D/Test: 老王卖出了第4张票
08-26 13:31:41.061 31687-31724/? D/Test: 老王卖出了第5张票
08-26 13:31:41.061 31687-31724/? D/Test: 老王卖出了第6张票
08-26 13:31:41.061 31687-31724/? D/Test: 老王卖出了第7张票
08-26 13:31:41.061 31687-31724/? D/Test: 老王卖出了第8张票
08-26 13:31:41.061 31687-31724/? D/Test: 老王卖出了第9张票
08-26 13:31:41.061 31687-31724/? D/Test: 老王卖出了第10张票
08-26 13:31:41.061 31687-31724/? D/Test: 老王卖出了第11张票
08-26 13:31:41.061 31687-31724/? D/Test: 老王卖出了第12张票
08-26 13:31:41.061 31687-31724/? D/Test: 老王卖出了第13张票
08-26 13:31:41.061 31687-31724/? D/Test: 老王卖出了第14张票
08-26 13:31:41.061 31687-31724/? D/Test: 老王卖出了第15张票
08-26 13:31:41.061 31687-31724/? D/Test: 老王卖出了第16张票
08-26 13:31:41.061 31687-31724/? D/Test: 老王卖出了第17张票
08-26 13:31:41.061 31687-31724/? D/Test: 老王卖出了第18张票
08-26 13:31:41.061 31687-31724/? D/Test: 老王卖出了第19张票
08-26 13:31:41.061 31687-31724/? D/Test: 老王卖出了第20张票
结果是没错,但是票全让老王给卖了,这也不正常,因为我们把锁加在整个卖票操作外面了,一旦老王开始卖票,其他人都没法卖了。再改:
class SaleRunnable implements Runnable {
private int ticket = 20;
@Override
public void run() {
while(true) {//判断条件做了修改
synchronized(this) {//锁加在循环判断里面,只有满足条件才能执行卖票操作
if (ticket > 0) {
Log.d("Test", Thread.currentThread().getName() + "卖出了第" + (20 - ticket + 1) + "张票");
ticket--;
} else {
break;
}
}
try {//这里的操作是为了方便线程间自动切换,如果不加的话,可能结果也是票全让老王线程给卖了
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
这样修改后结果正常了:
08-26 13:43:24.181 12176-12199/? D/Test: 老王卖出了第1张票
08-26 13:43:24.181 12176-12200/? D/Test: 老张卖出了第2张票
08-26 13:43:24.181 12176-12201/? D/Test: 老李卖出了第3张票
08-26 13:43:27.181 12176-12199/?D/Test: 老王卖出了第4张票
08-26 13:43:27.181 12176-12200/?D/Test: 老张卖出了第5张票
08-26 13:43:27.181 12176-12201/?D/Test: 老李卖出了第6张票
08-26 13:43:30.181 12176-12199/?D/Test: 老王卖出了第7张票
08-26 13:43:30.181 12176-12200/?D/Test: 老张卖出了第8张票
08-26 13:43:30.181 12176-12201/?D/Test: 老李卖出了第9张票
08-26 13:43:33.181 12176-12199/?D/Test: 老王卖出了第10张票
08-26 13:43:33.181 12176-12200/?D/Test: 老张卖出了第11张票
08-26 13:43:33.181 12176-12201/?D/Test: 老李卖出了第12张票
08-26 13:43:36.181 12176-12199/?D/Test: 老王卖出了第13张票
08-26 13:43:36.181 12176-12200/?D/Test: 老张卖出了第14张票
08-26 13:43:36.191 12176-12201/?D/Test: 老李卖出了第15张票
08-26 13:43:39.181 12176-12199/?D/Test: 老王卖出了第16张票
08-26 13:43:39.181 12176-12200/?D/Test: 老张卖出了第17张票
08-26 13:43:39.191 12176-12201/?D/Test: 老李卖出了第18张票
08-26 13:43:42.181 12176-12199/?D/Test: 老王卖出了第19张票
08-26 13:43:42.181 12176-12200/?D/Test: 老张卖出了第20张票
因此多个线程对同一个线程进行操作时,加锁是有效的解决冲突的方式,但是加到哪里一定要多多思考。