9.6 同步与死锁
一个多线程的程序如果是通过Runnabl接口实现的,则意味着类中的属性将被多个线程共享,那么这样一来就会造成一种问题,如果这多个线程操作同一资源时就有可能出现资源的同步问题。例如, 卖票程序,如果多个线程同时操作时就有可能出现卖出票为负数的问题。
9.6.1 问题的产生
范例:通过Runnable接口实现多线程,并产生3个线程对象,同时卖5张票。
class MyThread implements Runnable{
private int ticket = 5;
public void run(){
for (int i = 0; i < 7; i++){
if (ticket > 0){
try{
Thread.sleep(3000);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("卖票: ticket = " + ticket--);
}
}
}
}
public class SyncDemo01{
public static void main(String args[]){
MyThread my = new MyThread();
new Thread(my, "Thread-A").start();
new Thread(my, "Thread-B").start();
new Thread(my, "Thread-C").start();
}
}
运行结果截图
卖票: ticket = 2
卖票: ticket = 1
卖票: ticket = 0
<span style="color:#ff0000;">卖票: ticket = -1</span>
-------------------------------------------------
从程序的运行结果中可以发现,程序中加入了延迟操作,所以在运行的最后出现了负数的情况,那么为什么现在会产生这样的问题呢?
上面程序对于票数的操作步骤如下:
- 判断票数是否大于0,大于0则表示还有票可以卖
- 如果票数大于0,则将票卖出。
但是,代码中,在步骤1和步骤2之间加入了延迟操作,那么一个线程就有可能在还没有对票数进行减法操作之前,其他线程就已经将票数减少了,这样一来就会出现票数为负的情况。
如果想解决这样的问题,就必须使用同步。
所谓同步就是指多个操作在同一个时间段内只能有一个线程进行,其他线程要等待此线程完成之后才可以继续执行,如下图所示:
9.6.2 使用同步解决问题
解决资源共享的同步操作,可以使用同步代码块和同步方法两种方式完成。
- 同步代码块
所谓的代码块就是指使用“{}”括起来的一段代码,根据其位置和声明的不同,可以分为普通代码块、构造块、静态块3种,如果在代码块前面加上synchronized关键字,则此代码块就称为同步代码块。 其格式如下
synchronized(同步对象){
需要同步的代码;
}
*******从上面的格式可以看出,在使用同步代码块时必须制定一个同步对象,但一般都将当前对象(this)设置成同步对象!!!*******
范例:使用同步代码块解决的同步问题
class MyThread implements Runnable{
private int ticket = 5;
public void run(){
for (int i = 0; i < 7; i++){
synchronized(this){
if (ticket > 0){
try{
Thread.sleep(500);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("卖票: ticket = " + ticket--);
}
}
}
}
}
public class SyncDemo01{
public static void main(String args[]){
MyThread my = new MyThread();
new Thread(my, "Thread-A").start();
new Thread(my, "Thread-B").start();
new Thread(my, "Thread-C").start();
}
}
运行结果:
-------------------------------------------------
卖票: ticket = 5
卖票: ticket = 4
卖票: ticket = 3
卖票: ticket = 2
卖票: ticket = 1
-------------------------------------------------
将上面代码的取值和修改值的操作代码进行了同步,所以不会再出现卖出票为负数的情况了。
- 同步方法
除了可以将需要的代码设置成同步代码块外,也可以使用synchronized关键字将一个方法声明成同步方法。其格式如下:
synchronized 方法返回值 方法名称(参数列表){
方法体;
}
范例: 使用同步方法解决卖票出负数的情况
class MyThread implements Runnable{
private int ticket = 5;
public void run(){
for (int i = 0; i < 7; i++){
this.sale();
}
}
private synchronized void sale(){
if (ticket > 0){
try{
Thread.sleep(500);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("卖票:ticket = " + ticket--);
}
}
}
public class SyncDemo02{
public static void main(String args[]){
MyThread my = new MyThread();
new Thread(my, "Thread-A").start();
new Thread(my, "Thread-B").start();
new Thread(my, "Thread-C").start();
}
}
运行结果如下,与上面同步代码块实现了完全相同的功能。
-------------------------------------------------
卖票:ticket = 5
卖票:ticket = 4
卖票:ticket = 3
卖票:ticket = 2
卖票:ticket = 1
-------------------------------------------------
- 方法定义的完整格式
访问权限 {public|default|protected|private} [final] [static] [synchronized] 返回值类型|void 方法名称(参数类型 参数名称,…) [throws Exception1, Exception2] {
[return [返回值|返回调用处]]
}
9.6.3 死锁
同步可以保证资源共享操作的正确性,但是过多同步也会产生问题。例如, 现在张三想要李四的画,李四想要张三的书;张三对李四说“把你的画给我,我就给你书”,李四也对张三说了:“把你的书给我,我就给你画”,此时,张三在等着李四的答复,而李四也在等着张三的答复;那么这样下去最终结果就是,张三得不到李四的画,李四也得不到张三的书。这就是死锁的概念!!
所谓死锁就是指两个线程都在等待彼此先完成,造成了程序的停滞,一般程序的死锁都是在程序运行时出现的。
范例:死锁
class Zhangsan{
public void say(){
System.out.println(" 张三对李四说:“你给我画,我就把书给你。”");
}
public void get(){
System.out.println("张三得到画了!!");
}
}
class Lisi{
public void say(){
System.out.println("李四对张三说:“你给我书,我就把画给你。”");
}
public void get(){
System.out.println("李四得到书了!!");
}
}
public class ThreadDeadLock implements Runnable{
private static Zhangsan zs = new Zhangsan();
private static Lisi ls = new Lisi();
private boolean flag = false; //标记,用于判断哪个对象先执行
public void run(){
if(flag){
synchronized (zs){
zs.say();
try{
Thread.sleep(500);
}catch(InterruptedException e){
e.printStackTrace();
}
synchronized(ls){
zs.get();
}
}
}else{
synchronized(ls){
ls.say();
try{
Thread.sleep(500);
}catch(InterruptedException e){
e.printStackTrace();
}
synchronized(zs){
ls.get();
}
}
}
}
public static void main(String args[]){
ThreadDeadLock t1 = new ThreadDeadLock();
ThreadDeadLock t2 = new ThreadDeadLock();
t1.flag = true;
t2.flag = false;
new Thread(t1, "Thread-A").start();
new Thread(t2, "Thread-B").start();
}
}
程序运行结果:
-------------------------------------------------
张三对李四说:“你给我画,我就把书给你。”
李四对张三说:“你给我书,我就把画给你。”
[以下代码不再执行,程序进入死锁状态]
从程序的运行结果中可以发现,两个线程都在彼此等着对方的执行完成,这样,程序就无法向下继续执行,从而造成了死锁的现象。
- 关于同步与死锁, 多个线程共享同一资源时需要进行同步,以保证资源操作的完整性;但是过多的同步就有可能产生死锁。。。
关于线程的死锁、互锁、互斥锁, 以及它们产生的原因、排错方法等等要自己查资料看看!!!!!!!!!!!