线程

线程和进程

进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位,每个进程都有独立的代码和数据空间(进程上下文),进程切换的开销大。

线程:轻量的进程,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换的开销小。一个线程是一个程序内部的顺序控制流。

多进程:同时运行多个任务(程序)。
多线程:在同一应用程序中有多个顺序流同时执行。

线程的概念模型

●虚拟的CPU,封装在 java. lang. Thread类中。

●CPU所执行的代码,传递给 Thread类。

●CPU所处理的数据,传递给 Thread类。

●Java的线程是通过 java. lang. Thread类来实现的。、

●每个线程都是通过某个特定 Thread对象的方法run()来完成其操作的方法run()称为线程体。

构造线程的三种方法

定义一个线程类,它继承类 Thread并重写其中的方法run();
提供一个实现接口 Runnable的类作为线程的目标对象,在初始化;
通过Callable和Future创建线程。

一个 Thread类或者 Thread子类的线程对象时,把目标对象传递给这个线程实例,由该目标对象提供线程体run()。

main线程已经执行完后,新线程才执行完,main方法调用 thread. start()方法启动新线程后并不等待其run方法返回就继续运行,线程的run方法在一边独自运行,不影响原来的main方法的运行。

Runnable接口

只有一个run()方法,Thread类实现了 Runnable接囗,便于多个线程共享资源.

□Java不支持多继承,如果已经继承了某个基类,便需要

现 Runnable接口来生成多线程

口以实现 Runnable的对象为参数建立新的线程

