多线程详解

本文详细介绍了Java线程的基本概念,包括程序与进程、线程的创建与生命周期。线程可以通过Thread类或实现Runnable接口创建,线程同步通过synchronized关键字实现,防止数据不一致。此外,还探讨了线程安全类与非线程安全类,死锁现象,以及Lock接口的使用。最后提到了线程池的概念,强调其在处理大量并发请求时提高服务器性能的作用。
摘要由CSDN通过智能技术生成

1. 基本概念

1.1 程序和进程

  • 程序 : 数据结构 + 算法 ,存放在硬盘上的可执行文件,即.exe文件
  • 进程 :主要指运行在内存中的可执行文件
  • 目前主流的操作系统都支持多进程,让操作系统同时执行多个任务

1.2 线程

  • 线程就是进程内部的程序流,也就是说每个进程的内部,是支持多线程的
  • 线程是轻量级的,会共享所在进程的系统资源。主流的开发都是多线程
  • 多线程再用时间片轮转法,来保证线程的并发执行,所谓并发就是指宏观并行,围观穿行
    • 从一个时间段上看,是多线程,多个线程同时执行
    • 从一个时间点上看,是单线程,单个线程执行

2. 线程的创建【重点

2.1 Thread类

  • java.lang.Thread 代表线程,任何线程都是Thread类的子类
  • Thread类是线程的模板,封装了复杂的线程开启等操作

2.2 创建方式【2】

  1. 自定义Thread类,重写run方法。然后创建该类对象,来调用start方法(不是直接调用run方法)

    • 如果直接调用run方法,本质上就是单线程
    • 本来是一个main主线程,调用start方法后,一分为二,变为两个线程
    • 两个线程,没有先后顺序,有系统的调度算法决定
    // subThread是 Thread的子类
    Thread t = new subThread();
    // JVM会自动调用run方法
    t.start();
    
  2. 自定义类实现Runnable接口,并且重写run方法。再创建该类的对象作为实参来构造Thread类型对象,然后使用Thread类对象调用 run方法

    • 虽然是Thread类对象调用run方法,其实调用的是Runnable接口实现类中重写的run方法

      new Thread(new Runnable() {
          // 匿名内部类中,重写run方法
          @Override
          public void run() {
              System.out.println("Runnable接口实现线程的创建");
          }
      }).start();
      // 上次代码,使用了匿名对象和匿名内部内
      
    • 使用 lambda : (参数列表)->{方法体}

      new Thread(()->System.out.println("Runnable接口实现线程的创建");).start();
      

2.3 相关方法

方法声明功能介绍
Thread()无参构造
Thread(String name)根据参数指定线程名,来构造对象
Thread(Runnable target)实现Runnable接口,构造对象
Thread(Runnable target, String name)根据参数,指定引用名称来构造对象
void run()1. 若使用了Runnable引用构造了线程对象,调用run方法时最终调用接口中的重写的run
2. 若没有使用Runnable接口引用构造线程对象,调用该方法时,啥也不做
void start()用于另外启动一个线程,Java虚拟机会自动调用该线程的run方法

Thread类中的 run方法测试

Thread thread = new Thread();
thread.run();
  • run方法源码分析:

    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
    
    • 从上面的Java源码源码中我们可以看到,如果target 成员变量=null时,将不执行任何方法。

    • 那这个target?怎么判断呢?看构造方法

      • Thread(Runnable target)

        public Thread(Runnable target) {
            init(null, target, "Thread-" + nextThreadNum(), 0);
        }
        
      • Thread()

        public Thread() {
            init(null, null, "Thread-" + nextThreadNum(), 0);
        }
        
      • 其实这个target 就是Runnable接口引用,底层这个Runnable接口引用,会赋值给 target变量。所以,在构造方法时,就会确定target是否为空

        1. 所以,若没有使用Runnable接口引用构造线程对象作为参数,在调用该方法时,就会啥也不做。
        2. 使用自定义Thread类,来创建线程时,必须重写 run方法,不然也会是啥也不做
        3. 如果使用实现Runnable接口作为参数,来创建线程。那么:底层的target = Runnable实现类对象。所以,调用run方法,其实就是调用 Runnable实现类对象的重写的run方法

