线程的同步与死锁

在多线程中,同步与死锁概念很重要,在本章中必须了解以下几点:

1)哪里需要同步。

2)如何实现同步,了解代码即可。

3)及实现同步后有哪些副作用。

代码并不要求可以完整编写,但是概念必须清楚。

具体内容

1.1问题引出

  以买火车票为例,不管多少地方可以买火车票,最终一趟列车的车票数量是固定的,如果把各个售票点理解为线程的话,则所有线程应该共同拥有同一份票数。

package Thread1;
class MyThread implements Runnable{
    private int ticket = 5 ;    // 假设一共有5张票
    public void run(){
        for(int i=0;i<100;i++){
            if(ticket>0){    // 还有票
                System.out.println("卖票:ticket = " + ticket-- );
            }
        }
    }
};
public class demo1{
    public static void main(String args[]){
        MyThread mt = new MyThread() ;    // 定义线程对象
        Thread t1 = new Thread(mt) ;    // 定义Thread对象
        Thread t2 = new Thread(mt) ;    // 定义Thread对象
        Thread t3 = new Thread(mt) ;    // 定义Thread对象
        t1.start() ;
        t2.start() ;
        t3.start() ;
    }
};

  运行结果:

卖票:ticket = 5
卖票:ticket = 3
卖票:ticket = 2
卖票:ticket = 1
卖票:ticket = 4

  因为网络操作可能会有延迟,所以这里增加一个延迟操作sleep();修改如下;

package Thread1;
class MyThread implements Runnable{
    private int ticket = 5 ;    // 假设一共有5张票
    public void run(){
        for(int i=0;i<100;i++){
            if(ticket>0){    // 还有票
                try{
                    Thread.sleep(300) ;    // 加入延迟
                }catch(InterruptedException e){
                    e.printStackTrace() ;
                }
                System.out.println("卖票:ticket = " + ticket-- );
            }
        }
    }
};
public class demo1{
    public static void main(String args[]){
        MyThread mt = new MyThread() ;    // 定义线程对象
        Thread t1 = new Thread(mt) ;    // 定义Thread对象
        Thread t2 = new Thread(mt) ;    // 定义Thread对象
        Thread t3 = new Thread(mt) ;    // 定义Thread对象
        t1.start() ;
        t2.start() ;
        t3.start() ;
    }
};

  运行结果:

卖票:ticket = 5
卖票:ticket = 3
卖票:ticket = 4
卖票:ticket = 2
卖票:ticket = 1
卖票:ticket = 0
卖票:ticket = -1

  此时结果发现卖出的票数成负数,程序代码出现问题。

  例如,在线程2取出数据,但是还没有减一送回去之前,线程n也取出了这个数据,也执行了减一操作,这样这个数据同时执行了两次减一操作,导致数据为负。

  问题解决

  要解决这种问题就要使用同步,所谓同步就是指多个操作在同一时间段内只能有一个线程进行,其他线程要等这个线程完成之后才能继续执行

2. 使用同步解决问题。

  要想解决资源共享的同步操作问题,可以使用同步代码块同步方法两种方式完成。

2.1同步代码块

  之前已经介绍过代码块分四种

  1)普通代码块,直接定义在方法中。

  2)构造块,是直接定义在类中,优先于构造方法执行,重复调用

  3)静态块,是使用static关键字声明的,优先于构造块执行,只执行一次

  4)同步代码块,使用synchronized关键字声明的代码块,叫做同步代码块。

  syschronized关键字相关知识:

java线程同步: synchronized详解(转)

  同步代码块的格式:

synchronized(同步对象){
          需要同步的代码。          
}

  同步的时候必须指明同步的对象一般情况下,会将当前对象作为同步对象,使用this表示

package Thread1;
class MyThread implements Runnable{
    private int ticket = 5 ;    // 假设一共有5张票
    public void run(){
        for(int i=0;i<100;i++){
            synchronized(this){    // 要对当前对象进行同步
                if(ticket>0){    // 还有票
                    try{
                        Thread.sleep(300) ;    // 加入延迟
                    }catch(InterruptedException e){
                        e.printStackTrace() ;
                    }
                    System.out.println("卖票:ticket = " + ticket-- );
                }
            }
        }
    }
};
public class demo1{
    public static void main(String args[]){
        MyThread mt = new MyThread() ;    // 定义线程对象
        Thread t1 = new Thread(mt) ;    // 定义Thread对象
        Thread t2 = new Thread(mt) ;    // 定义Thread对象
        Thread t3 = new Thread(mt) ;    // 定义Thread对象
        t1.start() ;
        t2.start() ;
        t3.start() ;
    }
};

  运行结果:

