Java基础语法(二十七)

版权声明:作者已开启版权声明,如转载请注明转载地址。 https://blog.csdn.net/qq_34829447/article/details/82190389

一.多线程安全问题

1.线程操作共享数据的安全问题

  • 如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。
  • 程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

2.售票的案例

  • 多个线程并发访问同一个数据资源,实例代码:
/*
      * 多线程并发访问同一个数据资源
      * 3个线程,对一个票资源,出售
      */
public class ThreadDemo {
    public static void main(String[] args) {
        //创建Runnable接口实现类对象
        Tickets t = new Tickets();
        //创建3个Thread类对象,传递Runnable接口实现类
        Thread t0 = new Thread(t);
        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        t0.start();
        t1.start();
        t2.start();
    }
}

/*
 *  通过线程休眠,出现安全问题
 */
public class Tickets implements Runnable{
    //定义出售的票源
    private int ticket = 100;
    private Object obj = new Object();
    public void run(){
        while(true){
            //对票数判断,大于0,可以出售,变量--操作
            if( ticket > 0){
                try{
                    Thread.sleep(10); //加了休眠让其他线程有执行机会
                }catch(Exception ex){}
                System.out.println(Thread.currentThread().getName()+" 出售第 "+ticket--);
            }
        }
    }
}
  • 同步代码块synchronized解决线程安全问题(同步技术)

这里写图片描述

  • 同步代码块公式:同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全

    synchronized(任意对象){
    线程要操作的共享数据
    }
  • 同步的上厕所原理

    • 不使用同步:线程在执行的过程中会被打扰,线程比喻成人,线程执行代码就是上一个厕所,第一个人正在上厕所,上到一半,被另外一个人拉出来
    • 使用同步:线程比喻成人,线程执行代码就是上一个厕所,锁比喻成厕所门,第一个人上厕所,会锁门,第二个人上厕所,看到门锁上了,等待第一个人上完再去上厕所
  • 同步代码块应用实例代码

/*
           * 多线程并发访问同一个数据资源
           * 3个线程,对一个票资源,出售
           */
public class ThreadDemo {
    public static void main(String[] args) {
        //创建Runnable接口实现类对象
        Tickets t = new Tickets();
        //创建3个Thread类对象,传递Runnable接口实现类
        Thread t0 = new Thread(t);
        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        t0.start();
        t1.start();
        t2.start();
    }
}
/*
       *  通过线程休眠,出现安全问题
       *  解决安全问题,Java程序,提供技术,同步技术
       *  公式:
       *    synchronized(任意对象){
       *      线程要操作的共享数据
       *    }
       *    同步代码块
       */
public class Tickets implements Runnable{
    //定义出售的票源
    private int ticket = 100;
    private Object obj = new Object();
    public void run(){
        while(true){
            //线程共享数据,保证安全,加入同步代码块
            synchronized(obj){
                //对票数判断,大于0,可以出售,变量--操作
                if( ticket > 0){
                    try{
                        Thread.sleep(10);
                    }catch(Exception ex){}
                   System.out.println(Thread.currentThread().getName()+" 出售第 "+ticket--);
                }
            }
        }
    }
}

