黑马视频 7/6


Java SE


day24 线程

  • 什么是线程

    • 线程是程序执行的一条路径, 一个进程中可以包含多条线程
    • 多线程并发执行可以提高程序的效率, 可以同时完成多项工作
  • 多线程 并行 和 并发 的区别
  • 并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行。(需要多核CPU)
  • 并发是指两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行。
  • :Java程序运行原理

    • Java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。
    • JVM 启动至少启动了 垃圾回收线程 和 主线程 ,所以是 多线程 的。
  • 多线程程序实现

    • 继承Thread
    • 定义类继承 Thread
    • 重写run方法
    • 把新线程要做的事写在run方法中
    • 创建线程对象
    • 开启新线程, 内部会自动执行run方法
    public class Demo_Thread {
      public static void main(String[] args) {
          MyThread my = new MyThread() ;
          my.start();
          for(int i = 0 ; i<1000 ; i++) {
              System.out.println("saaaaaa");
          }    
      }
    }
    class MyThread extends Thread{
      public void run() {
          for (int i = 0; i < 1000; i++) {
              System.out.println("xxxx");
          }
      }
    }
  • 多线程程序实现

    • .实现Runnable
    • 定义类实现Runnable接口
    • 实现run方法
    • 把新线程要做的事写在run方法中
    • 创建自定义的Runnable的子类对象
    • 创建Thread对象, 传入Runnable
    • 调用start()开启新线程, 内部会自动调用 Runnable 的 run() 方法
              public class Demo3_Runnable {
                  public static void main(String[] args) {
                      MyRunnable mr = new MyRunnable();                        //4,创建自定义类对象
                      //Runnable target = new MyRunnable();
                      Thread t = new Thread(mr);                                //5,将其当作参数传递给Thread的构造函数
                      t.start();                                                //6,开启线程
                      for(int i = 0; i < 3000; i++) {
                          System.out.println("bb");
                      }
                  }
              }
              class MyRunnable implements Runnable {                            //1,自定义类实现Runnable接口
                  @Override
                  public void run() {                                            //2,重写run方法
                      for(int i = 0; i < 3000; i++) {                            //3,将要执行的代码,写在run方法中
                          System.out.println("aaaaaaaaaaaaaaaaaaaaaaaaaaaa");
                      }
                  }
              }
  • 实现 Runnable 原理:

    • 看 Thread 类的构造函数,传递了 Runnable 接口的引用
    • 2,通过 init() 方法找到传递的target给成员变量的 target 赋值
    • 3,查看 run 方法,发现 run 方法中有判断,如果 target 不为 null 就会调用 Runnable 接口子类对象的 run 方法
  • 两种方法的区别:

    • 继承Thread : 由于子类重写了 Thread 类的 run() , 当调用 start() 时, 直接找子类的 run() 方法
    • 实现Runnable : 构造函数中传入了Runnable 的引用, 成员变量记住了它, start() 调用 run() 方法时内部判断成员变量 Runnable 的引用是否为空, 不为空编译时看的 是Runnable 的 run(),运行时执行的是子类的 run() 方法
  • 继承Thread

    • 好处是:可以直接使用 Thread 类中的方法,代码简单
    • 弊端是:如果已经有了父类,就不能用这种方法
  • 实现 Runnable 接口

    • 好处是:即使自己定义的线程类有了父类也没关系,因为有了父类也可以实现接口,而且接口是可以多实现的
    • 弊端是:不能直接使用 Thread 中的方法需要先获取到线程对象后,才能得到 Thread 的方法,代码复杂
  • 匿名内部类实现的两种方式:

    //继承Thread类         
          new Thread() {                                                    //1,new 类(){}继承这个类
              public void run() {                                            //2,重写run方法
                  for(int i = 0; i < 3000; i++) {                            //3,将要执行的代码,写在run方法中
                      System.out.println("aaaaaaaaaaaaaaaaaaaaaaaaaaaa");
                  }
              }
          }.start();
    //实现Runnable接口            
          new Thread(new Runnable(){                                        //1,new 接口(){}实现这个接口
              public void run() {                                            //2,重写run方法
                  for(int i = 0; i < 3000; i++) {                            //3,将要执行的代码,写在run方法中
                      System.out.println("bb");
                  }
              }
          }).start(); 
  • 通过 getName( ) 方法获取线程对象的名字
  • 通过构造函数可以传入String类型的名字

              new Thread("xxx") {
                  public void run() {
                      for(int i = 0; i < 1000; i++) {
                          System.out.println(this.getName() + "....aaaaaaaaaaaaaaaaaaaaaaa");
                      }
                  }
              }.start();
              
              new Thread("yyy") {
                  public void run() {
                      for(int i = 0; i < 1000; i++) {
                          System.out.println(this.getName() + "....bb");
                      }
                  }
              }.start(); 
  • 通过 setName ( String ) 方法可以设置线程对象的名字

              Thread t1 = new Thread() {
                  public void run() {
                      for(int i = 0; i < 1000; i++) {
                          System.out.println(this.getName() + "....aaaaaaaaaaaaaaaaaaaaaaa");
                      }
                  }
              };            
              Thread t2 = new Thread() {
                  public void run() {
                      for(int i = 0; i < 1000; i++) {
                          System.out.println(this.getName() + "....bb");
                      }
                  }
              };
              t1.setName("芙蓉姐姐");
              t2.setName("凤姐");
              t1.start();
              t2.start();
  • 获取当前线程的引用, Thread.currentThread() , 主线程也可以获取

              new Thread(new Runnable() {
                  public void run() {
                      for(int i = 0; i < 1000; i++) {
                          System.out.println(Thread.currentThread().getName() + "...aa");
                      }
                  }
              }).start();            
              new Thread(new Runnable() {
                  public void run() {
                      for(int i = 0; i < 1000; i++) {
                          System.out.println(Thread.currentThread().getName() + "...bb");
                      }
                  }
              }).start();
              Thread.currentThread().setName("我是主线程");                    //获取主函数线程的引用,并改名字
              System.out.println(Thread.currentThread().getName());        //获取主函数线程的引用,并获取名字
  • 休眠线程,Thread.sleep ( 毫秒 , 纳 秒 ) , 控制当前线程休眠若干毫秒1秒= 1000毫秒 1秒 = 1000 1000 1000纳秒 1000000000

              new Thread() {
                  public void run() {
                      for(int i = 0; i < 10; i++) {
                          System.out.println(getName() + "...aaaa");
                          try {
                              Thread.sleep(10);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                      }
                  }
              }.start();    
              new Thread() {
                  public void run() {
                      for(int i = 0; i < 10; i++) {
                          System.out.println(getName() + "...bb");
                          try {
                              Thread.sleep(10);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                      }
                  }
              }.start();
  • setDaemon(),设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出

              Thread t1 = new Thread() {
                  public void run() {
                      for(int i = 0; i < 50; i++) {
                          System.out.println(getName() + "...aaaaaaaaaaaaaaaaaaaaaa");
                          try {
                              Thread.sleep(10);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                      }
                  }
              };
              Thread t2 = new Thread() {
                  public void run() {
                      for(int i = 0; i < 5; i++) {
                          System.out.println(getName() + "...bb");
                          try {
                              Thread.sleep(10);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                      }
                  }
              };
              t1.setDaemon(true);                        //将t1设置为守护线程            
              t1.start();
              t2.start();
  • join(),当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续
  • join(int), 可以等待指定的毫秒之后继续
  • 匿名内部类在使用主方法中的局部变量的时候,必须用 final 修饰局部变量

              final Thread t1 = new Thread() {
                  public void run() {
                      for(int i = 0; i < 50; i++) {
                          System.out.println(getName() + "...aaaaaaa");
                          try {
                              Thread.sleep(10);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                      }
                  }
              };
              Thread t2 = new Thread() {
                  public void run() {
                      for(int i = 0; i < 50; i++) {
                          if(i == 2) {
                              try {
                                  //t1.join();                        //插队,加入
                                  t1.join(30);                        //加入,有固定的时间,过了固定时间,继续交替执行
                                  Thread.sleep(10);
                              } catch (InterruptedException e) {
                                  
                                  e.printStackTrace();
                              }
                          }
                          System.out.println(getName() + "...bb");    
                      }
                  }
              };            
              t1.start();
              t2.start();
  • yield 让出 cpu
  • setPriority() 设置线程的优先级

          Thread t1 = new Thread() {
              public void run() {
                  for (int i = 0; i < 1000; i++) {
                      System.out.println(this.getName() + "xxxxxx");
                  }
              }
          } ;
          
          Thread t2 = new Thread() {
              public void run() {
                  for (int i = 0; i < 1000; i++) {
                      System.out.println(this.getName() + "aaaaaaaaaaa");
                  }
              }
          };
    //        t1.setPriority(10);
    //        t2.setPriority(1);
          t1.setPriority(Thread.MAX_PRIORITY);       //最大优先级
          t2.setPriority(Thread.MIN_PRIORITY);        // 最小优先级
          t1.start();
          t2.start();
  • 什么情况下需要同步

    • 当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行的过程中 CPU 不要切换到其他线程工作. 这时就需要同步.
    • 如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码.
  • 同步代码块

    • 使用 synchronized 关键字加上一个锁对象来定义一段代码, 这就叫同步代码块
    • 多个同步代码块如果使用相同的锁对象, 那么他们就是同步的
              class Printer {
                  Demo d = new Demo();
                  public static void print1() {
                      synchronized(d){                //锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象
                          System.out.print("黑");
                          System.out.print("马");
                          System.out.print("程");
                          System.out.print("序");
                          System.out.print("员");
                          System.out.print("\r\n");
                      }
                  }
                  public static void print2() {    
                      synchronized(d){          //锁对象不能用匿名对象,因为匿名对象不是同一个对象
                          System.out.print("传");
                          System.out.print("智");
                          System.out.print("播");
                          System.out.print("客");
                          System.out.print("\r\n");
                      }
                  }
              }
          class Demo{}
  • 使用 synchronized 关键字修饰一个方法, 该方法中所有的代码都是同步的
  • 非静态的同步方法的锁对象是 this
  • 静态的同步函数的锁是:字节码对象

          class Printer {
              public static void print1() {
                  synchronized(Printer.class){                //锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象
                      System.out.print("黑");
                      System.out.print("马");
                      System.out.print("程");
                      System.out.print("序");
                      System.out.print("员");
                      System.out.print("\r\n");
                  }
              }
              /*
               * 非静态同步函数的锁是:this
               * 静态的同步函数的锁是:字节码对象
               */
              public static synchronized void print2() {    
                  System.out.print("传");
                  System.out.print("智");
                  System.out.print("播");
                  System.out.print("客");
                  System.out.print("\r\n");
              }
          }
  • 线程安全问题

    • 多线程并发操作同一数据时, 就有可能出现线程安全问题
    • 使用同步技术可以解决这种问题, 把操作数据的代码进行同步, 不要多个线程一起操作
    • 如果用引用数据类型成员变量当做锁对象,那么必须是静态的,一般都是使用字节码文件当做锁对象
    • 多次启动一个线程是非法的,会报异常
              public class Demo2_Synchronized {
                  /**
                   * @param args
                   * 需求:铁路售票,一共100张,通过四个窗口卖完.
                   */
                  public static void main(String[] args) {
                      TicketsSeller t1 = new TicketsSeller();
                      TicketsSeller t2 = new TicketsSeller();
                      TicketsSeller t3 = new TicketsSeller();
                      TicketsSeller t4 = new TicketsSeller();
                      t1.setName("窗口1");
                      t2.setName("窗口2");
                      t3.setName("窗口3");
                      t4.setName("窗口4");
                      t1.start();
                      t2.start();
                      t3.start();
                      t4.start();
                  }
              }
              
              class TicketsSeller extends Thread {
                  private static int tickets = 100;
                  static Object obj = new Object();       //  必须是静态的
                  public TicketsSeller() {
                      super();    
                  }
                  public TicketsSeller(String name) {
                      super(name);
                  }
                  public void run() {
                      while(true) {
                          synchronized(obj) {
                              if(tickets <= 0) 
                                  break;
                              try {
                                  Thread.sleep(10);//线程1睡,线程2睡,线程3睡,线程4睡
                              } catch (InterruptedException e) {
                                  
                                  e.printStackTrace();
                              }
                              System.out.println(getName() + "...这是第" + tickets-- + "号票");
                          }
                      }
                  }
              }
  • 死锁(哲学家进餐问题)

    • 多线程同步的时候, 如果同步代码嵌套, 使用相同锁, 就有可能出现死锁
    • 尽量不要让同步代码块嵌套使用
              private static String s1 = "筷子左";
              private static String s2 = "筷子右";
              public static void main(String[] args) {
                  new Thread() {
                      public void run() {
                          while(true) {
                              synchronized(s1) {
                                  System.out.println(getName() + "...拿到" + s1 + "等待" + s2);
                                  synchronized(s2) {
                                      System.out.println(getName() + "...拿到" + s2 + "开吃");
                                  }
                              }
                          }
                      }
                  }.start();
                  
                  new Thread() {
                      public void run() {
                          while(true) {
                              synchronized(s2) {
                                  System.out.println(getName() + "...拿到" + s2 + "等待" + s1);
                                  synchronized(s1) {
                                      System.out.println(getName() + "...拿到" + s1 + "开吃");
                                  }
                              }
                          }
                      }
                  }.start();
              }
  • 以前的线程安全的类回顾
  • 回顾以前说过的线程安全问题

    • Vector是线程安全的 , ArrayList 是线程不安全的
    • StringBuffer 是线程安全的, StringBuilder 是线程不安全的
    • Hashtable 是线程安全的 ,HashMap 是线程不安全的
    • Collections.synchroinzed(xxx) ,这个方法可以将线程不安全的变成线程安全的。
最后修改:2018 年 07 月 17 日
哇卡哇卡