Java多线程编程核心技术 笔记

Java多线程编程核心技术

第1章 Java多线程技能

使用多线程

继承Thread类

局限:不支持多继承

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MyThread extends Thread {
@Override
public void run() {
……
}
}
public class Main {
public static void main(String[] args) {
MyThread mythread = new MyThread();
mythread.start();
}
}

Thread.java类中的start()方法通知线程规划器此线程已经准备就绪,等待调用线程对象的run()方法,让系统安排时间调用Thread中的run()方法,使线程得到运行。启动线程,具有异步执行的效果。

如果调用代码thread.run()就不是异步执行,而是同步,由main主线程来调用run()方法,必须等run()方法中的代码执行完毕后才可以执行后面的代码。

实现Runnable接口

1
2
3
4
5
6
7
8
9
10
11
12
public class MyRunnable implements Runnable {
public void run() {
……
}
}
public class Main {
public static void main(String[] args) {
Runnable r = new MyRunnable();
Thread thread = new Thread(r);
thread.start();
}
}

守护线程

当进程中不存在非守护线程,则守护线程自动销毁。典型的守护进程就是垃圾回收进程。

第2章 对象及变量的并发访问

synchronized同步方法

方法内的变量为线程安全

方法前加关键字synchronized

多个对象多个锁

关键字synchronized取得的锁都是对象锁,哪个线程先执行带synchronized关键字的方法,哪个线程就持有该方法所属对象的锁Lock,其他线程只能呈现等待状态,前提是多个线程访问的是同一个对象。

锁重入

一个synchronized方法/块的内部调用本类的其他synchronized方法/块时,是永远可以得到锁的。

出现异常,锁自动释放

当一个县城执行的代码出现异常时,其所持有的锁会自动释放。

同步不具有继承性

还要在子类的方法中添加synchronized关键字

synchronized同步语句块

当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object对象中的非synchronized(this)同步代码块。

当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对于同一个object中所有其他synchronized(this)同步代码块的访问将堵塞。

synchronized关键字加到static静态方法上是给Class类上锁,而synchronized关键字加到非static静态方法上是给对象上锁

volatile关键字

通过使用volatile关键字,强制地从公共内存中读取变量的值。

使用volatile关键字增加了实例变量在多个线程之间的可见性,但最致命的缺点是不支持原子性

synchronized VS volatile

  • 关键字volatile是线程同步的轻量级实现,性能更好;

    volatile只能修饰与变量,synchronized可以修饰方法以及代码块。

    随着JDK新版本发布,synchronized关键字在执行效率上得到很大提升

  • 多线程访问volatile不会发生堵塞,synchronized会发生堵塞

  • volatile能保证数据的可见性,但不能保持原子性;

    synchronized可以保证原子性,间接保证可见性

  • 关键字volatile解决的变量在多个线程之间的可见性;

    关键字synchronized解决的是多个线程之间访问资源的同步性;

非原子性

如果修改实例变量中的数据,比如i++,并不是一个原子操作,也就是非线程安全的

  1. 从内存中取出i的值
  2. 计算i的值
  3. 将i的值写到内存中

使用原子类进行i++操作

AtomicInteger原子类进行实现

第3章 线程间通信

通知等待机制

关键字synchronized可以将任何一个Object对象作为同步对象来看待,而Java为每个Object都实现了wait()notify()方法,它们必须用在被synchronized同步的Object的临界区内。通过调用wait()方法可以使处于临界区内的线程进入等待状态,同时释放被同步对象的锁。而notify()操作可以唤醒一个因调用了wait()操作而处于堵塞状态中的线程,使其进入就绪状态。

调用方法notify()一次只随机通知一个线程进行唤醒;

为了唤醒全部线程,可以使用notifyAll()方法。

类ThreadLocal的使用

ThreadLocal主要解决的就是每个线程绑定自己的值。

第4章 Lock的使用

使用ReentrantLock类

ReentrantLock需要借助于Condition对象可以实现等待通知模式。

公平锁与非公平锁

公平锁表示线程获取锁的顺序是按照线程加锁的顺序来分配的,即先来先得的FIFO先进先出顺序;

非公平锁就是一种获取锁的抢占机制,是随机获得锁的。

使用ReentrantReadWriteLock类

ReentrantLock具有完全互斥排他的效果,即同一时间只有一个线程在执行ReentrantLock.lock()方法后面的任务,虽然保证了实例变量的线程安全性,但效率却是非常底下的。

读写锁表示也有两个锁,一个是读操作相关的锁,也成为共享锁;另一个是写操作相关的锁,也叫排他锁。多个读锁之间不互斥,写锁与读锁互斥,写锁与写锁互斥。多个Thread可以同时进行读取操作,但是同一时刻只允许一个Thread进行写入操作。

第6章 单例模式与多线程

立刻加载

1
2
3
4
5
6
7
public class MyObject {
private static MyObject myObject = new MyObject();
private MyObject() {}
public static MyObject getInstance() {
return myObject;
}
}

延迟加载

1
2
3
4
5
6
7
8
9
10
public class MyObject {
private static MyObject myObject;
private MyObject() {}
public static MyObject getInstance() {
if (myObject == null) {
myObject = new MyObject();
}
return myObject;
}
}

不能保证多线程安全

延迟加载的解决方案

synchronized关键字

1
2
3
4
5
6
7
8
9
10
public class MyObject {
private static MyObject myObject;
private MyObject() {}
synchronized public static MyObject getInstance() {
if (myObject == null) {
myObject = new MyObject();
}
return myObject;
}
}

效率低

双检查锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyObject {
private volatile static MyObject myObject;
private MyObject() {}
public static MyObject getInstance() {
if (myObject == null) {
synchronized(MyObject.class) {
if (myObject == null) {
myObject = new MyObject();
}
}
}
return myObject;
}
}

第7章 其他

线程的状态

六种状态

  • NEW
  • RUNNABLE
  • BLOCKED
  • WAITING
  • TIMED_WAITING
  • TERMINATED

补充:五种状态

  • 新建
  • 就绪(RUNNABLE )
  • 运行(RUNNABLE )
  • 堵塞(BLOCKED, WAITING,TIMED_WAITING)
  • 死亡