多线程的同步和死锁

多线程同步和死锁

在多线程编程中,有可能会出现同时访问同一个资源的情况,这种资源可以是各种类型的的资源:一个变量、一个对象、一个文件、一个数据库表等,而当多个线程同时访问同一个资源的时候,就会存在一个问题:由于每个线程执行的过程是不可控的,所以很可能导致最终的结果与实际上的愿望相违背或者直接导致程序出错。

在售票员的案例中,多个线程访问的时候就会出现数据出错的情况

售票系统有两个个渠道, 网络购票,现场购票,下面模拟购票流程,然后我们启动两个线程代表网络和线程购票
我们定义两个个线程一起执行,
则会有可能出现数据错误, 如下图
这是因为当一个线程在还有一张票的时候进入到购票系统,但是在这儿cpu的时间段内没有走完程序,即票数没有-1, 然后cpu执行另一个线程,此时票数还是1,所以还会进入到购票流程,当这个线程执行完毕以后票数会变成0,cpu会调用第一次堵塞的线程, 此时票的数量为 0-1=-1 所以此时数据出错.

线程同步

可以用线程同步的方式解决上面的数据异常方法,有三种方法,分别为同步代码块,同步方法 ,Lock

当线程遇到同步代码块或者同步方法的时候,会先判断同步锁(一个对象)是否存在,如果存在,则会将同步锁加到这个线程上,执行程序,(如果程序没有执行完同步代码块的方法则这个同步锁不会被释放)  ,当另一个线程想要进入这个方法的时候会先判断一下同步锁是否存在,如果有,则进入执行,如果没有,则等待同步锁被释放,即保证了这个程序在某一时刻只能有一个线程去访问.

对象锁(同步锁)    :     任意对象,如果多个线程需要对某一个对象保持同步,则这些线程的对象锁要相同,锁住的不是变量,而是操作变量的方法,一个对象只拥有一个锁.类本身也有锁.

同步代码块

            synchronized(对象锁){
              线程要操作的共享数据
            }

            synchronized(obj){
            //对票数判断,大于0,可以出售,变量--操作
                if( ticket > 0){
                    try{
                       Thread.sleep(10);
                    }catch(Exception ex){}
                    System.out.println(Thread.currentThread().getName()+" 出售第 "+ticket--);
                }
            }

同步方法

同步方法的对象锁是当前对象  即 this     

静态方法的对象锁为当前类.class
        public  synchronized void payTicket(){  
            if( ticket > 0){
                try{
                   Thread.sleep(10);
                }catch(Exception ex){}
                System.out.println(Thread.currentThread().getName()+" 出售第 "+ticket--);
            }
        
     }

lock锁

lock所更灵活,但是需要自己手动释放锁

示例

        Lock ck = new ReentrantLock();
        public void run(){
            while(true){
            ck.lock();
            if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + "正在卖票:" + ticket--);
                }
                ck.unlock();
            }
        }

等待与唤醒(wait()、notify()、notifyAll())

这三个方法是 java.lang.Object 的 final native 方法,任何继承 java.lang.Object 的类都有这三个方法。它们是Java语言提供的实现线程间阻塞和控制进程内调度的底层机制.

三个方法的解释:

- wait() :等待,将正在执行的线程释放其执行资格 和 执行权,并存储到线程池中。

- notify():唤醒,唤醒线程池中被wait()的线程,一次唤醒一个,而且是任意的。

-  notifyAll(): 唤醒全部:可以将线程池中的所有wait() 线程都唤醒。

其实,所谓唤醒的意思就是让 线程池中的线程具备执行资格。必须注意的是,这些方法都是在 同步中才有效。同时这些方法在使用时必须标明所属锁,这样才可以明确出这些方法操作的到底是哪个锁上的线程。   

通过输入输出来演示等待和唤醒

有两个线程 input线程的作用是输入数据到对象,output作用是从对象中输出数据, 然后要求一次输入一次输出

  **基本过程**
  - 输入:赋值后,执行方法wait()等待,并且改变Resource的Tag值,唤醒输出

  - 输出:被唤醒后,判断Tag,如果不进入wait() 然后输出,改变Tag的值 , 输出完毕以后自己wait()  唤醒输入 notify()

   **注意** :1. 要给输入和输出同一个锁才能起到同步的作用  2. wait()和notify()需要用锁对象来调用,这样才知道唤醒或者休眠那个锁中的线程

**程序**

