Java多线程(一)多线程入门篇

1 说到线程,首先来讲下进程,如下是进程的定义:

进程是操做系统结构的基础,是程序的一次执行,是一个程序及其数据结构在处理机上顺序执行时所发生的活动,是程序在一个数据集合上运行的过程,它是系统进行资源分配和调度的一个独立单位。算法

简单来讲一个任务管理器中列表的一个exe文件就能够理解成进程,如QQ.exe就是一个进程,进程是受系统管理的基本运行单元。编程

1.1 什么是线程?

线程是操做系统可以进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运做单位。
安全

简单来讲,线程能够理解成为在进程中独立运行的子任务。好比,QQ.exe运行中就有不少的子任务在同时运行。网络

1.2 进程和线程的区别

1.2.1 调度:线程做为调度和分配的基本单位,进程做为拥有资源的基本单位 。数据结构

1.2.2 并发性:不只进程之间能够并发执行,同一个进程的多个线程之间也可并发执行。多线程

1.2.3 拥有资源:进程是拥有资源的一个独立单位,线程本身基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器、一组寄存器和栈),可是它能够与同属一个进程的其余线程共享进程所拥有的所有资源。进程之间是不能共享地址空间的, 而线程是共享着所在进程的地址空间的。并发

1.2.4 系统开销:在建立或撤消进程时,因为系统都要为之分配和回收资源,致使系统的开销明显大于建立或撤消线程时的开销。框架

1.3 什么是多线程?

多线程就是几乎同时执行多个线程。大数据

1.4 为何要使用多线程

1.4.1 使用线程能够把占据时间长的程序中的任务放到后台去处理。操作系统

1.4.2 用户界面更加吸引人,这样好比用户点击了一个按钮去触发某件事件的处理,能够弹出一个进度条来显示处理的进度。

1.4.3 程序的运行效率可能会提升。

1.4.4 在一些等待的任务实现上如用户输入,文件读取和网络收发数据等,线程就比较有用了。

2 线程的状态

通常来讲,线程包括如下这几个状态:建立(new)、就绪(runnable)、运行(running)、阻塞(blocked)、timed_waiting、waiting、消亡(dead)。



3 多线程的使用方式

3.1 继承 Thread类



能够看到程序在交替执行,可是最终都会执行到98。

3.2 实现Runnable接口




依旧能够看到程序在交替执行,可是最终都会执行到98。

3.3 继承实现Callable接口



这里须要说明的是实现Callable接口,必须重写call()方法,而且要用到FutureTask类,这里先不作介绍,等后面更新线程池再细讲。

3.4 使用线程池例如用Executor框架

这部分暂不介绍。后面更新线程池和Executor会详细介绍。

4 使用多线程必定快吗?

答:不必定,由于多线程会进行上下文切换,上下文切换会带来开销。

4.1 什么是上下文切换?

对于单核 CPU,CPU 在一个时刻只能运行一个线程,当在运行一个线程的过程当中转去运行另一个线程,这个叫作线程上下文切换(对于进程也是相似)。线程上下文切换过程当中会记录 程序计数器、CPU 寄存器 的 状态等数据。

4.2 如何减小上下文切换?

4.2.1 减小线程的数量

因为一个CPU每一个时刻只能执行一条线程,而傲娇的咱们又想让程序并发执行,操做系统只好不断地进行上下文切换来使咱们从感官上以为程序是并发执的行。所以,咱们只要减小线程的数量,就能减小上下文切换的次数。

然而若是线程数量已经少于CPU核数,每一个CPU执行一条线程,照理来讲CPU不须要进行上下文切换了,但事实并不是如此。

4.2.2 控制同一把锁上的线程数量

若是多条线程共用同一把锁,那么当一条线程得到锁后,其余线程就会被阻塞;当该线程释放锁后,操做系统会从被阻塞的线程中选一条执行,从而又会出现上下文切换。

所以,减小同一把锁上的线程数量也能减小上下文切换的次数。

4.2.3 采用无锁并发编程

须要并发执行的任务是无状态的:HASH分段

所谓无状态是指并发执行的任务没有共享变量,他们都独立执行。对于这种类型的任务能够按照ID进行HASH分段,每段用一条线程去执行。

须要并发执行的任务是有状态的:CAS算法

若是任务须要修改共享变量,那么必需要控制线程的执行顺序,不然会出现安全性问题。你能够给任务加锁,保证任务的原子性与可见性,但这会引发阻塞,从而发生上下文切换;为了不上下文切换,你可使用CAS算法, 仅在线程内部须要更新共享变量时使用CAS算法来更新,这种方式不会阻塞线程,并保证更新过程的安全性。


5 使用多线程的缺点:

5.1 上下文切换的开销

当 CPU 从执行一个线程切换到执行另一个线程的时候,它须要先存储当前线程的本地的数据,程序指针等,而后载入另外一个线程的本地数据,程序指针等,最后才开始执行。这种切换称为“上下文切换”。CPU 会在一个上下文中执行一个线程,而后切换到另一个上下文中执行另一个线程。上下文切换并不廉价。若是没有必要,应该减小上下文切换的发生。

5.2 增长资源消耗

线程在运行的时候须要从计算机里面获得一些资源。 除了 CPU,线程还须要一些 内存来维持它本地的堆栈。它也须要 占用操做系统中一些资源来管理线程。

5.3 编程更复杂

在多线程访问共享数据的时候,要考虑 线程安全问题 。

6 线程安全

6.1 线程安全定义

一个类在能够被多个线程安全调用时就是线程安全的。

6.2 线程安全分类

线程安全不是一个非真即假的命题,能够将共享数据按照安全程度的强弱顺序分红如下五类:

不可变、绝对线程安全、相对线程安全、线程兼容和线程对立。

6.2.1. 不可变

不可变(Immutable)的对象必定是线程安全的,不管是对象的方法实现仍是方法的调用者,都不须要再采起任何的线程安全保障措施,只要一个不可变的对象被正确地构建出来,那其外部的可见状态永远也不会改变,永远也不会看到它在多个线程之中处于不一致的状态。

不可变的类型:final 关键字修饰的基本数据类型;String ;枚举类型Number 部分子类,如 Long 和 Double 等数值包装类型,BigInteger 和 BigDecimal 等大数据类型。但同为 Number 的子类型的原子类 AtomicInteger 和 AtomicLong 则并不是不可变的。

6.2.2 绝对线程安全

无论运行时环境如何,调用者都不须要任何额外的同步措施。

6.2.3 相对线程安全

相对的线程安全须要保证对这个对象单独的操做是线程安全的,在调用的时候不须要作额外的保障措施,可是对于一些特定顺序的连续调用,就可能须要在调用端使用额外的同步手段来保证调用的正确性。

6.2.4 线程兼容

线程兼容是指对象自己并非线程安全的,可是能够经过在调用端正确地使用同步手段来保证对象在并发环境中能够安全地使用,咱们日常说一个类不是线程安全的,绝大多数时候指的是这一种状况。Java API 中大部分的类都是属于线程兼容的,如与前面的 Vector 和 HashTable相对应的集合类 ArrayList 和 HashMap 等。

6.2.5 线程对立

线程对立是指不管调用端是否采起了同步措施,都没法在多线程环境中并发使用的代码。因为Java 语言天生就具有多线程特性,线程对立这种排斥多线程的代码是不多出现的,并且一般都是有害的,应当尽可能避免。

相关文章
相关标签/搜索