多线程随笔

童忆.jpg


基本概念

程序:为完成某任务,用某种语言编写所组成的静态代码

进程:正在运行的程序,也就是加载到内存中。一个进程有一个方法区和一个堆

线程:一个程序内部的执行路径,一个进程通常会有多个线程。一个线程有一个虚拟机栈和一个程序计数器,也就是进程下的线程共享该进程的方法区和堆区,且每个线程都有自己独有的虚拟机栈和程序计数器(PC)(拓展:JVM调优也就是调整共享的这部分)

并行:多个CPU同时执行多个任务

并发:一个CPU“同时”(采用时间片)执行多个任务,如:秒杀、多个人做同一个事

多线程优点:提高程序响应(对图形化界面更有意义,提升用户体验)、提高CPU利用率、改善程序结构,利于理解与修改

多线程场景:程序需要同时执行两个或多个任务、需要实现一些等待任务(文件读写、网络操作、搜索等)、需要一些后台运行的程序

创建和使用

常用方法

  • void start()作用:
    a.启动当前线程
    b.调用当前线程的run()方法
    说明:start()只能对同一线程调用一次,也不能直接使用run()方法去启动线程(直接调用run()还是在主线程中操作的,目标线程并未启动),若方法名后续未使用,可以考虑使用匿名子类的方式启动
  • run():线程在被调度时执行的操作
  • void setName():设置该线程的名称(Thread有个构造器也可以命名)
  • String getName():返回该线程的名称
  • static Thread currentThread():返回当前线程。若在Thread子类中就是this,通常用于主线程和Runnable实现类
  • static void yield():线程让步
    a.释放CPU的执行权,把执行机会让给优先级相投或者更高的线程,也可能释放完成后,下次还是该线程抢到执行权。
    b.若队列中没有同优先级的线程,则忽略此方法
  • join():当某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到join()方法加入的join线程执行为止。低优先级的线程也可以获得执行
  • static void sleep(long millis):指定时间毫秒
    a.令当前活动线程在指定时间段内放弃对CPU控制(睡眠,在millis时间内,当前线程为阻塞状态),使其他线程有机会被执行,时间到后重新排队
    b.抛出InterruptedException异常
  • stop():强制线程生命周期结束,不推荐使用(方法已过时)
  • boolean isAlive:判断线程时候还活着

线程的调度

调度策略:时间片、抢占式(高优先级的线程抢占CPU)

Java的调度方法:

  1. 同优先级进程组成先进先出队列(先到先服务),使用时间片策略
  2. 对高优先级,使用优先调度的抢占式策略

线程的优先级等级:
MAX_PRIORITY:10(最大优先级)
MIN_PRIORITY:1(最小优先级)
NORM_PRIORITY:5(默认优先级)

涉及方法:
getPriority():返回线程优先级
setPriority(int newPriority):改变线程的优先级

说明:
线程创建时继承父线程的优先级
优先级只是针对概率讲,低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用。

方式一:继承Thread类

  1. 创建一个继承于Thread类的子类
  2. 重写Thread的run(),将你想让此线程操作什么写在run()方法中
  3. 创建Thread类的子类对象
  4. 通过此对象调用start()

方式二:实现Runnable接口

  1. 创建一个实现了Runnable接口的类
  2. 实现类去实现Runnable种的抽象方法:run()
  3. 创建实现类的对象
  4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
  5. 通过Thread类的对象调用start()方法

方式一、二对比

开发中优先使用Runnable接口的方式
原因:

  1. 实现的方式没有类的单继承的局限性
  2. 实现的方式更适合来处理多个线程有共享数据的情况

联系:

  1. Thread类本身也是实现了Runnable接口
  2. 两种方式都需要重写run()方法,将线程要实现的逻辑写在run()方法中

线程的生命周期

Thread.State类定义了线程的几种状态

  • 新建:当一个Thread类或其子类的对象被声明创建时,新生的线程对象处于新建状态(也就是new时)
  • 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时他已然具备了运行的条件,只是没分配到CPU资源时所处的状态
  • 运行:当就绪的线程被调度并获得了CPU资源时,便进入了运行状态,run()方法定义了线程的操作和功能
  • 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态
  • 死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现其他异常导致结束

