java并发学习笔记

来源:互联网 发布:unity编程语言 编辑:程序博客网 时间:2024/05/11 19:28

转载请注明出处:http://blog.csdn.net/tyhj_sf/article/details/72852546

1 Java内存模型

1.1
Java内存模型规定了所有的变量都存储在主内存(Main Memory)中,不包括线程内定义的变量,因为线程内定义的变量属于线程私有不在Java并发讨论范围。
1.2
每条线程还有自己的工作内存(Working Memory),线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量。
这里写图片描述

1.3 Java线程的实现原理
主流的操作系统都提供了线程实现,Java语言则提供了在不同硬件和操作系统平台下对
线程操作的统一处理,每个已经执行start()且还未结束的java.lang.Thread类的实例就代表了一个线程。我们注意到Thread类与大部分的Java API有显著的差别,它的所有关键方法都是声明为Native的。在Java API中,一个Native方法往往意味着这个方法没有使用或无法使用平台无关的手段来实现(当然也可能是为了执行效率而使用Native方法,不过,通常最高效率的手段也就是平台相关的手段)。
对于Sun JDK来说,它的Windows版与Linux版都是使用一对一的线程模型实现的,一条Java线程就映射到一条轻量级进程之中,因为Windows和Linux系统提供的线程模型就是一对一的。如下图所示:

这里写图片描述
Java使用的线程调度方式就是抢占式调度,虽然Java线程调度是系统自动完成的,但是我们还是可以通过设置线程优先级来“建议”系统给某些线程多分配一点执行时间,另外的一些线程则可以少分配一点。

2 Java线程的创建与使用

创建Java线程有三种方式:
1、Runnable类
class WorkerThread implements Runnable (
public void run () {
……
}
在创建这个线程类之后,需要创建Thread类的实例, 然后将WorkerThread类的对象作为
参数传递给Thread类的构造函数,就像下边这样:
Thread t = new Thread (new WorkerThread());
2、Thread类
通过子类化Thread类来创建线程:
class WorkerThread extends Thread (
public void run() {
……
}
}
然后调用线程类的start()方法启动线程。

3、ThreadGroup类
ThreadGroup类允许将所有逻辑上相关的线程归到组中,这样就能同时改变属于某个组的所有线程。此外,线程组也可以包含其他线程组。线程组构成一棵树,在树中,除了初始线程组外,每个线程组都有一个父线程组。线程被允许访问有关自己的线程组的信息,但是不允许它访问有关其线程组的父线程组或其他任何线程组的信息。
用法如:
ThreadGroup g=new ThreadGroup(“g1”);
Thread t=new Thread(g,new TestThread(1000,”AAA”));
其中的g即线程所属的线程组

经验:
实现Runnable接口被认为是一种面向对象的方法,这种方法被椎荐使用,优于通过子类化Thread类的方法。
J2SE 5.0以后为线程集合引入了更好的操作特性,使得ThreadGroup的使用有些多余。

3 Java的线程间通信

3.1 wait/notify 机制

等待/通知机制 主要由 Object类 中的三个方法保证:wait()、notify() 和 notifyAll().
这三个方法均非Thread类中所声明的方法,而是Object类中声明的方法。原因是锁可以是任意对象,而任意对象都是Object类的实例,所以自然应该是Object类的方法。
wait()
让当前线程 (Thread.concurrentThread() 方法所返回的线程) 释放对象锁并进入等待(阻塞)状态。线程执行wait()后,就放弃了运行资格,处于冻结状态;线程运行时,内存中会建立一个线程池,冻结状态的线程都存在于线程池中。
notify()
唤醒一个正在等待相应对象锁的线程,使其进入就绪队列,以便在当前线程释放锁后竞争锁,进而得到CPU的执行。notify()执行时唤醒的也是线程池中的线程,线程池中有多个线程时唤醒第一个被冻结的线程。在执行 notify() 方法后,当前线程不会马上释放该锁对象,呈 wait 状态的线程也并不能马上获取该对象锁。只有等到执行notify()方法的线程退出synchronized代码块/方法后,当前线程才会释放锁,而相应的呈wait状态的线程才可以去争取该对象锁。
notifyAll()
唤醒所有正在等待相应对象锁的线程,使它们进入就绪队列,以便在当前线程释放锁后竞争锁,进而得到CPU的执行。
值得注意:
1) wait()、notify() 和 notifyAll()方法是本地方法,并且为final方法,无法被重写;
2) 调用某个对象的 wait() 方法能让当前线程阻塞,并且当前线程必须拥有此对象的monitor(即锁);
3) 调用某个对象的 notify()方法能够唤醒一个正在等待这个对象的锁的线程,如果有多个线程(线程等待队列)都在等待这个对象的锁,则只能唤醒第一个开始等待的线程;
4) 调用notifyAll()方法能够唤醒所有正在等待这个对象的锁的线程。

3.2如何理解volatile的含义?

第一,是保证此变量对所有线程的可见性,这里的“可见性”是指当一条线程修改了这个变量的值,新值对于其他线程来说是可以立即得知的。而普通变量不能做到这一点,普通变量的值在线程间传递均需要通过主内存来完成。volatile变量在各个线程的工作内存中不存在一致性问题(在各个线程的工作内存中,volatile变量也可以存在不一致的情况,但由于每次使用之前都要先刷新,执行引擎看不到不一致的情况,因此可以认为不存在一致性问题),但是Java里面的运算并非原子操作,导致volatile变量的运算在并发下一样是不安全的。
第二,是禁止指令重排序优化,普通的变量仅仅会保证在该方法的执行过程中所有依赖赋值结果的地方都能获取到正确的结果,而不能保证变量赋值操作的顺序与程序代码中的执行顺序一致。

3.3 synchronized的含义

在Java中,最基本的互斥同步手段就是synchronized关键字,synchronized关键字经过编译
之后,会在同步块的前后分别形成monitorenter和monitorexit这两个字节码指令,这两个字节码都需要一个reference类型的参数来指明要锁定和解锁的对象。如果Java程序中的
synchronized明确指定了对象参数,那就是这个对象的reference;如果没有明确指定,那就根据synchronized修饰的是实例方法还是类方法,去取对应的对象实例或Class对象来作为锁对象。

3.4 重入锁ReentrantLock与synchronized的比较

1)代码写法上有点区别,前者表现为API层面的互斥锁(lock()和unlock()方法配合try/finally语句块来完成),后者表现为原生语法层面的互斥锁。
2)ReentrantLock增加了一些高级功能,主要有以下3项:等待可中断、可实现公平锁,以及锁可以绑定多个条件。
3)性能上,在jdk1.6之后两者性能相当。提倡在synchronized能实现需求的情况下,优先考虑使用语法层面上的synchronized来进行同步,仅在需要使用只有ReentrantLock才具有的高级功能时才考虑使用ReentrantLock。

参考资料

1.《深入理解Java虚拟机-jvm高级特性及最佳实现》第二版
2.《Java并发编程实战》
3. Java 并发:线程间通信与协作