口sta方法启动线程就会运行run(方法

多线程的同步控制

●有时线程之间彼此不独立、需要同步

口线程间的互斥

同时运行的几个线程需要共享一个(些)数据

共享的数据,在某一时刻只允许一个线程对其进行操作

“生产者/消费者”问题

假设有一个线程负责往数据区写数据,另一个线程从同一数据

区中读数据,两个线程可以并行执行

如果数据区已满,生产者要等消费者取走一些数据后才能再写

当数据区空时,消费者要等生产者写入一些数据后再取

●线程同步

口互斥:许多线程在同一个共享数据上操作而互不干扰,同一时刻

只能有一个线程访问该共享数据。因此有些方法或程序段在同

时刻只能被一个线程执行,称之为监视区

口协作:多个线程可以有条件地同时操作共享数据。执行监视区代

码的线程在条件满足的情况下可以允许其它线程进入监视区

synchronized-一线程同步关键字,实现互斥

口用于指定需要同步的代码段或方法,也就是监视区

口可实现与一个锁的交互。例如

synchronized(对象)(代码段

口 synchronized的功能是:首先判断对象的锁是否在,如果在就获得锁

然后就可以执行紧随其后的代码段;如果对象的锁不在(已被其他

线程拿走),就进入等待状态,直到获得锁

口当被 synchronized限定的代码段执行完,就释放锁

●后台线程

口也叫守护线程,通常是为了辅助其它线程而运行的线程

口它不妨碍程序终止

口一个进程中只要还有一个前台线程在运行,这个进程就不会结束;如

果一个进程中的所有前台线程都已经结束,那么无论是否还有未结束

的后台线程,这个进程都会结束

口“垃圾回收”便是一个后台线程

口如果对某个线程对象在启动(调用stat方法)之前调用了

setDaemon(true方法,这个线程就变成了后台线程

sleep是线程类(Thread)的方法,导致此线程暂停执行指定时间,给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁。 wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。

诞生状态

口线程刚刚被创建

就绪状态

口线程的 start方法已被执行

口线程已准备好运行

运行状态

口处理机分配给了线程,线程正在运行

阻塞状态( Blocked)

口在线程发出输入/输出请求且必须等待其返回

口遇到用 synchronized标记的方法而未获得锁

口为等候一个条件变量,线程调用wat(方法

●休眠状态( Sleeping)

口执行seep方法而进入休眠

死亡状态

口线程已完成或退出

线程调度

口在单CPU的系统中,多个线程需要共享CPU,在任何时间点

上实际只能有一个线程在运行

口控制多个线程在同一个CPU上以某种顺序运行称为线程调度

Java虚拟机支持一种非常简单的、确定的调度算法,叫做固

定优先级算法。这个算法基于线程的优先级对其进行调度

考虑这些线程在运行时环境下的调度和交替执行,也

不需要进行额外的同步,或者在调用方进行任何其他

的协调操作,调用这个对象的行为都可以获得正确的

结果,那这个对象是线程安全的。

Java线程安全 互斥同步、非阻塞同步、无同步方案

互斥同步

●同步的互斥实现方式:临界区( Critical Section),互斥量

( Mutex),信号量( Semaphore)

● Synchronized关键字:经过编译后,会在同步块前后形成

monitorenter和 monitorexit两个字节码。

口(1) synchronized同步块对自己是可重入的,不会将自己锁死;

口(2)同步块在已进入的线程执行完之前,会阻塞后面其他线程的进入

采用 synchronized,重入锁可实现:等待可中断、公平锁、锁

可以绑定多个条件

Synchronized表现为原生语法层面的互斥锁,而 RenentrantLock表

现为API层面的互斥锁

●阻塞同步:互斥同步存在的问题是进行线程阻塞和唤醒所带来的性

能问题,这种同步称为阻塞同步( Blocking Synchronization)。这是

种悲观并发策略

●非阻塞同步:不同于悲观并发策略,而是使用基于冲突检测的乐观

并发策略,就是先进行操作,如果没有其他线程征用共享数据,则

操作成功;否则就是产生了冲突,采取不断重试直到成功为止的策

种策略不需要把线程挂起,称为非阻塞同步

●使用硬件处理器指令进行不断重试策略(DK15以后

口测试并设置( Test-and-set)

口获取并增加( Fetch- and-Increment)

口交换(Swap)

口比较并交换( Compare-and-Swap,简称CAS)

口加载链接,条件存储( Load-Linked, Store-conditional简称LLSC)

例:java实现类 AtomicInteger, AtomicDouble等等。

●可重入代码:也叫纯代码。相对线程安全来说,可以保证线程安全。

可以在代码执行过程中断它,转而去执行另一段代码,而在控制权

返回后,原来的程序不会出现任何错误。

●线程本地存储:如果一段代码中所需要的数据必须与其他代码共享,

那就看看这些共享数据的代码是否能保证在同一个线程中执行,如

果能保证,就可以把共享数据的可见范围限定在同一个线程之内,

这样无需同步也能保证线程之间不出现数据争用问题。

锁优化(源自」DK6)

●自旋锁

●自适应锁

●锁消除

●锁粗化

偏向锁

●互斥同步存在的问题:挂起线程和恢复线程都需要转入内核态中完

成,这些操作给系统的并发性能带来很大的压力

●自旋锁:如果物理机器有一个以上的处理器能让两个或以上的线程

同时并行执行,那就可以让后面请求锁的那个线程“稍等一会”,

但不放弃处理器的执行时间,看看持有锁的线程是否很快就会释放

锁。为了让线程等待,我们只需要让线程执行一个忙循环(自旋)

这项技术就是自旋锁。Java中自旋次数默认10次

自适应自旋

●自适应意味着锁自旋的时间不再固定,而是由前一次在同一个锁

上的自旋时间及锁拥有者的状态来决定。如果在同一个锁对象上,

自旋等待刚刚成功获得锁,并且持有锁的线程正在运行中,那么

虚拟机就会认为这次自旋也很有可能再次成功,进而它允许自旋

等待相对更长的一段时间。

锁消除

●定义:JVM即时编译器在运行时,对一些代码上要求同步,但是

被检测到不可能存在共享数据竞争的锁进行消除

●判定依据:如果判断在一段代码中,堆上的所有数据都不会逃逸

出去从而被其他线程访问到,那就可以把它们当作栈上数据对待,

认为他们是线程私有的,同步加锁自然无需进行。

锁粗化

通常我们的代码总是将同步块的作用范围限制得尽量小,只在共享数

据的实际作用域中才进行同步,这样是为了使得同步操作的数量尽可

能变小

●另一种情况是,如果一系列的连续操作都对同一个对象反复加锁,甚至加

锁操作是出现在循环体中,那即使没有线程争用,频繁的进行互斥同步也

会导致不必要的性能损耗,此时只需要将同步块范围扩大即可。即:锁粗

偏向锁

●目的:消除数据无竟争情况下的同步原语,进一步提高程序运行的性

能。偏向锁就是在无竟争的情况下把整个同步都消除掉,连CAS操作

都不做

●偏向:意思是这个锁会偏向于第一个获得它的线程,如果在接下来的

执行中,该锁没有被其他线程获取,则持有偏向所得线程永远不需要

再进行同步

你可能感兴趣的:(线程)