Java多线程基础知识总结

2016-07-18 15:40:51编程

Java 多线程基础缓存

1. 线程和进程

1.1 进程的概念网络

进程是表示资源分配的基本单位,又是调度运行的基本单位。例如,用户运行本身的程序,系统就建立一个进程,并为它分配资 源,包括各类表格、内存空间、磁盘空间、 I / O 设备等。而后,把该进程放人进程的就绪队列。进程调度程序选中它,为它分配 CPU 以及其它有关资源,该进程 才真正运行。因此,进程是系统中的并发执行的单位。多线程

 

1.2 线程的概念并发

线程是进程中执行运算的最小单位,亦即执行处理机调度的基本单位。若是把进程理解为在逻辑上操做系统所完成的任务,那么线程表示完成该任务的许多可能的子任务之一。函数

 

1.3 引入多线程的好处高并发

( 1 )易于调度 测试

( 2 )提升并发性。经过线程可方便有效地实现并发性。进程可建立多个线程来执行同一程序的不一样部分。spa

( 3 )开销少。建立线程比建立进程要快,所需开销不多。操作系统

( 4 )利于充分发挥多处理器的功能。经过建立多线程进程(即一个进程可具备两个或更多个线程),每一个线程在一个处理器上运行,从而实现应用程序的并发性,使每一个处理器都获得充分运行。

 

1.4 进程和线程的关系

( 1 )一个线程只能属于一个进程,而一个进程能够有多个线程,但至少有一个线程。

( 2 )资源分配给进程,同一进程的全部线程共享该进程的全部资源。

( 3 )处理机分给线程,即真正在处理机上运行的是线程。

( 4 )线程在执行过程当中,须要协做同步。不一样进程的线程间要利用消息通讯的办法实现同步。

线程是指进程内的一个执行单元 , 也是进程内的可调度实体 .

1.5 线程和 进程的区别

(1) 调度:线程做为调度和分配的基本单位,进程做为拥有资源的基本单位

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

(3) 拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但能够访问隶属于进程的资源 .

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

(5) 内存访问:进程之间的内存是独立的,而一个进程内的内存是多个线程共享的,这就是 Java 的并发理论都是基于解决这个问题出现的。

 

2. Java 内存模型

2.1 Java 内存模型的基本原理

Java 内存模型,因为 Java 被设计为跨平台的语言,在内存管理上,显然也要有一个统一的 模型。系统存在一个主内存 (Main Memory) , Java 中全部变量都储存在主存中,对于全部线程都是共享的。每条线程都有本身的工做内存 (Working Memory) ,工做内存中保存的是主存中某些变量的拷贝,线程对全部变量的操做都是在工做内存中进行,线程之间没法相互直接访问,变量传递均须要经过主存完成。

 

由于当线程处于不一样的cpu中时,它各自的变量保存在各自cpu的寄存器或者高速缓存中,这样回事的变量对于其它线程暂时不可见。

 

 

2.2 Volatile 的内存工做原理

Volatile 是保证多个线程之间变量可见性的,也就是说一个线程对变量进行了写操做,另一个线程可以获取它最新的值。

它的工做原理是,它对写和读都是直接操做工做主存的。(这个能够经过操做字节码看到)

2.3 Synchronized 的内存操做模型 :

Synchronized, 有两层语义,一个是互斥,一个是可见性。在可见性上面,它的工做原理有点不一样:当线程进入同步块时,会从主存里面获取最新值,而后当线程离开同步块时,再把变量的值更新到主存。

2. 线程的同步       

因为同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题。 Java 语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问。

咱们只需针对方法提出一套机制,这套机制就是 synchronized 关键字,它包括两种用法: synchronized 方法和synchronized 块。

1. synchronized 方法:经过在方法声明中加入 synchronized 关键字来声明 synchronized 方法。 synchronized方法控制对类成员变量的访问:每一个类实例对应一把锁,每一个 synchronized 方法都必须得到调用该方法的类实例的锁方能执行,不然所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能得到 该锁,从新进入可执行状态。这种机制确保了同一时刻对于每个类实例,其全部声明为synchronized 的成员函数中至多只有一个处于可执行状态(由于至多只有一个可以得到该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要全部可能访问类成员变 量的方法均被声明为 synchronized )。

在 Java 中,不光是类实例,每个类也对应一把锁,这样咱们也可将类的静态成员函数声明为 synchronized ,以控制其对类的静态成员变量的访问。

synchronized 方法的缺陷:若将一个大的方法声明为 synchronized 将会大大影响效率,典型地,若将线程类的方法 run() 声明为 synchronized ,因为在线程的整个生命期内它一直在运行,所以将致使它对本类任何synchronized 方法的调用都永远不会成功。

 

2. synchronized 块:经过 synchronized 关键字来声明 synchronized 块。语法以下:

synchronized(syncObject) {

// 容许访问控制的代码

}

synchronized 块是这样一个代码块,其中的代码必须得到对象 syncObject 的锁方能执行,具体机制同前所述。因为能够针对任意代码块,且可任意指定上锁的对象,故灵活性较高。

 

4. 线程的阻塞

