JAVA语言-线程
黄泽民 2018-04-03 来源 : 阅读 440 评论 0

摘要:要了解JAVA语言的线程,就必须先要了解什么是进程。,进程是指运行中的应用程序,每个进程都有自己独立的地址空间(内存空间)。目前的操作系统都支持多进程。进程是资源分配的单元,线程是资源调度的单元,线程,又称为轻量级进程,是程序执行流的最小单元,是程序中一个单一的顺序控制流程。了解这些,可以使我们在JAVA语言的道路上越走越远。

线程

l 进程的概念

要了解线程,就必须先要了解什么是进程。

进程是指运行中的应用程序,每个进程都有自己独立的地址空间(内存空间)。

目前的操作系统都支持多进程。

l 线程的概念(进程是资源分配的单元,线程是资源调度的单元)

Ø 线程,又称为轻量级进程,是程序执行流的最小单元,是程序中一个单一的顺序控制流程。

Ø 线程是进程中的一个实体,是被系统独立调度和分派的基本单位。

Ø 线程本身不拥有系统资源,与同属一个进程的其他线程共享所在进程所拥有的资源。

Ø 同一进程中的多个线程之间可以并发执行

Ø 可以在单个程序中同时运行多个线程来完成不同的工作(多线程)

l 线程的用处和好处

如果没有学过线程,就不要说自己学过Java。线程在实际编程过程过应用非常的广泛,例子有很多,比如迅雷的多线程下载技术,360安全卫士的各个功能可以同时执行等。总之一句话:只要应用程序涉及到了并发,就离不开多线程编程。

l Thread类

Java通过Thread类将线程所必须的功能都封装了起来,建立一个线程,必须要有一个执行函数,Java中这个执行函数对应Thread类的run()方法。

Ø 常用方法

getName() // 获得线程的名称

currentThread() // 返回当前正在执行的线程对象的引用

sleep(long millis) // 让线程睡眠指定秒数(释放CPU资源)

l 创建线程(有两种方式)

PS:创建线程后必须要调用start()方法来启动线程。

1. 通过继承Thread类创建线程

示例代码:

JAVA语言-线程 

2. 通过实现Runnable接口创建线程

实现Runnable接口的类必须借助Thread类才能创建线程。

通过Runnable接口创建线程分为两步:

1. 创建实现Runnable接口的类的实例

2. 创建一个Thread类对象,将第一步实例化得到的Runnable对象作为参数传入Thread类的构造方法

示例代码:

 JAVA语言-线程

 两种创建线程的方式之间的区别

实现runnable接口的好处:

1. 将线程的任务从线程的子类中分离出来,进行了单独的封装。按照面向对象的思想将任务封装成了对象。

2. 避免了Java单继承的局限性

线程的生命周期

与人有生老病死一样,线程也有它完整的生命周期。

 JAVA语言-线程

l 新建状态(New):

Ø 代表线程的对象已经被初始化,此时该线程仅仅是一个空对象,它具备了线程的一些特征,但系统还没有为其分配资源,这时线程处于新生状态。

ü 线程处于新建状态时,可通过Thread类的方法来设置线程的各种属性,如线程的优先级(setPriority)、线程名(setName)和线程类型(setDaemon)等。

PS:不能对已经启动的线程再次调用start()方法。

l 就绪状态(Runnable):

Ø 处于就绪状态的线程已经具备了运行条件,但并没有被分配到CPU资源,处于线程就绪队列(尽管是采用队列形式,但事实上,更恰当的是把它称为可运行池而不是可运行队列。因为CPU的调度不一定是按照先进先出的顺序来调度的),等待系统为其分配CPU资源。就绪状态并不是运行状态,当系统选定一个等待执行的Thread对象后,它就会从就绪状态进入运行状态,系统挑选的行为称之为“CPU调度”。一旦获得CPU资源,线程就进入运行状态并自动调用自己的run方法。

l 运行状态(Running):

Ø 处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。