3 线程的生命周期【了解】

在这里插入图片描述

步骤说明
new Thread()创建线程(并没有执行)
start()JVM执行 run方法,激活线程,进入就绪状态
线程调度器使用线程调度器调用该线程,线程进入运行状态。新线程开启

运行状态的3种情况:

  1. 如果cpu分配的时间片用完后,该线程的活还没干完,将进入就绪状态,继续等待调度器的调度
  2. 如果线程的任务,执行完成后,将进入消亡状态
  3. 在线程执行的过程中,发生了阻塞事件,如:sleep方法等。线程将进入阻塞状态,在阻塞状态解除后,进入就绪状态,等待调度器的调度

4. 线程的编号和名称【了解】

  • 编号:线程的唯一标识
  • 名称:线程名
方法声明功能介绍
long getId()获取当前线程的编号
String getName()获取当前线程的名称
void setName(String name)修改线程名
static Thread currentThread()获取当前正在执行线程的引用

5. 常用方法【重点】

方法声明功能介绍
static void yield()让当前线程让出CPU,进入就绪状态
static void sleep(times)让线程,进入阻塞状态,并且休眠 times毫秒
1. 其他线程可能会打断阻塞状态,则发生InterruptedException异常
2. 所以使用sleep方法,要使用try/catch
3.(因为,子类重写的方法不能抛出比父类更大的异常,只能使用try/catch,不能使用throws
int getPriority()获取当前线程的优先级
1. 最大优先级:10
2. 最小优先级:1
void setPriority( int newPriority)修改线程的优先级
优先级越高的线程不一定先执行,但该线程获取到时间片的机会会更多一些
void join()等待该线程终止
void join(long millis)参数指定等待的额毫秒数(等待结束后,直接和子线程抢夺时间片运行)
boolean isDaemon()判断是否为守护线程
void setDaemon (boolean on )设置线程为守护线程
  • 注意,优先级越高的线程不一定先打印。只是所,优先级越高CPU分配给他的时间片几率就越高

  • 守护线程:

    1. 必须在主线程启动前,将子线程设置为守护线程

      // 创建线程
      Thread t = new Thread();
      // 必须在start前,设为守护线程
      t.setDaemon(true);
      t.start();
      
    2. 守护线程,在主线程结束后。不管守护线程是否完成执行任务,都要立马结束掉线程

  • 小应用,主程序让新线程执行5s后,停掉子线程

    • 利用 flag 和 while来解决
    // flag私有属性,和setter
    boolean flag = true;
    public void setFlag(boolean flag) {
        this.flag = flag;
    }
    
    public void method(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (flag){
                    System.out.println("子线程正在执行...");
                    try {
                        // 让当前线程(main主线程)睡眠1秒, 效果就是1s打印一次
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
    
    public static void main(String[] args) {
        Thread01 t = new Thread01();
        t.method();
    
        try {
            // 让当前线程(main主线程)睡眠5秒
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t.setFlag(false);
        System.out.println("子线程结束");
    }
    

6. 线程的同步机制【重点】

6.1 基本概念

  • 多个线程访问同一资源时,需要对线程之间,进行通信协调,保证数据的一致性
  • 线程安全:多个线程并发读写同一个临界资源时会发生线程并发安全问题
  • 异步操作:多线程并发的操作,各自独立运行
  • 同步操作:多线程串行的操作,先后执行的顺序。

6.2 线程同步的实现

  • IDEA快捷键:Ctrl + Alt + T

  • 使用synchronized关键字来实现线程的同步 (也叫对象锁机制),从而保证线程执行的原子性

    • 部分代码的锁定
    synchronized(类类型的引用) {
    	// TODO 编写所有需要锁定的代码;
    }
    
    
    • 全部代码的锁定

      synchronized(this) {
          // TODO 整个方法体的代码
      }
      

      如果使用Runnable接口实现类做参数,实现的线程。那么,this就是Runnable接口的实现类引用

    • synchronized修饰类:

      • 比如:StringBuffer的append方法
      public synchronized StringBuffer append(char c)
      
  • 注意:

    如果synchronized的参数,不是同一个对象的引用,那么会锁不住。就可能出现线程安全问题

6.3 线程安全类、非线程安全类

  • StringBuffer类是线程安全的类,但StringBuilder类非线程安全的类。
  • Vector类和Hashtable类是线程安全的类,但ArrayList类和HashMap类不是线程安全的类。
  • Collections.synchronizedList()Collections.synchronizedMap()等方法实现安全。
    • 即,将ArrayList、HashMap非线程安全的类,实现线程安全

6.4 死锁

  • 线程-1,执行的代码:

    public void run(){
        synchronized(a){ //持有对象锁a,等待对象锁b
            synchronized(b){
            	// TODO 编写锁定的代码;
            }
        }
    }
    
  • 线程-2,执行的代码:

    public void run(){
        synchronized(b){ //持有对象锁a,等待对象锁b
            synchronized(a){
            	// TODO 编写锁定的代码;
            }
        }
    }
    
  • 两个线程竞争了不可剥夺的资源。比如:上面2个线程,同时执行,线程1需要锁b,才能释放锁a。而线程2需要锁a,才能释放锁b。这样相互就形成了死锁,永远都打不开

  • 结论

    在以后的开发中尽量减少同步的资源,减少同步代码块的嵌套结构的使用!

6.5 使用 Lock(锁) 实现线程同步

6.5.1 基本概念

  • 从Java5开始提供了更强大的线程同步机制,用显式定义的同步锁对象来实现
  • java.util.concurrent.locks.Lock接口是控制多个线程,对共享资源进行访问的工具
  • Lock接口的实现类:ReentrantLock类,该类拥有和 synchronized 相同的并发性。在线程安全的控制中,经常使用 ReentrantLock类 显式示加锁和释放锁

6.5.2 常用方法

方法声明功能介绍
ReentrantLock()无参构造,构造对象
void lock()获取锁
void unlock()释放锁

举例:

ReentrantLock rl = new ReentrantLock();
public void run(){
    // 加锁
    rl.lock();
    
    // TODO 加锁的代码部分
    
    // 释放锁
    rl.unlock();
}

6.5.3 和synchronized的比较

  • Lock是显式锁,需要手动的调用去加锁、解锁;synchronized 是隐式锁,执行锁定的代码后自动释放
  • Lock锁,Java虚拟机将花费更少的时间来调度线程,性能更好
  • Lock锁,只能同步代码块方式的锁;synchronized,有同步代码块、同步方法这两种方式的锁

6.6 Object类中常用方法

方法声明功能介绍
void wait()将线程进入阻塞状态
void wait( long timeout )指定参数的时间过去后,接触阻塞状态
void notify()将进入阻塞状态的线程唤醒
void notifyAll()用于唤醒所有进入阻塞的线程

6.7 生产者/消费者模型

在这里插入图片描述

7. 线程池

  • 一个客户端就要创建一个新的线程

  • 当大量客户端连接服务器,就要不断地销毁和创建线程,这严重影响到了服务器的性能

  • 所以引入,线程池这一概念。任务直接提交给整个线程池,而不是直接交给某个线程,线程池在拿到任务
    后,它就在内部找有无空闲的线程,再把任务交给内部某个空闲的线程。任务是提交给整个线程
    池,一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。

7.1 相关类和方法:

  • 从Java5开始提供了线程池的相关类和接口:

    • java.util.concurrent.Executors类
    • java.util.concurrent.ExecutorService接口
  • Executors类

    • 是个工具类线程池的工厂类

    • 常用方法如下

      方法声明功能介绍
      static ExecutorService newCachedThreadPool()创建一个可根据需要创建新线程的线程池
      static ExecutorService newFixedThreadPool( int nThreads )创建一个可重用固定线程数的线程池
      static ExecutorService newSingleThreadExecutor()创建一个只有一个线程的线程池

) | 创建一个只有一个线程的线程池 |

  • ExecutorService接口

    • 是真正的线程池接口,主要实现类是ThreadPoolExecutor

    • 常用方法如下

      方法声明功能介绍
      void execute(Runnable command)执行任务和命令
      Future submit(Callable task)执行任务和命令
      void shutdown()启动有序关闭
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

疯子的模样

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

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

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

打赏作者

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

抵扣说明:

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

余额充值