多线程学习(下)

1、线程同步

  • 多个线程操作共享数据时,出现的线程安全问题

  • 解决方式:

    • 方式一:同步代码块---不能包含多了,也不能包含少了

      synchronized(同步监视器){
          //需要被同步的代码
      }
      
      • 说明:操作共享数据的代码==需要被同步的代码
        共享数据:多个线程共同操作的变量

      • 同步监视器:锁,任何一个类的对象都可充当为锁

        要求:多个线程必须要共用同一把锁

        • 对于继承Thread类创建多线程方法

          1. 同步监视器可以考虑使用定义静态类对象
          2. 可以考虑使用当前类对象synchronized (类名.class)
        • 对于实现Runnable接口创建多线程方法

          1. 同步监视器可以考虑使用 this
    • 方式二:同步方法: 如果操作共享数据的代码完整的声明在一个方法中,将此方法声明为同步的

      private synchronized void show(){ 
          //同步监视器 this
          
      }
      private static synchronized void show(){ 
          //同步监视器:当前类本身    
      }
      
      1. 同步方法仍然涉及到同步监视器,只是不需要显式的声明

      2. 非静态的同步方法,同步监视器是:this

        静态的同步方法,同步监视器是:当前类本身

    • 方式三:Lock(锁) jdk5.0新增

      1. 实例化ReentrantLock

      2. 调用lock()方法

      3. 调用解锁方法unlock()

        package com.han.java.ere;
        
        import java.util.concurrent.locks.ReentrantLock;
        
        /**
         * 解决线程安全的方式三:lock--- jdk5.0新增
         * @author hlrstart
         * @create 2021-04-04-9:35
         */
        public class LockTest {
            public static void main(String[] args) {
                Windw win=new Windw();
                Thread t1=new Thread(win);
                Thread t2=new Thread(win);
                Thread t3=new Thread(win);
                t1.setName("窗口一");
                t2.setName("窗口二");
                t3.setName("窗口三");
                t1.start();
                t2.start();
                t3.start();
            }
        }
        class Windw implements  Runnable{
            private int sticket=100;
        //    1.实例化ReentrantLock
             private ReentrantLock lock=new ReentrantLock() ;
            @Override
            public void run() {
                while(true){
                    try {
        //              2.调用lock()方法
                        lock.lock();
                        if (sticket > 0) {
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            System.out.println(Thread.currentThread().getName() + ": 票号为" + sticket);
                            sticket--;
                        } else {
                            break;
                        }
                    }finally {
        //          3.调用解锁方法
                        lock.unlock();
                    }
                }
            }
        }
        
  • 同步的方式优缺点

    • 优点:解决了线程安全问题
    • 局限性:操作同步代码时,是单线程的过程,效率低
  • 线程死锁问题

    • 不同的线程分别占用对方需要的资源不放弃,都在等待对方释放自己需要的资源。

    • 出现死锁,不会出现异常,不会出现提示,所有线程都处于阻塞状态,无法继续。

2、线程通信

涉及到的三个方法

  • wait():一旦执行此方法,当前线程进入阻塞状态,并释放同步监视器

  • notify():一旦执行此方法,就会唤醒被wait()的一个线程,若有多个线程被wait(),就唤醒优先级高的那一个

  • notifyAll() :一旦执行此方法,就会唤醒所有被wait()的线程

    说明

    1. wait()、notify()、notifyAll() 三个方法必须使用在同步代码块或同步方法中。
    2. wait()、notify()、notifyAll() 三个方法的调用者必须是同步代码块或同步方法中的同步监视器,否则会出现异常。
    3. wait()、notify()、notifyAll() 三个方法是定义在java.lang.Object类中。

3、JDK5.0新增线程创建方式

新增方式一:实现Callable接口

  • 与使用Runnable相比,Callable功能更强大些

    • call()方法可以有返回值
    • call()方法可以抛出异常
    • 支持泛型的返回值
    • 需要借助FutureTask类,获取返回结果
  • 实现步骤

    1. 创建一个实现Callable的实现类
    2. 实现call方法,将此线程需要执行的操作声明在call()中
    3. 创建Callable接口实现类的对象
    4. 将此Callable接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask 的对象
    5. 将FutureTask 的对象作为参数传递到Thread类的构造器中,创建Thread对象,调用start()
    6. 获取Callable中call方法的返回值
    package com.han.createThread;
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
    
    /**
     * 创建线程的方式三:实现Callable接口  ---jdk5.0 新增
     *
     * @author hlrstart
     * @create 2021-04-04-11:24
     */
    
    //1.创建一个实现Callable的实现类
    class NumThread implements Callable{
    //   2. 实现call方法,将此线程需要执行的操作声明在call()中
        @Override
        public Object call() throws Exception {
            int sum=0;
            for (int i = 1; i <=100 ; i++) {
                if(i%2==0){
                    System.out.println(i);
                    sum+=i;
                }
            }
            return sum;
        }
    }
    public class ThreadNEw {
        public static void main(String[] args) {
           // 3.创建Callable接口实现类的对象
            NumThread num=new NumThread();
      //4.将此Callable接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask 的对象     
            FutureTask f=new FutureTask(num);
      //5.将FutureTask 的对象作为参数传递到Thread类的构造器中,创建Thread对象,调用start()
            new Thread(f).start();
            try {
    //   6.         get()方法的返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值
              Object sum=  f.get();
                System.out.println("总和为:"+sum);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    
    }
    

新增方式二:使用线程池(开发中使用)

  • 思想:提前创建好多个线程,放入线程池,使用时直接获取,使用完放回池中。

  • 好处:

    • 提高响应速度(减少创建新线程的时间)
    • 降低资源消耗(重复利用)
    • 便于线程管理
  • 实现步骤

    1. 提供指定线程数量的线程

    2. 执行指定的线程的操作,需要提供实现Runnable接口或Callable接口实现类的对象

      class Num implements Runnable{
          @Override
          public void run() {
              for (int i = 0; i <=100; i++) {
                  if(i%2==0){
                      System.out.println(Thread.currentThread().getName()+":"+i);
                  }
              }
          }
      }
      public class ThreadPool {
          public static void main(String[] args) {
              //1.提供指定线程数量的线程
              ExecutorService service = Executors.newFixedThreadPool(10);
              //2.执行指定的线程的操作,需要提供实现Runnable接口或Callable接口实现类的对象
              service.execute(new Num()); //适用于Runnable
      //        service.submit(Callable callable); //适用于callable
          }
      }
      

4、涉及到的面试题

  1. sleep()和wait()的异同?、

    相同点:

    • 执行该方法,都可以使当前线程进入阻塞状态

    不同点:

    1. 两个方法的定义位置不同:Thread类中定义sleep(),Object类中定义wait()
    2. 调用的要求不同:sleep()可以在任何需要的场景下调用,wait()必须使用在同步代码块或同步方法中
    3. 是否会释放同步监视器:若两个方法都使用在同步代码块或同步方法中,sleep()不会释放(例如睡眠中,厕所门依然没有打开),wait()会释放
  2. 面试题:synchronized 与Lock的异同

    相同:

    • 都可以解决线程安全问题

    不同点:

    • synchronized机制在执行完同步代码之后,自动释放同步监视器

    • Lock需要手动启动同步(lock()),同时结束同步也需要手动的实现(unlock())

上一篇:java--线程池及Callable接口的使用


下一篇:Java程序跑的快,全要靠线程带