JAVA多线程

多线程

1. 多线程基础

  1. 对于程序、进程、线程的理解

    • 程序:为完成某种目的,而使用一种语言编写的一组指令集合。
    • 进程:一个运行中的程序。它是一个动态的过程,同时进程也是操作系统资源分配的最基本单位
    • 线程:一个程序内部的执行路径,由进程细化而来,线程是操作系统调度和执行的基本单位。
  2. 多线程的创建一共有四种方式(更具体的内容参照4.线程的新增创建方式

    • 继承Thread
    • 实现Runnable接口:在实际开发中优先选择(相比于继承而言,实际上使用最对多的是直接创建线程池)
      • 对比:使用接口实现避免了Ja va单继承的局限性,并且多个线程可以共享一个接口实现类的对象,具有天然的数据共享性
    • 实现Callable接口:JDK5.0新增
    • 使用线程池
  3. 启动一个线程必须调用start()方法,不能调用run()方法

    • 调用run()方法就相当于调用一个普通的方法。而只有调用start方法,才能启动一个线程,并使其处于可运行状态,这才能够被JVM调度并执行
  4. start()只能调用一次,不能让已经start()的线程再次start(),它内部有一个变量专门记录是否是首次启动,如果不是会报错:非法线程状态(ILLegalThreadStateException)

  5. 线程优先级

    • 高优先级的线程要抢占低优先级的线程CPU,但这并不是绝对了,只是高优先级的线程有更高的概率被优先执行。
  6. 线程分类

    • 守护线程:不能单独存在,必须辅以用户线程才能存在,用户线程结束,守护线程结束。Java的垃圾回收就是一个典型的守护线程。
    • 用户线程:如main方法
  7. 线程的几种状态

    • NEW(新建):新创建的一个线程类,但未调用start()方法
    • RUNNABLE(就绪\运行):当一个新建线程调用start()方法之后进入就绪状态,进入线程队列等待CPU分配时间片,一旦得到CPU时间片只有变进入运行状态run()方法指定了线程的操作和功能
    • BLOCKED(阻塞):需要等待同步锁的释放、join()等。
    • WAITING(等待状态):表示该线程需要等待其他线程做出⼀些特定动作(通知或中断)、sleep()等
    • TIMED_WAITING(超时等待):可以在指定的时间后⾃⾏返回⽽不是像 WAITING 那样⼀直等
      待。
    • TERMINATED(终止状态):表示线程已经运行完毕。
    • image-20221121164133468

2. 线程同步

  1. 线程同步问题(线程安全):多个线程修改共享数据,都会出现线程安全问题(多个线程读共享数据不会出现问题)

    • 解决方法

      • 同步代码块(一)

        • synchronized(同步监视器){
              //需要被同步的代码块
          }
          // 同步监视器:锁;多个线程必须共用同一把锁
          
      • 同步方法(二)

        • synchronized void show(){ //非静态的同步方法,,同步监视器是`this`
              //需要被同步的代码
          }
          
          // 然后在run方法中调用show方法
          static synchronized  void show(){ //静态的同步方法,同步监视器是:`当前类本身`
              //需要被同步的代码
          }
          
  2. 单例模式

    • //懒汉式
      // #懒汉式:要使用才创建,不使用就不创建。
      // ##普通懒汉式:执行效率低 无论是否已经存在实例,在多线程的情况下都会发生阻塞。
      public class Dog {
          private Dog() {}	// 防止调用构造方法
          private static Dog instance = null;
          public synchronized static Dog getInstance() {  //无论是否已经实例化,这里都会被阻塞
              if (instance == null) instance = new Dog();
              return instance;
          }
      }
      // ##双重判断型单例模式
      public class Dog {
          private Dog() {}
          private volatile static Dog instance = null; // 防止出现指令重排的问题
          public static Dog getInstance() {  
      		// 确定是否需要阻塞
              if (instance == null){
                  synchronized(Dog.class){
                       //这里在多线程的情况下会出现指令重排的问题,所以对共有资源instance使用关键字volatile修饰
                      if (instance == null) instance = new Dog();
                  }
              } 
              return instance;
          }
      }
      
    • //饿汉式:不管使不使用都先创建
      class Dog{
          private Dog(){}
          private static Dog instance = new Bank();
          public static Dog getInstance(){
              return instance;
          }
      
  3. 死锁问题

    • 产生的原因

      • 互斥:锁在同一时刻只能被一个线程使用
      • 不可剥夺:其他线程无法抢夺已经被占有的锁
      • 请求与保持:线程持有锁,不释放,并请求其他的锁。
      • 循环等待:持有锁资源的线程相互等待。
  • 解决方式:破坏上述任意一个条件即可(一般是采用专门的算法,或者原则)

    • 资源不互斥(难以做到)
      • 可剥夺
      • 同时请求资源,要么同时持有所需资源,要么都不持有
      • 为资源编号,线程只能从小到大依次请求资源,不能请求一个更小编号的资源
  1. 线程同步-Lock(锁)问题
    • 解决现场安全问题的方式三:Lock锁 — JDK5新增
    • JUC的包下面由一个 ReentrantLock类实现了Lock锁,其本身有两个构造方法,其中一个可以实现公平锁,每一个进入的进程形成一种队列分方式,先进先出
    • synchronized与Lock的异同
      • 相同点:都可以解决线程安全问题
      • 不同点:
        • synchronized是一种隐私锁,在执行完相应的同步代码之后,自动释放同步监视器
        • Lock是一种显示锁,需要手动的启动同步(lock()),也需要手动结束同步(unlock())。性能更好,JVM调度线程的时间开销小
  • // #线程安全:方式三
    // Lock实现
    ReentrantLock lock = new ReentrantLock(); 
    ReentrantLock lock = new ReentrantLock(true);//实现公平锁,进来的线程组成一个队列。
    
    // 在进程里面加锁
    public void run(){
    	try{
            // 加锁
        	lock.lock()
        	//同步代码块
        
        }finally{
            // 解锁
            lock.unlock()
        }
    }
    

3. 线程通信

  • 线程通信有多种方式
    • 第一种方式Object方法:wait() 、notify()、notifyAll()
      • 这三个方法必须使用在同步代码块或同步方法中,且必须是同步监视器
      • sleep()与wait()方法的不同
        • 相同点;两个方法都可以使当前线程进入阻塞状态
        • 不同点:
          • 声明位置不同:sleep()声明在Thread类中,wait()声明在Object类中
          • 调用要求不同:sleep()可以在任何需要的场景下调用,wait()必须使用在同步代码块中
          • 如果使用在同步代码块或者方法中:sleep()不会释放锁,wait()会释放锁,wait()需要被notify()唤醒
// 令当前线程挂起,并放弃cpu、同步资源并等待。
//  可以被notify()、notifyAll()方法唤醒
wait() 
// 随机唤醒一个正在等待的线程, 如果有优先级,就唤醒优先级高的。
notify()

// 唤醒所有一个正在等待的线程
notifyAll()
  • 经典的线程通信问题
    • 生产者和消费者

4. 线程的新增创建方式

  • 线程的创建方式
    • 继承Thread类
    • 实现Runnable接口
    • 实现Callable接口:JDK5.0新增
    • 使用线程池
  • 线程创建方式三:实现Callable接口
    • 与Runable相比,Callable的功能更强大一些
      • 相比run()方法,可以有返回值
      • 方法可以抛出异常,run()方法只能在内部处理异常不能抛出
      • 支持泛型的返回值
      • 需要借助FutureTask类,比如获取返回结果等
        • Future接口
          • 可以对Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等
          • FutureTask是Future接口的唯一实现类
          • FutureTask同时实现了Runnable、Future接口。它既可以作为Runnable被线程执行,有可以作为Future得到Callable的返回值
class MyThread implements Callable<T>{
    // 线程需要执行的方法
    @Override
    public T call() throws Exception {
        return null;
    }
}

// #调用方式,需要使用到FutureTask
MyThread mythread = new MyThread();
FutureTask futureTask = new FutureTask(mythread);
// ##启动线程
new Thread(futureTask).start();
// ##获取线程返回内容返回内容,并处理异常(不需要就不调)
T reback = futureTask.get();

  • 线程创建方式四:线程池【最常用
    • 优点
      • 提高响应速度(减少了创建新线程的时间)
      • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
      • 便于线程管理(在ThreadPoolExecutor实现类中)
        • corePoolSize:核心池的大小
        • maximumPollSize:最大线程数
        • keepAliveTime:线程没有任务时最多保持多长时间后会终止
    • 线程池相关API:接口ExecutorService,工具类Executors
      • ExecutorService两个方法
        • execute():执行任务,没有返回值
        • submit():执行任务存在返回值,适合Callable
      • Executors工具类,创建并返回不同类型的线程池
        • Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
        • Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
        • Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
        • Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
class MyThread implements Callable<String>{
    @Override
    public String call() throws Exception {
        return  Thread.currentThread().getName()+": test";
    }
}
// #线程池
// ## 提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
// ##启动线程
Future<String> submit = service.submit(new MyThread());
Future<String> submit1 = service.submit(new MyThread());

String o = submit.get();
String o1 = submit1.get();
System.out.println(o);
System.out.println(o1);
// ##关闭线程池
service.shutdown();

// ###输出
pool-1-thread-1: test
pool-1-thread-2: test
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

勇者lin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值