3.同步方法

  • 同步方法:将线程共享数据,和同步,抽取到一个方法中,在方法声明上加上synchronized (同步方法中的锁对象是 this)

  • 同步方法的格式

    public synchronized void method(){
        可能会产生线程安全问题的代码
    }
  • 实例代码

    public class Ticket implements Runnable {
      //共100票
      int ticket = 100;
      //定义锁对象
      Object lock = new Object();
      @Override
      public void run() {
          //模拟卖票
          while(true){
              //同步方法
              method();
          }
      }
    
      //同步方法,锁对象this
      public synchronized void method(){
          if (ticket > 0) {
              //模拟选坐的操作
              try {
                  Thread.sleep(10);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              System.out.println(Thread.currentThread().getName() + "正在卖票:" + ticket--);
          }
      }
    }
  • 静态同步方法:在方法声明上加上static synchronized(静态同步方法中的锁对象是类名.class)

  • 静态同步方法的格式

    public static synchronized void method(){
      可能会产生线程安全问题的代码
    }

4.JDK1.5新特性Lock接口

  • 查阅API,查阅Lock接口描述,Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。

  • Lock接口中的常用方法

    • void lock()
    • void unlock()
  • Lock接口改进售票案例

    /*
         * 多线程并发访问同一个数据资源
         * 3个线程,对一个票资源,出售
         */
    public class ThreadDemo {
      public static void main(String[] args) {
          //创建Runnable接口实现类对象
          Tickets t = new Tickets();
          //创建3个Thread类对象,传递Runnable接口实现类
          Thread t0 = new Thread(t);
          Thread t1 = new Thread(t);
          Thread t2 = new Thread(t);
          t0.start();
          t1.start();
          t2.start();
      }
    }
    /*
     *  使用JDK1.5 的接口Lock,替换同步代码块,实现线程的安全性
     *  Lock接口方法:
     *     lock() 获取锁
     *     unlock()释放锁
     *  实现类ReentrantLock
     */
    public class Tickets implements Runnable{
      //定义出售的票源
      private int ticket = 100;
      //在类的成员位置,创建Lock接口的实现类对象
      private Lock lock = new ReentrantLock();
      public void run(){
          while(true){
              //调用Lock接口方法lock获取锁
              lock.lock();
              //对票数判断,大于0,可以出售,变量--操作
              if( ticket > 0){
                  try{
                      Thread.sleep(10);
                     System.out.println(Thread.currentThread().getName()+" 出售第 "+ticket--);
                  }catch(Exception ex){
    
                  }finally{
                      //释放锁,调用Lock接口方法unlock
                      lock.unlock();
                  }
              }
          }
      }
    }

二.等待唤醒机制

1.死锁

  • 同步锁使用的弊端:当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。这时容易引发一种现象:程序出现无限等待,这种现象我们称为死锁。这种情况能避免就避免掉。
    这里写图片描述

  • 出现代码样式

    synchronzied(A锁){
      synchronized(B锁){
      }
    }
  • 死锁代码情况演示

    • 定义锁对象类
    public class MyLock {
    public static final Object lockA = new Object();
    public static final Object lockB = new Object();
    }
    • 线程任务类
    public class ThreadTask implements Runnable {
      int x = new Random().nextInt(1);//0,1
      //指定线程要执行的任务代码
      @Override
      public void run() {
          while(true){
              if (x%2 ==0) {
                  //情况一
                  synchronized (MyLock.lockA) {
                      System.out.println("if-LockA");
                      synchronized (MyLock.lockB) {
                          System.out.println("if-LockB");
                          System.out.println("if大口吃肉");
                      }
                  }
              } else {
                  //情况二
                  synchronized (MyLock.lockB) {
                      System.out.println("else-LockB");
                      synchronized (MyLock.lockA) {
                          System.out.println("else-LockA");
                          System.out.println("else大口吃肉");
                      }
                  }
              }
              x++;
          }
      }
    }
    • 测试类
    public class ThreadDemo {
      public static void main(String[] args) {
          //创建线程任务类对象
          ThreadTask task = new ThreadTask();
          //创建两个线程
          Thread t1 = new Thread(task);
          Thread t2 = new Thread(task);
          //启动线程
          t1.start();
          t2.start();
      }
    }

2.线程等待与唤醒案例介绍

  • 线程等待与唤醒机制所涉及到的方法

    • wait() :等待,将正在执行的线程释放其执行资格和执行权,并存储到线程池中
    • notify():唤醒,唤醒线程池中被wait()的线程,一次唤醒一个,而且是任意的
    • notifyAll():唤醒全部:可以将线程池中的所有wait() 线程都唤醒
  • 所谓唤醒的意思就是让线程池中的线程具备执行资格。必须注意的是,这些方法都是在同步中才有效。同时这些方法在使用时必须标明所属锁,这样才可以明确出这些方法操作的到底是哪个锁上的线程。

  • 为什么这些操作线程的方法(notify()/notifyAll()/wait())定义在Object类中?因为这些方法在使用时,必须要标明所属的锁,而锁又可以是任意对象。能被任意对象调用的方法一定定义在Object类中。

  • 实例代码

    • 模拟资源类
    public class Resource {
        private String name;
        private String sex;
        private boolean flag = false;
    
        public synchronized void set(String name, String sex) {
            if (flag)
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            // 设置成员变量
            this.name = name;
            this.sex = sex;
            // 设置之后,Resource中有值,将标记该为 true ,
            flag = true;
            // 唤醒output
            this.notify();
        }
    
        public synchronized void out() {
            if (!flag)
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            // 输出线程将数据输出
            System.out.println("姓名: " + name + ",性别: " + sex);
            // 改变标记,以便输入线程输入数据
            flag = false;
            // 唤醒input,进行数据输入
            this.notify();
        }
    }
    • 输入线程任务类
    public class Input implements Runnable {
        private Resource r;
    
        public Input(Resource r) {
            this.r = r;
        }
    
        @Override
        public void run() {
            int count = 0;
            while (true) {
                if (count == 0) {
                    r.set("小明", "男生");
                } else {
                    r.set("小花", "女生");
                }
                // 在两个数据之间进行切换
                count = (count + 1) % 2;
            }
        }
    }
    • 输出线程任务类
    public class Output implements Runnable {
        private Resource r;
    
        public Output(Resource r) {
            this.r = r;
        }
    
        @Override
        public void run() {
            while (true) {
                r.out();
            }
        }
    }
    • 测试类
    public class ResourceDemo {
        public static void main(String[] args) {
            // 资源对象
            Resource r = new Resource();
            // 任务对象
            Input in = new Input(r);
            Output out = new Output(r);
            // 线程对象
            Thread t1 = new Thread(in);
            Thread t2 = new Thread(out);
            // 开启线程
            t1.start();
            t2.start();
        }
    }

三.线程知识点总结

1.同步锁

  • 多个线程想保证线程安全,必须要使用同一个锁对象

  • 同步代码块(同步代码块的锁对象可以是任意的对象)

    synchronized (锁对象){
       可能产生线程安全问题的代码
    }
  • 同步方法(同步方法中的锁对象是this)

    public synchronized void method(){
       可能产生线程安全问题的代码
    }
  • 静态同步方法(锁对象是类名.class)

    public synchronized static void method(){
       可能产生线程安全问题的代码
    }

2.多线程有几种实现方案

  • 继承Thread类
  • 实现Runnable接口
  • 通过线程池,实现Callable接口

3.同步有几种方式,分别是什么?

  • 同步代码块
  • 同步方法
    • 静态同步方法

4.启动一个线程是run还是start,他们的区别是?

  • 启动一个线程是start()
  • 区别
    • start:启动线程,并调用线程中的run方法
    • run:执行该线程对象要执行的任务

5.sleep和wait方法的区别?

  • sleep:不释放锁对象,释放cpu使用权;在休眠时间内不能唤醒
  • wait:释放锁对象,释放cpu使用权;在等待时间内能唤醒

6.为什么wait、notify、notifyAll等方法都定义在Object类中?

  • 锁对象可以使任意类型对象

猜你喜欢

转载自blog.csdn.net/qq_34829447/article/details/82190389
今日推荐