多线程

多线程

  • 并发与并行

    • 并发:指多个事件在同一时间段发生(事件交替执行)
    • 并行:指多个事件在同一时刻发生(事件同时执行)
  • 线程与进程

    • 进程:指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程是程序的一次执行过程,是系统运行程序的基本单位
    • 线程:是进程中的一个执行单位,负责当前进程中程序的执行,一个具有多个线程的进程,被称为多线程程序
  • 线程调度:

    • 分时调度:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间

    • 抢占式调度:让优先级高的线程优先使用CPU,若线程的优先级相同,则会随机选择一个线程使用,Java的线程调度为抢占式调度

Java线程

  • 主线程

    执行主方法(main)的线程

  • 创建线程类

    • 创建Thread类的子类

    java.lang.Thread类是描述线程的类,可继承Thread类以实现多线程程序

    1. 创建Thread类的子类
    2. 在Thread类的子类中重写run方法,设置线程任务
    3. 创建线程子类对象
    4. 调用Thread类的start方法,开启新线程,执行线程的run方法,完成线程任务

    tips:在创建线程后,原线程与新建线程同时存在,分别执行任务,抢占式调度同一优先级下随机选择一个线程执行任务,两个线程并发执行

    • 实现Runnable接口
    1. 创建一个Runnable接口的实现类
    2. 在实现类中重写Runnable接口的Run方法,设置线程任务
    3. 创建一个Runnable接口的实现类对象
    4. 创建Thread类对象,在构造方法中传递Runnable接口的实现类对象
    5. 调用Thread类的start方法,开启新的线程执行Runnable接口实现类的run方法
      • 优点:
        1. 避免了单继承的局限性,即若继承Thread类开启线程就无法继承其他类
        2. 增强了程序的拓展性,降低了程序的耦合性,即实现Runnable接口的方式,将设置线程任务与开启新线程的任务进行了分离
    • 匿名内部类实现线程创建

      //继承方式实现
      new Thread(){
          @Override
          public void run(){
              //线程任务
          }
      }.start();
      //Runnable接口实现
      Runnable r = new Runnable(){
          @Override
          public void run(){
              //线程任务
          }
      };
      new Thread(r).start();
      
  • 内存图解

image-20200916195140557

  • Thread类
    • 常用方法:
      1. public String getName() :获取当前线程名称。
      2. public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法。
      3. public void run() :此线程要执行的任务在此处定义代码。
      4. public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
      5. public static Thread currentThread() :返回对当前正在执行的线程对象的引用。

线程安全

多线程访问了共享的数据可能会发生线程安全问题,如对同一数据产生了不同的操作

线程同步

当我们使用多个线程访问同一资源,且多个线程中对资源有写的操作时,就容易出现线程安全问题,为解决多线程并发访问一个资源的安全性问题,Java中提供了线程同步机制来解决该问题。

  • 同步代码块

    通过使用synchronized关键字标记方法中的某一区块,表示只对该区块的资源实行互斥访问

    synchronized(锁对象){
    //需要同步操作的代码块
    }
    
    • 同步代码块中的锁对象可以是任意的对象

    • 必须保证多个线程使用的锁对象是同一个

    • 通过锁对象即可将代码块锁定,只让一个线程在同步代码块中执行

    • 同步代码块中的锁对象被称为同步锁,对象锁或对象监视器

    • 原理:

      每一个进程类似于一把钥匙,但同步代码块的锁只有一把,只有首先取得锁对象的进程才能进入同步代码块中,直到完成操作再归还锁对象,而其他未夺得锁对象的进程在该进程操作时自动进入阻塞状态,直到夺得锁对象的进程完成操作运行完同步代码块代码释放锁对象。

  • 同步方法

    • 通过使用synchronized关键字修饰的方法被称为同步方法

      public synchronized void method(){
          //可能产生线程安全问题的代码
      }
      //静态同步方法
      public static synchronized void method(){
          //可能产生线程安全问题的代码
      }
      
    • 使用步骤

      1. 抽取共享的数据代码到一个方法中
      2. 使用synchronized关键字修饰
    • 普通同步方法的锁对象是调用该方法的线程实现类对象

    • 静态同步方法的锁对象是本类的class属性,即class文件对象

  • Lock锁

    • java.util.concurrent.locks.lock机制提供了比同步代码块与同步方法更广泛的锁定操作,更体现面向对象的思想

    • Lock锁也称同步锁,其加锁与释放锁的功能被方法化

      public void lock();//加同步锁
      public void unlock();//释放同步锁
      
    • 使用步骤

      1. 成员位置创建一个ReentrantLock(Lock接口的实现类)对象

        Lock l = new ReentrantLock();

      2. 在可能出现安全问题的代码前调用Lock接口的lock方法加同步锁

        l.lock();

      3. 在可能出现安全问题的代码后调用Lock接口的unlock方法释放同步锁

        tyr{
        }catch(){  
        }finally{//无论程序是否出现异常均将同步锁释放
        l.unlock();
        }
        