卖票:ticket = 5
卖票:ticket = 4
卖票:ticket = 3
卖票:ticket = 2
卖票:ticket = 1

  从运行结果可以发现,程序加入了同步操作,所以不会产生负数的情况,但是程序的执行效率明显降低很多。

  2.2同步方法

  使用了synchronized声明的方法为同步方法。

  同步方法定义格式:

synchronized 方法返回值类型   方法名称(参数列表)
{
}

  同步方法解决如下:

package Thread1;
class MyThread implements Runnable{
    private int ticket = 5 ;    // 假设一共有5张票
    public void run(){
        for(int i=0;i<100;i++){
            this.sale() ;    // 调用同步方法
        }
    }
    public synchronized void sale(){    // 声明同步方法
        if(ticket>0){    // 还有票
            try{
                Thread.sleep(300) ;    // 加入延迟
            }catch(InterruptedException e){
                e.printStackTrace() ;
            }
            System.out.println("卖票:ticket = " + ticket-- );
        }

    }
};
public class demo1{
    public static void main(String args[]){
        MyThread mt = new MyThread() ;    // 定义线程对象
        Thread t1 = new Thread(mt) ;    // 定义Thread对象
        Thread t2 = new Thread(mt) ;    // 定义Thread对象
        Thread t3 = new Thread(mt) ;    // 定义Thread对象
        t1.start() ;
        t2.start() ;
        t3.start() ;
    }
};

运行结果:

卖票:ticket = 5
卖票:ticket = 4
卖票:ticket = 3
卖票:ticket = 2
卖票:ticket = 1

 3.3死锁

   资源共享时候需要进行同步操作。

  程序中过多的同步会产生死锁

  死锁一般情况下就是表示互相等待,是在程序运行时候出现的一种文体。

  下面通过一个代码来模拟一下死锁的概念,本代码读者只需了解其效果即可,对于代码不必深入了解。

package Thread1;
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 demo1 implements Runnable{
    private static Zhangsan zs = new Zhangsan() ;        // 实例化static型对象,保证两个操作的对象是同一个,这样syschronized(对象)才能起作用,能够起同步的作用
    private static Lisi ls = new Lisi() ;        // 实例化static型对象
    private boolean flag = false ;    // 声明标志位,判断那个先说话
    public void run(){    // 覆写run()方法
        if(flag){
            synchronized(zs){    // 同步张三,占用了zs对象
                zs.say() ;
                try{
                    Thread.sleep(500) ;
                }catch(InterruptedException e){
                    e.printStackTrace() ;
                }
                synchronized(ls){  //因为ls对象已经被t2占用了,所以同步ls的时候被阻塞,所以在等待
                    zs.get() ;
                }
            }
        }else{
            synchronized(ls){  //同步李四,占用了ls对象
                ls.say() ;
                try{
                    Thread.sleep(500) ;
                }catch(InterruptedException e){
                    e.printStackTrace() ;
                }
                synchronized(zs){  //因为zs已经被t1占用了,所以同步zs的时候被阻塞,所以等待
                    ls.get() ;
                }
            }
        }
    }
    public static void main(String args[]){
        demo1 t1 = new demo1() ;        // 控制张三
        demo1 t2 = new demo1() ;        // 控制李四
        t1.flag = true ;
        t2.flag = false ;
        Thread thA = new Thread(t1) ;
        Thread thB = new Thread(t2) ;
        thA.start() ;
        thB.start() ;
    }
};

运行结果:

张三对李四说:“你给我画,我就把书给你。”
李四对张三说:“你给我书,我就把画给你”

此时双方说完话都在等待对方回应,此时处于停滞状态,都在互相等待着对方回答。

posted @ 2016-07-15 21:26  美好的明天  阅读(2414)  评论(0编辑  收藏  举报