Java中对多线程理解

一、概念

程序、进程、线程的基本概念

  • 程序:为了完成特定任务,用某种语言编写的一组指令集。一段静态的代码块,静态对象
  • 进程:正在运行的一个程序,相当于是一个程序执行的生命周期过程。
  • 线程:进程内的一条执行路线,拥有独立的运行栈和程序计数器(pc)

单线程与多线程的区别

  • 单线程:
    • 相当于程序运行时只有一条主线流程
    • 流式程序速度上运行快
  • 多线程
    • 相当于程序运行时有多条主线流程运行
    • 提高应用程序的响应
    • 提高CPU的利用率
    • 改善程序结构,将复杂的进程拆分为多个线程运行

二、使用

Java的一门支持多线程的编程语言,通过java.lang.Thread类来体现使用

线程的创建

JDK1.5之前创建新执行线程有两种方法

  • 继承Thread类的方式
    • 定义子类继承Thread类
    • 子类中重写Thread类中的run方法
    • 创建Thread子类对象,即创建了线程对象
    • 调用线程对象start方法:启动线程,调用run方法
  • 实现Runnable接口的方式
    • 定义子类,实现Runnable接口
    • 子类中重写Runnable接口中的run方法
    • 通过Thread类含参构造器创建线程对象
    • 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中
    • 调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法

注意:一个线程对象只能调用一次start()方法启动

继承方式和实现方式

  • 继承方式
    • Java中只能单继承,之后的代码重构会有很大弊端
    • 继承之后可以直接使用Thread的API方法
    • 线程使用各自对象数据的共享性差,要通过static来实现耗费内存空间
  • 实现方式
    • Java中接口的多继承,代码重构方便
    • 对于Thread中API的调用要通过 Thread.currentThread方式
    • 线程使用同一个对象数据间共享性方便。

Java中的线程分为两类:一种是 守护线程,一种是 用户线程。

  • 它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开
  • 守护线程是用来服务用户线程的,通过在start()方法前调用
  • thread.setDaemon(true)可以把一个用户线程变成一个守护线程
  • Java垃圾回收就是一个典型的守护线程
  • 若JVM中都是守护线程,当前JVM将退出

Java中线程的生命周期

在这里插入图片描述

线程的同步(synchronized)

同步代码块和同步方法块

对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以
参与执行

同步锁的范围
  1. 如何 找问题 , 即代码是否存在线程安全 ? ( 非常重要 )
    1. 明确哪些代码是多线程运行的代码
    2. 明确多个线程是否有共享数据
    3. 明确多线程运行代码中是否有多条语句操作共享数据
  2. 如何解决呢 ? ( 非常重要 )
    1. 对多条操作共享数据的语句,只能让一个线程都执行完
    2. 在执行过程中,其他线程不可以参与执行
    3. 所有操作共享数据的这些语句都要放在同步范围中
  3. 切记 :
    范围太小:没锁住所有有安全问题的代码
    范围太大:没发挥多线程的功能。
释放锁的操作
  1. 当前线程的同步方法、同步代码块执行结束
  2. 当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、该方法的继续执行
  3. 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
  4. 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁
不会释放锁的操作
  1. 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行
  2. 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)。
  3. 应尽量避免使用suspend()和resume()来控制线程
线程死锁
  1. 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
  2. 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续

Lock(锁)

  • 从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当
  • java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具
  • ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的ReentrantLock,可以显式加锁、释放锁

synchronized与Lock

  1. Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域自动释放
  2. Lock只有代码块锁,synchronized有代码块锁和方法锁
  3. 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
  4. 优先使用顺序:
    Lock 同步代码块(已经进入了方法体,分配了相应资源) 同步方法(在方法体之外)

wait()、notify()、notifyAll()

  • 在Object类中声明

  • wait():令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行

  • notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待

  • notifyAll ():唤醒正在排队等待资源的所有线程结束等待.

JDK5.0 新增线程创建方式

方式一:实现Callable接口

Callable与Runnable相比功能更强大些

  • 相比run()方法,可以有返回值
  • 方法可以抛出异常
  • 支持泛型的返回值
  • 需要借助FutureTask类,比如获取返回结果
    • 可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
    • FutrueTask是Futrue接口的唯一的实现类
    • FutureTask 同时实现了Runnable, Future接口。它既可以作为
    • Runnable被线程执行,又可以作为Future得到Callable的返回值
方式二:线程池

概念:

jdk1.5之后,jdk内置了线程池。一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。

优势:

  • 降低资源消耗
  • 提高响应速度
  • 提高线程的可管理性

使用:

java.util.concurrent.Executor接口是线程池的顶级接口,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。

java.util.concurrent.ExecutorService才是真正的线程池接口,它继承了Executor接口。

java.util.concurrent.Executors线程工厂类,里面提供了一些静态工厂,用于生成一些常用的线程池。官方建议使用Executors工程类来创建线程池对象。

四种线程池:

  • newFixedThreadPool:
    • 创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。
  • newCachedThreadPool:
    • 创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。
  • newScheduledThreadPool :
  • 可安排在给定延迟后运行命令或者定期地执行的线程池
  • newSingleThreadExecutor:
    • 单例线程池(这个线程池只有一个线程),这个线程池可以在线程死后(或发生异常时)重新启动一个线程来替代原来的线程继续执行下去
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值