4.1 线程阻塞基本概念

为了解决对共享存储区的访问冲突, Java 引入了同步机制,如今让咱们来考察多个线程对共享资源的访问,显然同步机制已经不够了,由于在任意时刻所要求的资源不必定已经准备好了被访问,反过来,同 一时刻准备好了的资源也可能不止一个。为了解决这种状况下的访问控制问题, Java 引入了对阻塞机制的支持。

阻塞指的是暂停一个线程的执行以等待某个条件发生(如某资源就绪)。

进入阻塞状态可能因为下列缘由:

1.       调用了 sleep 方法使线程进入休眠状态

2.       调用了 wait 方法,使得线程挂起,直到线程调用 notify 方法

3.       用户等待输入

4.       网络 IO

 

4.2 Thread.sleep()

Java 提供了大量方法来支持阻塞,下面让对它们逐一分析。

sleep() 方法: sleep() 容许指定以毫秒为单位的一段时间做为参数,它使得线程在指定的时间内进入阻塞状态,不能获得 CPU 时间,指定的时间一过,线程从新进入可执行状态。

典型地, sleep() 被用在等待某个资源就绪的情形:测试发现条件不知足后,让线程阻塞一段时间后从新测试,直到条件知足为止。

4.2 Thread.yield()

yield() 方法: yield() 使得线程放弃当前分得的 CPU 时间,可是不使线程阻塞,即线程仍处于可执行状态,随时可能再次分得 CPU 时间。调用 yield() 的效果等价于调度程序认为该线程已执行了足够的时间从而转到另外一个线程。

4.3 wait() and notify()

wait() 和 notify() 方法:两个方法配套使用, wait() 使得线程进入阻塞状态,它有两种形式,一种容许指定以毫秒为单位的一段时间做为参数,另外一种没有参数,前者当对应的 notify() 被调用或者超出指定时间时线程从新进入可执行状态,后者则必须对应的 notify() 被调用。

   2 和 4 区别的核心在于,前面叙述的全部方法,阻塞时都不会释放占用的锁(若是占用了的话),而这一对方法则相反。上述的核心区别致使了一系列的细节上的区别。

  首先,前面叙述的全部方法都隶属于 Thread 类,可是这一对却直接隶属于 Object 类,也就是说,全部对象都拥有这一对方法。由于这一对方法阻塞时要释放占用的锁,而锁是任何对象都具备的,调用任意对象的 wait()方法致使线程阻塞,而且该对象上的锁被释放。而调用任意对象的 notify() 方法则致使因调用该对象的 wait() 方法而阻塞的线程中随机选择的一个解除阻塞(但要等到得到锁后才真正可执行)。

  其次,前面叙述的全部方法均可在任何位置调用,可是这一对方法却必须在 synchronized 方法或块中调用,理由也很简单,只有在 synchronized 方法或块中当前线程才占有锁,才有锁能够释放。一样的道理,调用这一对方法的对象上的锁必须为当前线程所拥有,这样才有锁能够释放。所以,这一对方法调用 必须放置在这样的synchronized 方法或块中,该方法或块的上锁对象就是调用这一对方法的对象。若不知足这一条件,则程序虽然仍能编译,但在运行时会出现 IllegalMonitorStateException 异常。

   wait() 和 notify() 方法的上述特性决定了它们常常和 synchronized 方法或块一块儿使用,将它们和操做系统的进程间通讯机制做一个比较就会发现它们的类似性: synchronized 方法或块提供了相似于操做系统原语的功能,它们的结合用于解决各类复杂的线程间通讯问题。

关于 wait() 和 notify() 方法最后再说明两点:

  第一:调用 notify() 方法致使解除阻塞的线程是从因调用该对象的 wait() 方法而阻塞的线程中随机选取的,咱们没法预料哪个线程将会被选择,因此编程时要特别当心,避免因这种不肯定性而产生问题。

  第二:除了 notify() ,还有一个方法 notifyAll() 也可起到相似做用,惟一的区别在于,调用 notifyAll() 方法将把因调用该对象的 wait() 方法而阻塞的全部线程一次性所有解除阻塞。固然,只有得到锁的那一个线程才能进入可执行状态。

  谈到阻塞,就不能不谈一谈死锁,略一分析就能发现, suspend() 方法和不指定超时期限的 wait() 方法的调用均可能产生死锁。遗憾的是, Java 并不在语言级别上支持死锁的避免,咱们在编程中必须当心地避免死锁。

4.4 join ()

阻塞当前线程,直到指定线程执行完毕,再继续执行

5. 线程的生命周期

 

6 . 守护 线程

守护线程是一类特殊的线程,它和普通线程的区别在于它并非应用程序的核心部分,当一个 应用程序的全部非守护线程终止运行时,即便仍然有守护线程在运行,应用程序也将终止,反之,只要有一个非守护线程在运行,应用程序就不会终止。守护线程一 般被用于在后台为其它线程提供服务。调用方法 isDaemon() 来判断一个线程是不是守护线程,也能够调用方法 setDaemon() 将一个线程设为守护线程。

相关文章
相关标签/搜索