为了账号安全,请及时绑定邮箱和手机立即绑定

JAVA互联网架构——JAVA多线程编程

标签:
Java

进程: 一个正在执行的程序.每个进程执行都有一个执行顺序,该顺序是一个执行路径,或叫一个控制单元. 一个进程至少有一个线程.

线程:就是进程中的一个独立的控制单元. 线程控制这进程的执行.

多进程的缺点:进程切换开销大;进程间的通信很不方便。

多线程: 指的是在单个程序中可以同时运行多个不同的线程,执行不同的任务,线程切换的开销小 。

线程的生命周期

Java 做了很多工作,力求把这些细节抽象化。Java 提供了一个名为 Thread.State 的枚举类型,囊括了操作系统看到的线程状态。 Thread.State 中的值概述了一个线程的生命周期。

webp

图一

NEW

已经创建线程,但还没在线程对象上调用 start() 方法。所有线程一开始都处于这个状态。

RUNNABLE

线程正在运行,或者当操作系统调度线程时可以运行。

Java实现内存管理和并发编程的方式。

BLOCKED阻塞状态

线程中止运行,因为它在等待获得一个锁,以便进入声明为 synchronized 的方法或代码块。

具备运行资格, 没有执行权。

WAITING

线程中止运行,因为它调用了 Object.wait() 或 Thread.join() 方法。

在sleep和wait时, 既没有运行资格,有没有执行权。

TIMED_WAITING

线程中止运行,因为它调用了 Thread.sleep() 方法,或者调用了 Object.wait() 或Thread.join() 方法,而且传入了超时时间。

TERMINATED

线程执行完毕。线程对象的 run() 方法正常退出,或者抛出了异常。

可见性和可变性

在 Java 中,其实一个进程中的每个 Java 应用线程都有自己的栈(和局部变量),不过这些线程共用同一个堆,因此可以轻易在线程之间共享对象,毕竟需要做的只是把引用从一个线程传到另一个线程。

webp

我刚整理了一套2018最新的0基础入门和进阶教程,无私分享,加Java学习q-u-n :六七八,二四一,五六三 即可获取,内附:开发工具和安装包,以及系统学习路线图

图一

由此引出 Java 的一个一般设计原则——对象默认可见。如果我有一个对象的引用,就可以复制一个副本,然后将其交给另一个线程,不受任何限制。Java 中的引用其实就是类型指针,指向内存中的一个位置,而且所有线程都共用同一个地址空间,所以默认可见符合自然规律。

除了默认可见之外,Java 还有一个特性对理解并发很重要——对象是可变的(mutable),对象的内容(实例字段的值)一般都可以修改。使用 final 关键字可以把变量或引用声明为常量,但这种字段不属于对象的内容。

这两个特性(跨线程可见性和对象可变性)结合在一起,大大增加了理解 Java 并发编程的难度。

并发编程的安全性

如果我们想编写正确的多线程代码,得让程序满足一个重要的条件,

即:在一个程序中,不管调用什么方法,也不管操作系统如何调度应用线程,一个对象看到的任何其他对象都不处于非法或不一致的状态,这样的程序才称得上是 安全的多线程程序 。

互斥(mutual exclusion)和状态保护

只要修改或读取对象的过程中,对象的状态可能不一致,这段代码就要受到保护。为了保护这种代码,Java 平台只提供了一种机制:互斥

Java 为开发者提供了 synchronized 关键字。这个关键字可以用在代码块或方法上,使用时,Java 平台会限制访问代码块或方法中的代码。

因为 synchronized 关键字把代码包围起来,所以很多开发者认为,Java 的

并发和代码有关。有些资料甚至把 synchronized 修饰的块或方法中的代码

称为 临界区 ,还认为临界区是并发的关键所在。其实不然,稍后会看到,其

实我们要防范的是数据的不一致性

Java 平台会为它创建的每个对象记录一个特殊的标记,这个标记叫监视器(monitor)。synchronized 使用这些监视器(或叫锁)指明,随后的代码可以临时把对象渲染成不一致的状态。 synchronized 修饰的代码块或方法会发生一系列事件,详述如下:

线程需要修改对象时,会临时把对象变成不一致状态;

线程获取监视器,指明它需要临时互斥存储这个对象;

线程修改对象,修改完毕后对象处于一致的合法状态;

线程释放监视器。

同步是保护状态的一种协助机制,因此非常脆弱。一个缺陷(需要使用

synchronized 修饰的方法却没有使用)就可能为系统的整体安全性带来灾难

性的后果。