线程状态

image-20200917124414165

  • NEW:新建状态

    线程刚被创建,但还未被启动

  • Runnable:可运行

    线程在Java虚拟机中运行的状态

  • Blocked:锁阻塞

    当一个线程未获取到线程锁时进入阻塞状态

  • Waiting:无限等待

    一个线程等待另一个线程执行Object的notify或notifyAll方法唤醒时,处于无限等待状态

  • Timed Waiting:计时等待

    调用含有超时参数的方法如:Thread.sleep、Object.wait,线程将进入计时等待状态,该状态将保持到超时期满或接收到唤醒通知

  • Teminated:被终止

    因为run方法执行完毕而死亡或因没有捕获的异常终止了run方法而死亡

线程间通信

多个线程处理同一个资源,但各线程的线程任务却不相同,则各线程之间存在着线程通信的问题,避免多个线程在操作同一份数据时对同一共享变量的争夺,通过使用等待唤醒机制使得多线程之间能有效的利用资源

  • 在同步代码块或同步函数中调用锁对象的wait与notify方法来实现线程的等待与唤醒

线程池

如果并发的线程数量很多,且每一个线程均执行一个时间很短的任务就结束了,这样频繁的创建线程会大大降低系统的效率,而在Java中通过线程池可以解决该问题,达到线程复用的效果,在执行完任务后并不销毁,而是执行其他的任务

  • 线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复调用。

  • Java.util.concurrent.Executors:线程池的工厂类,用来生产线程池

    • 静态方法:static ExecutorService newFixedThreadPool(int nThreads);

      创建一个可重用固定线程数(int nThreads)的线程池,返回一个ExecutorService接口的实现类对象

    • java.util.concurrent.ExecutorService:线程池接口

      用来从线程池中获取线程,调用start方法,执行线程任务

      • submit(Runnable task)方法:提交一个Runnable任务用以执行
      • shutdown()方法:关闭/销毁线程池
  • 线程池的使用步骤

    1. 使用线程池的工厂类Executors提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池

      ExecutorService es = Executors.newFixedThreadPool(n);

    2. 创建一个Runnable接口的实现类,重写run方法,设置线程任务

      public clas RunnableImpl implements Runnable{
          @Override
          public void run(){
              //线程任务
          }
      }
      
    3. 调用ExecutorService中的submit方法,传递线程任务,开启线程,执行run方法

      es.submit(new RunnableImpl());

    4. 调用ExecutorService中的shutdown方法销毁线程池(不建议)

      es.shutdown();:线程池会一直开启,完成线程任务后会自动归还线程给线程池,销毁后,线程池无法继续使用

Lambda表达式

面向对象编程思想:程序的功能实现由创建对象具有的方法来完成

函数式编程思想:只要能获取到最终结果,实现的过程忽略

JDK1.8推出的一个重要特性

  • 线程任务实现

    • 匿名内部类:

      new Thread(new Runnable(){
          @Override
          public void run(){
              //线程任务
          }
      }).start();
      
    • Lambda表达式

      new Thread(()->{
          //线程任务
      }
      ).start();
      
  • 标准格式

    (参数列表) -> {参数传递的重写方法体}

    ():接口中抽象方法的参数列表,无参为空,有参逗号分隔

    ->:表示传递参数

    {}:重写接口的抽象方法的方法体

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值