```
    main:
         public class ThreadDemo{
           public static void main(String[] args) {
             Resource r = new Resource();

             Input in = new Input(r);   // 要给输入和输出同一个锁才能起到同步的作用
             Output out = new Output(r);
             
             Thread tin = new Thread(in);
             Thread tout = new Thread(out);
             
             tin.start();
             tout.start();
           }
         }
    
 public class Resource {
      public String name;
      public String sex;
      public boolean flag = false;
     }

     /*
      *  输入的线程,对资源对象Resource中成员变量赋值
      *  一次赋值 张三,男
      *  下一次赋值 lisi,nv
      */
     public class Input implements Runnable {
      private Resource r ;
      
      public Input(Resource r){
        this.r = r;
      }
      
      public void run() {
        int i = 0 ;
        while(true){
          synchronized(r){
            //标记是true,等待
              if(r.flag){
                try{r.wait();}catch(Exception ex){}
              }
            
            if(i%2==0){
              r.name = "张三";
              r.sex = "男";
            }else{
              r.name = "lisi";
              r.sex = "nv";
            }
            //将对方线程唤醒,标记改为true
            r.flag = true;
            r.notify();   // wait()和notify()需要用锁对象来调用,这样才知道唤醒或者休眠那个锁中的线程
          }
          i++;
        }
      }

     }
     
     /*
      *  输出线程,对资源对象Resource中成员变量,输出值
      */
     public class Output implements Runnable {
      private Resource r ;
      
      public Output(Resource r){
        this.r = r;
      }
      public void run() {
        while(true){
          synchronized(r){  
            //判断标记,是false,等待
          if(!r.flag){
            try{r.wait();}catch(Exception ex){}
            }
          System.out.println(r.name+".."+r.sex);
          //标记改成false,唤醒对方线程
          r.flag = false;
          r.notify();
          }
        }
      }

     }
   

```

通过

死锁

当线程任务中出现了多个同步(多个锁)  时,如果同步中嵌套了其他的同步。这时容易引发一种现象:程序出现无限等待,这种现象我们称为死锁。这种情况能避免就避免掉。其中同步锁要是唯一锁(即整个程序中只有一个这种锁)

两个线程互相持有对象在等待的东西

死锁的四个必要条件

  • 互斥条件:一个资源每次只能被一个进程使用。
  • 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  • 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
  • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。 程序演示死锁
 public class DeadLock implements Runnable{
            private int i = 0;
            public void run(){
              while(true){
                if(i%2==0){
                  //先进入A同步,再进入B同步
                  synchronized(LockA.locka){
                    System.out.println("if...locka");
                    synchronized(LockB.lockb){
                      System.out.println("if...lockb");
                    }
                  }
                }else{
                  //先进入B同步,再进入A同步
                  synchronized(LockB.lockb){
                    System.out.println("else...lockb");
                    synchronized(LockA.locka){
                      System.out.println("else...locka");
                    }
                  }
                }
                i++;
              }
            }
           }
        
          public class DeadLockDemo {
            public static void main(String[] args) {
              DeadLock dead = new DeadLock();
              Thread t0 = new Thread(dead);
              Thread t1 = new Thread(dead);
              t0.start();
              t1.start();
            }
          }


          public class LockA {
            private LockA(){}
            
            public  static final LockA locka = new LockA();
          }

          
          public class LockB {
            private LockB(){}
            
            public static final LockB lockb = new LockB();
          }

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏java一日一条

深入分析Java线程中断机制

在平时的开发过程中,相信都会使用到多线程,在使用多线程时,大家也会遇到各种各样的问题,今天我们就来说说一个多线程的问题——线程中断。在java中启动线程非常容易...

372
来自专栏haifeiWu与他朋友们的专栏

FutureTask源码分析

FutureTask:一个可取消的异步任务执行类,这个类提供了Future接口的基本实现,主要有以下功能:

703
来自专栏微信公众号:Java团长

Java多线程编程

1)设计更复杂 虽然有一些多线程应用程序比单线程的应用程序要简单,但其他的一般都更复杂。在多线程访问共享数据的时候,这部分代码需要特别的注意。线程之间的交互往往...

1252
来自专栏desperate633

Java线程通信(Thread Signaling)利用共享对象实现通信忙等(busy waiting)wait(), notify() and notifyAll()信号丢失(Missed Sign

线程通信的目的就是让线程间具有互相发送信号通信的能力。 而且,线程通信可以实现,一个线程可以等待来自其他线程的信号。举个例子,一个线程B可能正在等待来自线程A...

772
来自专栏积累沉淀

Python快速学习第十一天--Python多线程

Python中使用线程有三种方式: 方法一:函数式 调用thread模块中的start_new_thread()函数来产生新线程。语法如下: thread...

1749
来自专栏小灰灰

Java并发学习之synchronized使用小结

synchronized工作原理及使用小结 为确保共享变量不会出现并发问题,通常会对修改共享变量的代码块用synchronized加锁,确保同一时刻只有一个线...

1997
来自专栏北京马哥教育

Python实现线程安全队列

作者:愤怒的屎壳螂 来源:http://blog.csdn.net/hit0803107/article/details/52876143 最近学习spa...

3557
来自专栏流柯技术学院

Python多线程学习

1、  函数式:调用thread模块中的start_new_thread()函数来产生新线程。如下例:

621
来自专栏Hongten

java多线程系列_线程的生命周期(4)

与人有生老病死一样,线程也同样要经历开始(等待)、运行、挂起和停止四种不同的状态。这四种状态都可以通过Thread类中的方法进行控制。下面给出了Thread类中...

462
来自专栏desperate633

Java并发之Slipped conditions什么是Slipped conditions一个关于Slipped conditions的具体例子

所谓Slipped conditions,就是说, 从一个线程检查某一特定条件到该线程操作此条件期间,这个条件已经被其它线程改变,导致第一个线程在该条件上执行了...

571

扫码关注云+社区