小言_互联网的博客

一篇文章看懂多线程

303人阅读  评论(0)

多线程


三种创建方式

一丶继承Thread类(重点)

二丶实现Runnable接口(重点)

三丶实现Callable接口(了解)


Thread

  • 自定义线程类继承Thread类
  • 重写run()方法,编写线程执行体
    • 创建线程对象,调用start(方法启动线程

      public class TestThread1 extends Thread {
      @Override
      public void run() {
      //run方法线程体
      for (int i = 0; i < 20; i++) {
      System.out.println(“我在看代码”+i);
      }
      }

      public static void main(String[] args) {
      
          //创建一个线程对象
          TestThread1 testThread1 = new TestThread1();
      
          //调用start()方法开启线程
          testThread1.start();
      
          //main线程,主线程
          for (int i = 0; i < 200; i++) {
              System.out.println("我在学习多线程"+i);
          }
      }
      

      }

总结:线程开启不一定执行,有CPU调度执行

实现Runnable

  • 定义MyRunnable类实现Runnable接口

  • 实现run()方法,编写线程执行体

  • 创建线程对象,调用start()方法启动线程
    public class TestThread3 implements Runnable {
    public void run() {
    //run方法线程体
    for (int i = 0; i < 20; i++) {
    System.out.println(“我在看代码” + i);
    }
    }

        public static void main(String[] args) {
            //创建实现类对象
            TestThread3 testThread3 = new TestThread3();
    
            //创建线程对象,通过线程对象来开启我们的线程,代理
    
            new Thread(testThread3).start();
    
            for (int i = 0; i < 200; i++) {
                System.out.println("我在学习多线程" + i);
            }
        }
    }
    

总结:

继承Thead类

  • 子类继承Thread类具备多线程能力
  • 启动线程:子类对象.start()
  • 不建议使用:避免OOP单继承局限性

实现Runnable接口

  • 实现接口Runnable具有多线程能力
  • 启动线程:传入目标对象+Thread对象.start()
  • 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用

多个线程操作同一个对象

//多个线程同时操作一个对象
public class TestThread4 implements Runnable {

    private int ticketNumbers = 20;

    public void run() {
        while (true){
            if(ticketNumbers<=0){
                break;
            }
            System.out.println(Thread.currentThread().getName()+"-->拿到了"+ticketNumbers--+"票");
        }
    }

    public static void main(String[] args) {
        TestThread4 testThread4 = new TestThread4();

        new Thread(testThread4,"小明").start();
        new Thread(testThread4,"小张").start();
        new Thread(testThread4,"小王").start();
    }
}

实现Callable接口

  • 实现Callable接口,需要返回值类型
  • 重写call方法,需要抛出异常
  • 创建目标对象
  • 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);
  • 提交执行:Future result1 = ser.submit(1);
  • 获取结果:boolean r1 = result1.get();
  • 关闭服务:ser.shutdownNow();

总结:

Thread的底层实现原理就是静态代理模式:

  • 真实对象和代理对象都要实现同一个接口
  • 代理对象要代理真实角色
  • 代理对象可以做很多真是对象做不了的事情
  • 真实对象专注做自己的事情

Lambda表达式

函数式接口的定义

  • 任何接口,如果指包含唯一一个抽象方法,那么它就是一个函数式接口。

  • 对于函数式接口,我们可以通过lambda表达式来创建该接口的对象。

    public class TestLambda1 {

      public static void main(String[] args) {
    
          ILove iLove = (int a) -> {
              System.out.println("i love you" + a);
          };
    
          iLove.love(520);
      }
    

    }

    interface ILove {
    void love(int a);
    }

线程状态


线程停止

  • 不推荐使用JDK提供的stop() destroy()方法,以废弃

  • 推荐线程自己停止下来

  • 建议使用一个标志位进行终止变量,当flag=false,则终止线程运行

    public class TestStop implements Runnable {

      //设置一个标志位
      private boolean flag = true;
    
      @Override
      public void run() {
          int i = 0;
          while (flag) {
              System.out.println("run thread" + i++);
          }
      }
    
      //设置一个公开的方法停止线程
      public void stop() {
          this.flag = false;
      }
    
      public static void main(String[] args) {
          TestStop testStop = new TestStop();
          new Thread(testStop).start();
    
          for (int i = 0; i < 200; i++) {
              System.out.println("main"+i);
              if(i==120){
                  //调用stop方法切换标志位,让线程停止
                  testStop.stop();
                  System.out.println("线程已停止");
              }
          }
      }
    

    }

线程休眠

  • sleep指定当前线程阻塞的毫秒数
  • sleep时间达到后线程进入就绪状态
  • sleep可以模拟网络延迟,倒计时等
  • 每个对象都有一把锁,sleep不会释放锁

Thlead.sleep(“指定的毫秒数”);

线程礼让

  • 礼让线程,让当前正在执行的线程暂停,但不阻塞
  • 将线程从运行状态转为就绪状态
  • 让cpu重新调度,礼让不一定成功,看cpu心情

Thread.yield();

Join

  • Join合并线程,待此线程执行完成后,在执行其他线程,其他线程阻塞
  • 可以想象为插队

Thread.join();

线程状态观测

线程可以处于以下状态之一:

  • NEW
    尚未启动的线程处于此状态
  • RUNNABLE
    在java虚拟机中执行的线程处于此状态
  • BLOCKED
    被阻塞等待监视器锁定的线程处于此状态
  • WAITING
    正在等待另一个线程执行特定动作的线程处于此状态
  • TIMED_WAITING
    正在等待另一个线程执行动作达到指定等待时间的线程处于此状态
  • TERMINATED
    已退出的线程处于此状态

Thread.State();

线程优先级

  • Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行
  • 线程的优先级用数字表示,范围从1~10
    • Thread.MIN_PRIORITY=1;
    • Thread.MAX_PRIORITY=10;
    • Thread.NORM_PRIORITY=5;
  • 使用以下方式改变或获取优先级
    getPriority() setPriority(int xxx)

注意:优先级的设定建议在start()调度前

优先级低只是意味着获取调度的概率低,并不是优先级低就不会被调用了,这都得看CPU的调度

守护(daemon)线程

  • 线程分为用户线程和守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕

setDaemon(true); //默认是false表示用户线程,正常的线程都是用户线程

线程同步

多个线程操作同一个资源

并发:同一个对象被多个线程同时操作

  • 同步方法:public synchronized void method(int args){}
    若将一个大的方法申明为synchronized将会影响效率
  • 同步块:synchronized (obj) {}
    • obj称之为同步监视器,但是推荐使用共享资源作为同步监视器
    • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身

死锁

多个线程互相抱着对方需要的资源,然后形成僵持

死锁避免方法

产生死锁的四个必要条件

  • 互斥条件:一个资源每次只能被一个进程使用
  • 请求与保持条件:一个进程因请求资源而阻塞,对已获得的资源保持不放
  • 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
  • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系

只要破其中的任意一个或者多个条件就可以避免死锁发生

Lock锁

实例

class A{
  private final ReentrantLock lock = new ReentrantLock();//ReentrantLock 可重入锁
  public void method(){
    lock.lock();
    try{
      //保证线程安全的代码
    }
    finally{
      lock.unlock();
      //如果同步代码有异常,要将unlock()写入finally语句块
    }
  }
}

synchronized与Lock的对比

  • Lock是显示锁(手动开启和关闭锁),synchronized是隐式锁,出了作用域自动释放
  • Lock只有代码块锁,synchronized有代码块锁和方法锁
  • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好,并且具有很好的扩展性

转载:https://blog.csdn.net/Apandam/article/details/105306696
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场