之所以使用 synchronized 这个词作为“需要临时互斥存储”的关键词,除了说明需要获取监视器之外,还表明进入代码块时,JVM 会从主内存中重新读取对象的当前状态。类似地,退出 synchronized 修饰的代码块或方法时,JVM 会刷新所有修改过的对象,把新状态存入主内存。

volatile关键字

Java 还提供了另一个关键字,用来并发访问数据—— volatile 。这个关键字指明,应用代码使用字段或变量前,必须重新从主内存读取值。同样,修改使用 volatile 修饰的值后,在写入变量之后,必须存回主内存。

volatile 关键字的主要用途之一是在“关闭前一直运行”模式中使用。编写多线程程序时,如果外部用户或系统需要向处理中的线程发出信号,告诉线程在完成当前作业后优雅关闭线程,那么就要使用 volatile 。这个过程有时叫作“优雅结束”模式。

Thread 类中有用的方法

setName()和 getName()

开发者使用这两个方法设定或取回单个线程的名称。为线程起名字是个好习惯,因为这样调试时更方便,尤其是使用 jvisualvm 等工具。

isAlive()

用来测试线程是否还“活着”。

start()

这个方法用来创建一个新应用线程,然后再调用 run() 方法调度这个线程,开始执行。正常情况下,执行到 run() 方法的末尾或者执行 run() 方法中的一个 return 语句后,线程就会结束运行。

interrupt()

中断线程. 如果调用 sleep() 、 wait() 或 join() 方法时阻塞了某个线程,那么在表示这个线程的Thread 对象上调用 interrupt() 方法,会让这个线程抛出InterruptedException 异常(并把线程唤醒)。如果线程中涉及可中断的 I/O 操作,那么这个 I/O 操作会终止,而且线程会收到 ClosedByInterruptException 异常。即便线程没有从事任何可中断的操作,线程的中断状态也会被设为 true。

join()

在调用 join() 方法的 Thread 对象“死亡”之前,当前线程一直处于等待状态。可以把这个方法理解为一个指令,在其他线程结束之前,当前线程不会继续向前运行。貌似只在start()后才生效.

setDaemon()

用户线程是这样一种线程,只要它还“活着”,进程就无法退出——这是线程的默认行为。有时,程序员希望线程不阻止进程退出——这种线程叫守护线程(可以理解为后台线程)。一个线程是守护线程还是用户线程,由 setDaemon() 方法控制。这个方法必须在invoked before the thread is started.

setUncaughtExceptionHandler()

线程因抛出异常而退出时,默认的行为是打印线程的名称、异常的类型、异常消息和堆栈跟踪。如果这么做还不够,可以在线程中安装一个自定义的处理程序,处理未捕获的异常。

yield() 暂停当前正在执行的线程对象,并执行其他线程。

interrupt用法

如果一个线程处于了阻塞状态(如线程调用了thread.sleep、thread.join、thread.wait、1.5中的condition.await、以及可中断的通道上的 I/O 操作方法后可进入阻塞状态),则在线程在检查中断标示时如果发现中断标示为true,则会在这些阻塞方法(sleep、join、wait、1.5中的condition.await及可中断的通道上的 I/O 操作方法)调用处抛出InterruptedException异常,并且在抛出异常后立即将线程的中断标示位清除,即重新设置为false。抛出异常是为了线程从阻塞状态醒过来,并在结束线程前让程序员有足够的时间来处理中断请求

webp

图三

Thread 类弃用的方法

Thread 类除了有一些有用的方法之外,还有一些危险的方法,开发者不应该使用。这些方法是 Java 线程 API 原来提供的,但很快就发现不适合开发者使用。可惜的是,因为 Java要向后兼容,所以不能把这些方法从 API 中移除。

stop()

如若不违背并发安全的要求,几乎不可能正确使用 Thread.stop() ,因为 stop() 方法会立即“杀死”线程,不会给线程任何机会把对象恢复成合法状态。这和并发安全等原则完全相悖,因此绝对不能使用 stop() 方法。

suspend() 、 resume() 和 countStackFrames()

调用 suspend() 方法挂起线程时,不会释放这个线程拥有的任何一个监视器,因此,如果其他线程试图访问这些监视器,这些监视器会变成死锁。其实,这种机制会导致死锁之间的条件竞争,而且 resume() 会导致这几个方法不能使用。

destroy()

这个方法一直没有实现,如果实现了,会遇到与 suspend() 方法一样的条件竞争。开发者始终应该避免使用这些弃用的方法。为了达到上述方法的预期作用,Java 开发了一些安全的替代模式。前面提到的“关闭前一直运行”模式就是这些模式的一例。



作者:糖宝_d864
链接:https://www.jianshu.com/p/5daaaa2e7966


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消