Java多线程之线程同步和死锁

来源:互联网 发布:华龙点金手机交易软件 编辑:程序博客网 时间:2024/05/18 00:09

Java多线程之线程同步和死锁

  • 前言:大家在看此文章时若有时间请先看前面的俩篇文章:Java多线程之多线程概述和俩种创建方式与Java多线程之线程状态转换、控制线程和线程同步。这三篇Java多线程文章均为总结性文章,适合自己复习使用。

(一).线程同步

  • 为什么要实现线程同步?当使用多个线程来访问同一个数据时,很容易“偶然”出现线程安全问题。我用通俗的语言来说明:我们知道Thread类实现了Runnable接口,新建的线程中每个线程都共享Thread类的成员变量(也就是共享资源)。那么很有可能当线程足够多时俩个线程同时取得成员变量,同时对成员变量进行操作,这时候数据紊乱,出现线程不安全现象。

  • 【经典的银行取钱问题】:假如你的银行账户里面有2000块钱,有一天你去银行柜台取钱,取1500,正在你办理的时候,你老婆去了取款机,她也取钱,事先没商量好谁取,所以她也想取1500。如果两个人都取走了1500,合起来就3000了,银行咋办???

  • 【个人理解】:所以我个人把线程同步理解为:多个线程同时访问同一共享资源,并且对共享资源进行操作,导致操作之后的资源混乱,不符合正确地流程所得结果。我们来看下面一个具体问题:

      以买车票为例,不管多少售票点可以买火车票,同一列火车的票数一定,也就是说把每个售票点理解为线程的话,所有线程共享一份票数,票数就是线程共享资源。卖票就是线程对共享资源的操作。

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取出数据,但是还没有把ticket减一的数据送回去之前,线程n也取出了这个数据执行了减一操作,这样这俩个数据都执行了俩次减一操作导致数据为负。

【问题解决】

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

  • 2.使用同步方法解决:要想解决共享资源的同步操作问题,需要使用同步代码块或是同步方法俩种方式来完成。

  • 【同步代码块】,使用synchronized关键字声明的代码块叫做同步代码块。格式如下:
        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
  • 从运行结果可以发现,程序加入了同步操作,所以不会产生负数的情况,但是程序的执行效率明显降低很多。

  • 【同步方法】:使用了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

(二).死锁

  • 当俩个线程在互相等待对方释放同步监视器时就会发生死锁,java虚拟机没有检测,也没有采取措施来处理死锁情况,所以多线程编程应该采取措施避免死锁出现。一旦出现死锁,整个程序既不会发生任何异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。
  • 资源共享时候需要进行同步操作,程序中过多的同步会产生死锁。
    这里写图片描述

  • 死锁一般情况下表示互相等待,线程之间互相需要对方的资源程序才能执行下去。

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型对象    private static Lisi ls = new Lisi() ;        // 实例化static型对象    private boolean flag = false ;    // 声明标志位,判断那个先说话    public void run(){    // 覆写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[]){        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() ;    }};
张三对李四说:“你给我画,我就把书给你。”李四对张三说:“你给我书,我就把画给你”
  • 此时双方说完话都在等待对方回应,此时处于停滞状态,都在互相等待着对方回答。
0 0
原创粉丝点击