Ø 处于就绪状态的线程,如果获得了CPU的调度,就会从就绪状态变为运行状态,执行run()方法中的任务。如果该线程失去了CPU资源,就会又从运行状态变为就绪状态。重新等待系统分配资源。也可以对在运行状态的线程调用yield()方法,它就会让出CPU资源,再次变为就绪状态。

ü 可以通过Thread类的isAlive()方法来判断线程是否处于就绪/运行状态。

ü 当线程处于就绪/运行状态时,返回true,当返回false时,可能线程处于阻塞状态,也可能处于死亡(停止)状态。

Ø 当发生如下情况是,线程会从运行状态变为阻塞状态:

ü 线程调用sleep方法主动放弃所占用的系统资源

ü 线程调用一个阻塞式I/O方法,在该方法返回之前,该线程被阻塞

ü 线程试图获得一个同步监视器,但更改同步监视器正被其他线程所持有

ü 线程在等待某个通知(notify)

ü 程序调用了线程的suspend方法将线程挂起。不过该方法容易导致死锁,所以程序应该尽量避免使用该方法。

l 阻塞状态(Blocked):

Ø 处于运行状态的线程因某些原因不能继续运行时,就会进入阻塞状态。

这些情况包括:

ü 当执行了某个线程对象的suspend()、sleep()等阻塞类型的方法时,该线程对象会被置入一个阻塞集(Blocked Pool)内,等待被唤醒(执行resume()方法)或因为超时而自动苏醒。

ü 当多个线程试图进入某个同步区域(synchronized)时,没能进入该同步区域的线程会被置入锁定集(Lock Pool),直到获得该同步区域的锁,进入就绪状态。

ü 当线程执行了某个对象的wait()方法时,线程会被置入该对象的等待集(Wait Pool)中,直到执行了该对象的notify()方法,wait()/notify()方法的执行要求线程首先获取到该对象的锁。

Ø 在阻塞状态的线程不能进入就绪队列。只有当引起阻塞的原因消除时,如睡眠时间已到,或等待的I/O设备空闲下来,线程便转入就绪状态,重新到就绪队列中排队等待,被系统选中后从原来停止的位置开始继续运行。

Ø 在使用suspend阻塞线程后,可以通过resume方法唤醒线程,而使用sleep使线程休眠后,只能在设定的时间后使线程处于就绪状态。

l 死亡状态(Dead):

Ø 线程在run()方法执行结束后进入死亡状态。此外,如果线程执行了interrupt()或stop()方法,那么它也会以异常的方式进入死亡状态。

Ø 线程一旦死亡,就不能复生,处于死亡状态的线程对象不能再次调用start()方法。

线程同步

l 为什么需要同步

Ø 线程同步是为了防止多个线程访问一个数据对象(竞争资源)时,对数据造成破坏

Ø 线程同步是保证多线程安全访问竞争资源的一种手段

l 同步和锁定

Ø Java中每个对象都有一个内置锁

Ø 当程序运行到非静态的synchronized同步方法时,自动获得与正在执行代码类的当前实例(this)有关的锁,当程序运行到synchronized同步代码块时,自动获得锁定对象的锁。(synchronized同步静态方法时,锁就是当前类的class对象)

Ø 获得一个对象的锁也称为获取锁、锁定对象、在对象上锁定或在对象上同步。当程序运行到synchronized同步方法或代码块时该对象锁才起作用。

Ø 一个对象只有一个锁。所以,如果一个线程获得该锁,就没有其他线程可以获得,直到第一个线程锁释放。这也意味着任何其他线程都不能进入synchronized方法或代码块,直到该锁被释放。释放锁是指持锁线程退出了synchronized同步方法或代码块。

l 对于同步,一般而言需要完成两个操作:

1. 把竞争访问的资源标识为private

2. 使用synchronized关键字同步那些访问资源的方法或代码块

l 同步代码块的格式

JAVA语言-线程

l 同步机制的原理

Java中的每个对象都有一个标志位,该标志位具有0,1两种状态,其开始状态为1,当某个线程执行了synchronized(object)语句后,object对象的标志位变为0的状态,直到执行完整个synchronized语句中的代码块后,该对象的标志位会回到1状态。

