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() ;
    }
};
张三对李四说:“你给我画,我就把书给你。”
李四对张三说:“你给我书,我就把画给你”
  • 此时双方说完话都在等待对方回应,此时处于停滞状态,都在互相等待着对方回答。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值