线程的通信

  • 常用通信方法,以下三个方法都定义在Object中
    wait()、notify()、notifyAll()
  • 线程分类:
  1. 守护线程(e.g. java gc垃圾回收线程)
  2. 用户线程(e.g. java main()主线程)
  • 说明:守护线程时用来服务用户线程的,通过在start()前调用,thread.setDaemon(true)可以把一个用户线程变成一个守护线程,Java的垃圾会后就是一个典型的守护线程,若JVM中都是守护线程,则当前JVM将退出(兔死狗烹)
  • sleep()和wait()的异同
    同:一旦执行方法,都可以使当前线程进入阻塞状态
    异:
  1. 两个方法声明的位置不同,Thread类种声明sleep(),Object种声明wait()
  2. 调用的要求不同,sleep()可以在任何场景下调用,wait()必须在同步方法或同步代码块中调用
  3. 关于是否释放同步监视器,如果两个方法都是用在同步代码块或同步方法中时,sleep()不会释放同步监视器,wait()会释放同步监视器

线程的同步(为了解决线程安全问题)

概念

  • 多个线程执行的不确定性引起的执行结果的不稳定

  • 多个线程变量的共享,会造成操作结果的不完整性,会破坏数据

  • 方式一:同步代码块

synchronized(同步监视器){
    // 需要被同步的代码
 }
  1. 说明:操作共享数据的代码,即为需要被同步的代码(不能包含代码多了,也不能包含少了)
  2. 共享数据:多个线程共同操作的变量,e.g. 例子中的ticket
  3. 同步监视器:俗称:锁(任何一个类的对象都可以充当同步监视器也就是锁)
  4. 同步监视器要求:多个线程必须要共同使用同一把锁,锁必须唯一。
  5. 补充:可以考虑使用this充当同步监视器(继承Thread方式慎用,也可以考虑当前类来充当同步监视器)
  • 方式二:同步方法
    说明:如果操作共享数据的代码完整的声明在一个方法中,我们不妨使用同步代方法来解决线程安全问题
  1. 非静态的同步方法使用的同步监视器是this
  2. 静态的同步方法使用的同步监视器是当前类本身
  • 死锁(e.g. 两人吃饭一双筷子,一人一只都不放弃等对方放弃,谁也没吃到)
    说明:
  1. 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
  2. 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
    解决方案:
  3. 专门的算法、原则
  4. 尽量减少同步资源定义
  5. 尽量避免嵌套同步
  • 方式三:Lock锁(JDK5.0新增)
    方法:lock()加锁,unlock()解锁

  • synchronized和Lock的异同(使用推荐顺序 Lock -> 同步代码块 -> 同步方法)
    同:两者都能解决线程安全问题
    异:

  1. synchroized机制在执行完相应的代码逻辑后自动释放同步监视器
  2. Lock需要手动启动同步,结束同步也需手动实现
  • 小结
  1. 同步的方式,解决了线程的安全问题
  2. 操作同步代码时,只能由一个线程参与,其他线程等待,相当于是一个单线程的过程效率较低
  3. 有可能会引发死锁

JDK5.0新增线程创建方式

方式三:实现Callable接口

与使用Runnable相比,Callable功能更加强大

  1. 相比run(),可以有返回值
  2. 方法可以抛出异常
  3. 支持泛型的返回值
  4. 需要借助FutureTask类,比如获取返回结果

方式四:使用线程池

说明:线程的创建销毁会使用大量资源,比如并发情况下的线程,对性能影响很大。
思路:提前创建好好多个线程,放入线程池中,使用时直接获取,使用完在放回线程池中,可以避免创建销毁以实现重复利用,类似公交车
优点:提高响应速度、降低资源消耗、便于线程管理
相关API:ExecutorService 和 Executors

ExecutorService:真正的线程池接口,常见子类ThreadPoolExecutor

  1. void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
  2. <T> Future<T> submit(Callable<T> task):执行任务,有返回值,一般用来执行Callable
  3. void shutdown():关闭连接池

Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

  1. Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
  2. Executors.newFixedThreadPool(n):创建一个可重用固定线程数的线程池
  3. Executors.newSingleThreadExecutos():创建一个只有一个线程的线程池
  4. Executors.newScheduledThreadPool(n):创建一个线程池,它可以安排在给定延迟后运行命令或者定期的执行
    Java线程生命周期.jpg
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值