当一个线程执行到synchronized(object)语句的时候,先检查object对象的标志位,如果为0状态,表名已经有另外一个线程正在执行synchronized代码块中的代码,那么这个线程将暂时进入阻塞状态,释放cpu资源,直到另外的线程执行完相关的同步代码,并将object对象的标志位变为1状态,这个线程的阻塞就会被取消,阻塞的线程继续运行,该线程将object的标志位变为0状态,防止起塔的线程再进入先关的同步代码块中。

l 同步的好处与弊端

好处:解决了线程的安全问题

弊端:降低了效率

l 同步的前提

必须是多线程并且用的是同一把锁。

l 单例模式中的线程同步问题

在懒汉式(延迟加载的单例模式)中,存在这线程安全的隐患,所以需要进行同步处理。(采用双重判断的形式同时解决效率和线程安全的问题)

l 死锁(不应该出现的问题,要避免)

常见情景:同步的嵌套

线程间的通信

l wait()、notify()、notifyAll()这三个由Java.lang.Object类提供,用于协调多个线程对共享数据的访问

Ø wait ()

ü 当调用wait()后,线程会释放掉它所占有的“锁标志”,从而使线程所在对象中的其它synchronized数据可被别的线程使用,被wait的线程会被存储到等待池中

ü wait ()有两种形式:

1) 接受一个毫秒值,用于在指定时间长度内暂停线程,使线程进入停滞状态。

2) 不带参数,代表wait()在notify()或notifyAll()之前会持续停滞

Ø notify()

当对一个对象执行notify()时,会从线程等待池中移走该对象的任意一个线程,并把它放到就绪队列中。

Ø notifyAll()

当对一个对象执行notifyAll()时,会从线程等待池中移走所有该对象的所有线程,并把他们放到就绪队列中。

PS:以上这些方法必须定义在同步代码中,因为这些方法是用于操作线程状态的,就必须要明确到底操作的是哪个锁上的线程。否则,虽然编译能通过,但在运行时会发生IllegalMonitorStateException(非法监控状态异常)

l 思考:为什么操作线程的方法wait、notify、notifyAll被定义在了Object类中?

答: 因为这些方法都是监视器的方法(监视器就是锁对象),而监视器可以是任意对象,既然是任意对象,那么这些方法就必须定义在Object类中。

本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注职坐标编程语言JAVA频道!

本文由 @职坐标 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论
本文作者 联系TA

擅长javase核心技术

  • 11
    文章
  • 1522
    人气
  • 83%
    受欢迎度

已有6人表明态度,83%喜欢该老师!

进入TA的空间
名师指导 直通车
  • 索取资料 索取资料 索取资料
  • 答疑解惑 答疑解惑 答疑解惑
  • 技术交流 技术交流 技术交流
  • 职业测评 职业测评 职业测评
  • 面试技巧 面试技巧 面试技巧
  • 高薪秘笈 高薪秘笈 高薪秘笈
TA的其他文章 更多>>
​JAVA从入门到精通-八大排序总结
经验技巧 0% 的用户喜欢
JAVA从入门到精通-变量和数据类型
经验技巧 0% 的用户喜欢
JAVA语言-异常
经验技巧 0% 的用户喜欢
JAVA从入门到精通-内部类、自动拆装箱、枚举
经验技巧 0% 的用户喜欢
JAVA从入门到精通-泛型
经验技巧 0% 的用户喜欢
其他海同名师 更多>>
刘新华
刘新华 联系TA
实力型。激情饱满,对专业充满热情
吴翠红
吴翠红 联系TA
独创“教、学、练、测”循环教学模式
吕益平
吕益平 联系TA
熟悉企业软件开发的产品设计及开发
程钢
程钢 联系TA
擅长大型企业商业网站开发和管理
孔庆琦
孔庆琦 联系TA
对MVC模式和三层架构有深入的研究
经验技巧30天热搜词 更多>>

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:13167058313
小职老师的微信号:13167058313

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    ICP许可  沪B2-20190160

站长统计