美文网首页
十、Java语言基础---多线程(同步,等待唤醒机制,Lock,

十、Java语言基础---多线程(同步,等待唤醒机制,Lock,

作者: SkyFireDragon | 来源:发表于2019-04-12 11:43 被阅读0次

    这些是笔者学习Java基础时的笔记,供后来者参考(学习是持之以恒的的事情,一定要坚持哟,切记!切记!!!)

    1、线程安全问题

    问题描述:当多个窗口(多个线程)同时售票,票为共享数据源;当不对窗口(线程)进行限制的时候,就容易造成线程安全问题。
    
    //下面的例子
    public class Ticket implements Runnable {
      private static int num = 100;
    
        @Override
        public void run() {
          while(true){
             if(num>0){
              System.out.println(Thread.currentThread().getName()+"...sale..."+num);
              num--;
            }else{
              break;
            }
          }
        }
    }
    
    public static void main(String[] args){
       Ticket ticket = new Ticket();
      Thread thread = new Thread(ticket);
      Thread thread1 = new Thread(ticket);
      Thread thread2 = new Thread(ticket);
    
      thread.start();
      thread1.start();
      thread2.start();
    }
    
    此时,运行代码,可能产生重票和跳票的问题,这就是多个线程同时操作相同资源,造成的线程不安全问题。
    

    2、解决线程安全问题(使用同步机制)

     (1)类锁和对象锁:
        1、类锁:在代码中的方法加static和synchronized的锁,或者synchroonized(xxx.class)
        2、对象锁:在代码中的非静态方法加了synchronized的锁,或者synchronized(this)的代码段;
        3、私有锁:在类内部声明一个私有属性如private Object lock,在需要加锁的代码段synchronized(lock)
    
    (2)同步代码块:
    
        synchronized(锁){ //锁可以为任意对象,但是需要保证多线程用的是同一把锁
          对同一个资源的操作语句。
        }
    
    (3)同步方法:
        <1>同步方法的锁:this;
    
    public class Ticket implements Runnable {
      private static int num = 100;
    
      @Override
      public void run() {
        while(true){
          synchronized (this){
            if(num > 0){
              System.out.println(Thread.currentThread().getName()+"...sale..."+num);
              num--;
            }else{
              break;
            }
          }
        }
      }
    }
    
     <2>静态同步方法的锁:类名.class,使用synchronized修饰需要同步的方法;
    
    public class Ticket implements Runnable {
      private static int num = 100;
    
      @Override
       public void run() {
         while(num>0){
           printTicket();
        }
      }
    
      private synchronized void printTicket(){
        if(num > 0){
          System.out.println(Thread.currentThread().getName()+"...sale..."+num);
          num--;
        }
      }
    }
    
    3、死锁问题
    
        同步机制解决了线程安全问题,同时也可能引入新的问题:线程死锁问题。
        问题描述:两个线程操作同一个资源,但是在该资源上添加了两个锁A,B,当其中一个线程获取了锁A,同时另一个线程获取了锁B,这时就造成了线程死锁。
        所以在平常开发过程中,为避免死锁,就要尽量避免同步代码块的嵌套。
    
    死锁实例:
    需求描述:用程序来描述以下情况:一手交钱一手交货。商家与顾客两人都是很小气的人,顾客买商家的东西,商家收顾客的前,顾客说:先给我货我再给你钱;商家说:先给我钱我再给你货。最好的结局就是各自得到各自的东西。
    
    代码:
    public class Customer extends Thread {
      public static Object money = new Object();
    
       @Override
        public void run() {
          synchronized (money){
            System.out.println("客户等商家给货");
            synchronized (Seller.goods){
            System.out.println("客户给商家钱");
          }
        }
      }
    }
    
    public class Seller extends Thread {
      public static Object goods = new Object();
    
      @Override
      public void run() {
        synchronized (goods){
          System.out.println("商家等客户给钱");
          synchronized (Customer.money){
            System.out.println("商家给客户活");
          }
        }
      }
    }
    
    public class DeadLockDemo {
      public static void main(String[] args){
        Thread t1 = new Customer();
        Thread t2 = new Seller();
        t1.start();
        t2.start();
      }
    }
    
    4、等待唤醒机制
    (1)当多个线程执行相同的任务,操作相同在资源的时候,使用加锁机制,同步线程没有问题。但当多线程的执行任务不同时,加锁机制就失效了;这时我们就使用等待唤醒机制,完成线程同步。
    (2)问题线程(两个问题,一读写资源不同步,二、线程不是交替执行(不是生产消费模型)):
    
    public class Resource {
      String name;
      String sex;
    }
    
    public class Input implements Runnable{
      Resource r;
      public Input(Resource r){
        this.r = r;
      }
    
      @Override
      public void run() {
        int x=0;
        while(true){
          if(x == 0){
            r.name = "张三";
            r.sex = "男";
          }else{
            r.name = "小红";
            r.sex = "女";
          }
          x=(x+1)%2;
        }
      }
    }
    
    public class Output implements Runnable{
    
      Resource r;
    
      public Output(Resource r){
        this.r = r;
      }
    
      @Override
      public void run() {
        while(true){
          System.out.println("---------name:"+r.name+" sex:"+r.sex);
        }
      }
    }
    
    public class ThreadDemo2 {
    
      public static void main(String[] args){
        Resource resource = new Resource();
        Thread thread = new Thread(new Input(resource));
        Thread thread1 = new Thread(new Output(resource));
        thread.start();
        thread1.start();
      }
    }
    
    (3)等待唤醒机制(必须在相同的锁中,等待唤醒机制才能有效)
      wait():放弃线程执行权,线程进入线程池
      notify():唤醒线程池中任意一个线程
      notifyAll():将线程池中所有线程都唤醒
    
    优化后的代码:
    public class Resource {
      String name;
      String sex;
      boolean flag;
    
      public synchronized void set(String name,String sex){
         if(flag){
          try {
            this.wait();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
        this.name = name;
        this.sex = sex;
        flag = true;
        this.notify();
      }
    
      public synchronized void print(){
        if(!flag){
          try {
            this.wait();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
    
        System.out.println("---------name:"+name+" sex:"+sex);
        flag = false;
        this.notify();
      }
    }
    
    public class Input implements Runnable{
    
      Resource r;
    
      public Input(Resource r){
        this.r = r;
      }
    
       @Override
       public void run() {
          int x=0;
          while(true){
            if(x == 0){
                r.name = "张三";
                r.sex = "男";
            }else{
                r.name = "小红";
                r.sex = "女";
            }
            x=(x+1)%2;
        }
      }
    }
    
    public class Output implements Runnable{
      Resource r;
    
      public Output(Resource r){
        this.r = r;
      }
    
      @Override
      public void run() {
        while(true){
          r.print();
        }
      }
    }
    
    (4)多生产多消费者的问题
        <1>当前等待线程被唤醒的时候,没有进行标记的判断,所以会多生产和唤醒本方线程(生产者唤醒生产者,消费者
    唤醒消费者)的可能。为了解决这个问题,我们将条件判断,换成while循环,将notify替换为notifyAll(),问题解决。
    
    (5)线程的结束
        <1>线程的stop方法已经过时,结束线程的一种方法是,将循环的标记设置为false;线程自动结束。
        <2>当线程的标记读不到时(即线程处于冻结状态的时候),调用interrupt()函数唤醒处于wait()、sleep()等冻结状态
    的线程,让其去读取标记,结束线程。
    
    (6)setDaemon()守护线程
      该方法必须在start方法调用之前调用。将参数设置为true时,线程就变成了守护线程,被守护线程结束,守护线程
      自动结束。
    
    (7)join()方法
      当某个线程调用改方法的时候,改线程可以抢夺其他线程的执行权;被中段的线程需要等待程A执行终止完成,才被唤醒。
    (通俗一点说,join方法是阻塞的调用该方法的线程,当被调用线程结束之后执行)。
    
    (8)setPriority() 设置线程的优先级(取值范围1~10)
    
    (9)yield(),临时释放执行权。
    
    (10)wait()方法和sleep()方法的区别:
      <1>两个方法可以让线程处于冻结状态;
      <2>sleep()必须指定时间,wait()方法可以指定时间,也可以不指定时间;
      <3>sleep()会释放执行权,不会释放锁。wait()会释放执行权,同时也会释放锁;
    
    (11)多生产者等待唤醒新写法:
    public class Product {
      private int number=0; //产品编号
      private boolean flag; //等待唤醒标签
      private Lock lock = new ReentrantLock();
      private Condition proCon = lock.newCondition();
      private Condition cusCon = lock.newCondition();
    
      public void get(){
        lock.lock();
        try{
          while (flag){
            try {
              proCon.await();
            } catch (InterruptedException e) {
              e.printStackTrace();
            }
           }
          number++;
          System.out.println("生产者:"+Thread.currentThread()+"..生产产品:"+number);
          flag = true;
          cusCon.signal();
          }finally{
            lock.unlock();
          }
        }
    
      public void product(){
        lock.lock();
        try{
          while (!flag){
            try {
              cusCon.await();
            } catch (InterruptedException e) {
              e.printStackTrace();
            }
          }
        System.out.println("消费者:"+Thread.currentThread()+"..消费产品:"+number);
        flag = false;
        proCon.signal();
      }finally{
        lock.unlock();
      }
      }
    }
    

    相关文章

      网友评论

          本文标题:十、Java语言基础---多线程(同步,等待唤醒机制,Lock,

          本文链接:https://www.haomeiwen.com/subject/yluqwqtx.html