Java并发编程基础
4.1 线程简介
4.1.1 什么是线程?
现代操作系统调度的最小单元是线程,也叫轻量级进程(Light
Weight Process),在一个进程里可以创建多个线程,这些线程都拥有各自的计数器、堆栈和局
部变量等属性,并且能够访问共享的内存变量。处理器在这些线程上高速切换,让使用者感觉
到这些线程在同时执行。
4.1.2 为什么要使用多线程?
(1)更多的处理器核心
(2)更快的响应速度
(3)更好的编程模型(JUC)
4.1.3 线程优先级
时分的形式调度运行的线程,线程会分配到若干时间片,当线程的时间片用完了就会发生线程调度,并等待着下次分配,而线程优先级就是决定线程需要多或者少分配一些处理器资源的线程属性。
变量priority来控制优先级,范围1-10,默认为5 注意(程序正确性不能依赖线程的优先级高低。因为优先级只是决定占用时间的多少,并不决定谁先谁后)。
4.1.4 线程的状态
6种状态,一个线程在当下只有一种状态,(一个线程只能启动一次,第二次会抛出IllegalThreadStateException,这是一种运行时异常)
注意 Java将操作系统中的运行和就绪两个状态合并称为运行状态。阻塞状态是线程
阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态,但是阻塞在
java.concurrent包中Lock接口的线程状态却是等待状态,因为java.concurrent包中Lock接口对于
阻塞的实现均使用了LockSupport类中的相关方法。
4.1.5 Daemon线程
Daemon线程是一种支持型线程,因为它主要被用作程序中后台调度以及支持性工作。这
意味着,当一个Java虚拟机中不存在非Daemon线程的时候,Java虚拟机将会退出。可以通过调
用Thread.setDaemon(true)将线程设置为Daemon线程。
注意 Daemon属性需要在启动线程之前设置,不能在启动线程之后设置。
Daemon线程被用作完成支持性工作,但是在Java虚拟机退出时Daemon线程中的finally块
并不一定会执行
4.2 启动和终止线程
start()启动,run()运行完时结束
4.3 线程间通信
4.3.1 volatile和synchronized关键字
关键字volatile可以用来修饰字段(成员变量),就是告知程序任何对该变量的访问均需要
从共享内存中获取,而对它的改变必须同步刷新回共享内存,它能保证所有线程对变量访问
的可见性。
关键字synchronized可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线程
在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性
和排他性。
实现synchronized关键字的方法有两种
public class Synchronized {
public static void main(String[] args) {
// 对Synchronized Class对象进行加锁
synchronized } | (Synchronized.class) { |
// 静态同步方法,对Synchronized Class对象进行加锁
m();
}
public static synchronized void m() {
}
}
在Synchronized.class同级目录执行javap–v Synchronized.class,部分相关输出如下所示:
public static void main(java.lang.String[]);
// 方法修饰符,表示:public staticflags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: ldc 2: dup | #1 | // class com/murdock/books/multithread/book/Synchronized |
3: monitorenter 4: monitorexit 5: invokestatic | // monitorenter:监视器进入,获取锁 // monitorexit:监视器退出,释放锁 #16 // Method m:()V |
8: return
public static synchronized void m();
// 方法修饰符,表示: public static synchronized
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
Code:
stack=0, locals=0, args_size=0
0: return
上面class信息中,对于同步块的实现使用了monitorenter和monitorexit指令,而同步方法则是依靠方法修饰符上的ACC_SYNCHRONIZED来完成的。无论采用哪种方式,其本质是对一个对象的监视器(monitor)进行获取,而这个获取过程是排他的,也就是同一时刻只能有一个线程获取到由synchronized所保护对象的监视器。
任意一个对象都拥有自己的监视器,当这个对象由同步块或者这个对象的同步
4.3.2 等待/通知机制
4.3.3 等待/通知的经典范式
等待/通知的经典范式,该范式分为两部分,分别针对等待方(消费者)和通知方(生产者)。
等待方遵循如下原则。
1)获取对象的锁。
2)如果条件不满足,那么调用对象的wait()方法,被通知后仍要检查条件。
3)条件满足则执行对应的逻辑。
对应的伪代码如下。
synchronized(对象) {
while(条件不满足) {
对象.wait();
} 对
应的处理逻辑
}
通知方遵循如下原则。
1)获得对象的锁。
2)改变条件。
3)通知所有等待在对象上的线程。
对应的伪代码如下。
synchronized(对象) {
改变条件
对象.notifyAll();
}
4.3.5 Thread.join()的使用
如果一个线程A执行了thread.join()语句,其含义是:当前线程A等待thread线程终止之后才从thread.join()返回。线程Thread除了提供join()方法外,还提供了join(long millis)和join(longmillis,int nanos)
4.3.6 ThreadLocal的使用
参照另一篇博客
参考资料 并发编程的艺术