- 第 1 章 线程概述
- 第 2 章 线程安全问题
- 第 3 章 线程同步
- 第 4 章 线程间的通信
1.1 线程相关概念
进程:进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是操作系统进行资源分配与调度的基本单位。
线程:线程(thread)是进程的一个执行单元。
一个线程就是进程中一个单一顺序的控制流, 进程的一个执行分支。
进程是线程的容器,一个进程至少有一个线程.一个进程中也可以有多个线程。
在操作系统中是以进程为单位分配资源,如虚拟存储空间,文件描述符等. 每个线程都有各自的线程栈,自己的寄存器环境,自己的线程本地存储。
并发可以提高以事物的处理效率, 即一段时间内可以处理或者完成更多的事情.
并行是一种更为严格,理想的并发
从硬件角度来说, 如果单核 CPU,一个处理器一次只能执行一个线程的情况下,处理器可以使用时间片轮转技术 ,可以让 CPU 快速的在各个线程之间进行切换, 对于用来来说,感觉是三个线程在同时执行. 如果是多核心CPU,可以为不同的线程分配不同的CPU 内核.
1.2 线程的创建与启动
在Java 中,创建一个线程就是创建一个 Thread 类(子类)的对象(实例).
Thread 类有两个常用 的构造方法:Thread()与 Thread(Runnable).对应的创建线程的两种方式:
(1)定义Thread 类的子类
(2)定义一个Runnable 接口的实现类
这两种创建线程的方式没有本质的区别.
调用线程的start()方法来启动线程, 启动线程的实质就是请求JVM 运行相应的线程,这个线程具体在什么时候运行由线程调度器(Scheduler)决定。
注意:
start()方法调用结束并不意味着子线程开始运行 新开启的线程会执行 run()方法
如果开启了多个线程,start()调用的顺序并不一定就是线程启动的顺序
多线程运行结果与代码执行顺序或调用顺序无关
当线程类已经有父类了,就不能用继承 Thread 类的形式创建线程,可以使用实现 Runnable
定义类实现 Runnable 接口
public class MyRunnable implements Runnable {
//2)重写 Runnable 接口中的抽象方法 run(), run()方法就是子线程要执行的代码@Override
public void run() {
for(int i = 1; i<=1000; i++){
System.out.println( "sub thread --> " + i);
}
}
}
测试实现 Runnable 接口的形式创建线程
public class Test {
public static void main(String[] args) {
//3)创建 Runnable 接口的实现类对象
MyRunnable runnable = new MyRunnable();
//4)创建线程对象
Thread thread = new Thread(runnable);
//5)开启线程thread.start();
//当前是 main 线程
for(int i = 1; i<=1000; i++){
System.out.println( "main==> " + i);
}
}
}
1.3 线程的常用方法
1.3.1 currentThread()方法
Thread.currentThread()方法可以获得当前线程
Java 中的任何一段代码都是执行在某个线程当中的. 执行当前代码的线程就是当前线程.
同一段代码可能被不同的线程执行, 因此当前线程是相对的,Thread.currentThread()方法的返回值是在代码实际运行时候的线程对象
1.3.2 setName() / getName()
thread.setName(线程名称), 设置线程名称
thread.getName()返回线程名称
通过设置线程名称,有助于程序调试,提高程序的可读性, 建议为每个线程都设置一个能够体现线程功能的名称
1.3.3 isAlive()
thread.isAlive()判断当前线程是否处于活动状态
活动状态就是线程已启动并且尚未终止
1.3.4 sleep()
Thread.sleep(millis); 让当前线程休眠指定的毫秒数
1.3.5 getId()
thread.getId() 可以获得线程的唯一标识
注意:某个编号的线程运行结束后,该编号可能被后续创建的线程使用
1.3.6 yield()
Thread.yield()方法的作用是放弃当前的CPU 资源
1.3.7 setPriority()
thread.setPriority( num ); 设置线程的优先级
java 线程的优先级取值范围是 1 ~ 10 , 如果超出这个范围会抛出异常IllegalArgumentException.
在操作系统中,优先级较高的线程获得CPU 的资源越多
线程优先级本质上是只是给线程调度器一个提示信息,以便于调度器决定先调度哪些线程. 注意不能保证优先级高的线程先运行.
Java 优先级设置不当或者滥用可能会导致某些线程永远无法得到运行,即产生了线程饥饿.
线程的优先级并不是设置的越高越好,一般情况下使用普通的优先级即可,在开发时不必设置线程的优先级。默认优先级为5
线程的优先级具有继承性, 在A 线程中创建了B 线程,则B 线程的优先级与A 线程是一样的.
1.3.8 interrupt()
中断线程.
注意调用 interrupt()方法仅仅是在当前线程打一个停止标志,并不是真正的停止线程
1.3.9 setDaemon()
Java 中的线程分为用户线程与守护线程
守护线程是为其他线程提供服务的线程,如垃圾回收器(GC)就是一个典型的守护线程
守护线程不能单独运行, 当JVM 中没有其他用户线程,只有守护线程时,守护线程会自动销毁, JVM 会退出
设置守护线程的代码应该在线程启动前,不然设置无效
1.4 线程的生命周期
线程状态图:
1.5 多线程编程的优势与存在的风险
2.1 原子性
2.2 可见性
2.3 有序性
2.3.1 重排序
2.3.2 指令重排序
2.3.3 存储子系统重排序
2.3.4 貌似串行语义
2.3.4 保证内存访问的顺序性
可以使用volatile 关键字, synchronized 关键字实现有序性
2.4 Java 内存模型
刷新处理器缓存是将主内存的数据读取到缓存区中;冲刷处理器缓存指的是将缓存的数据刷新到主内存中。
3.1 线程同步机制简介
3.2 锁概述
3.2.1 锁的作用
3.2.2 锁相关的概念
3.3 内部锁:synchronized 关键字
3.3.1 synchronized 同步代码块
this 锁对象:
使用一个常量对象作为锁对象:
使用一个常量对象作为锁对象,不同方法中(例如普通方法和静态方法)的同步代码块也可以同步。
3.3.2 同步方法
package com.wkcto.intrinsiclock;
public class Test05 {
public static void main(String[] args) {
//先创建 Test01 对象,通过对象名调用 mm()方法
Test05 obj = new Test05();
//一个线程调用 mm()方法
new Thread(new Runnable() {
@Override
public void run() {
obj.mm(); //使用的锁对象this就是obj对象
}
}).start();
//另一个线程调用 mm22()方法
new Thread(new Runnable() {
@Override
public void run() {
obj.mm22(); //使用的锁对象 this 也是 obj 对象, 可以同步
// new Test05().mm22(); //使用的锁对象 this 是刚刚 new 创建的一个新对象,不是同一个锁对象不能同步
}
}).start();
}
//定义方法,打印 100 行字符串
public void mm(){
synchronized ( this ) { //经常使用this当前对象作为锁对 象
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + " --> " + i);
}
}
}
//使用 synchronized 修饰实例方法,同步实例方法, 默认 this 作为 锁对象
public synchronized void mm22(){
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + " --> " + i);
}
}
}
package com.wkcto.intrinsiclock;
public class Test06 {
public static void main(String[] args) {
//先创建 Test01 对象,通过对象名调用 mm()方法
Test06 obj = new Test06();
//一个线程调用 mm()方法
new Thread(new Runnable() {
@Override
public void run() {
obj.m1(); //使用的锁对象是 Test06.class
}
}).start();
//另一个线程调用 sm2()方法
new Thread(new Runnable() {
@Override
public void run() {
obj.sm2(); ///使用的锁对象是 Test06.class
}
}).start();
}
//定义方法,打印 100 行字符串
public void m1(){
//使用当前类的运行时类对象作为锁对象,可以简单的理解 为把 Test06 类的字节码文件作为锁对象
synchronized ( Test06.class ) {
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + " --> " + i);
}
}
}
//使用 synchronized 修饰静态方法,同步静态方法, 默认运行时类 Test06.class 作为锁对象
public synchronized void sm2(){
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + " --> " + i);
}
}
}
package com.wkcto.intrinsiclock;
public class Test07 {
public static void main(String[] args) {
//先创建 Test01 对象,通过对象名调用 mm()方法
Test07 obj = new Test07();
//一个线程调用 mm()方法
new Thread(new Runnable() {
@Override
public void run() {
obj.doLongTimetask();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
obj.doLongTimetask();
}
}).start();
}
//同步方法, 执行效率低
public synchronized void doLongTimetask(){
try {
System.out.println("Task Begin");
Thread.sleep(3000); //模拟任务需要准备 3 秒 钟
System.out.println("开始同步");
for(int i = 1; i <= 100; i++){
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
System.out.println("Task end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//同步代码块,锁的粒度细, 执行效率高
public void doLongTimetask2(){
try {
System.out.println("Task Begin");
Thread.sleep(3000); //模拟任务需要准备3秒钟
synchronized (this){
System.out.println("开始同步");
for(int i = 1; i <= 100; i++){
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
System.out.println("Task end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3.3.3 脏读
脏读:出现读取属性值出现了一些意外, 读取的是中间值,而不是修改之后的值。
出现脏读的原因是 对共享数据的修改 与对共享数据的读取不同步。
解决方法:不仅对修改数据的代码块进行同步,还要对读取数据的代码块同步
3.3.4 线程出现异常会自动释放锁
同步过程中线程出现异常, 会自动释放锁对象,其它等待线程可以获得锁继续执行。
3.3.5 死锁
在多线程程序中,同步时可能需要使用多个锁,如果获得锁的顺序不一致,可能会导致死锁。
如何避免死锁?当需要获得多个锁时,所有线程获得锁的顺序保持一致即可。
3.4 轻量级同步机制:volatile 关键字
3.4.1 volatile 的作用
volatile 关键的作用使变量在多个线程之间可见.
volatile 的作用可以强制线程从公共内存中读取变量的值,而不是从工作内存中读取
volatile 与synchronized 比较
volatile就是只有对内存进行冲刷的作用,而synchronized不仅有内存冲刷,还加上了锁来进行串行。
3.4.2 volatile 非原子特性
volatile 关键字增加了实例变量在多个 线程之间的可见性,但是不具备原子性。
package com.wkcto.volatilekw;
public class Test03 {
public static void main(String[] args) {
//在 main 线程中创建 10 个子线程
for (int i = 0; i < 100; i++) {
new MyThread().start();
}
}
static class MyThread extends Thread{
//volatile 关键仅仅是表示所有线程从主内存读取 count 变量的值
public static int count; //加了static表示count是类锁
public synchronized static void addCount(){
for (int i = 0; i < 1000; i++) {
//count++不是原子操作
count++;
}
System.out.println(Thread.currentThread().getName() + " count=" + count);
}
@Override
public void run() {
addCount();
}
}
}
3.4.3 常用原子类进行自增自减操作
我们知道i++操作不是原子操作, 除了使用Synchronized 进行同步外,也可以使用 AtomicInteger/AtomicLong 原子类进行实现
package com.wkcto.volatilekw;
import java.util.concurrent.atomic.AtomicInteger;
public class Test04 {
public static void main(String[] args) throws InterruptedException {
//在 main 线程中创建 10 个子线程
for (int i = 0; i < 1000; i++){
new MyThread().start();
}
Thread.sleep(1000);
System.out.println( MyThread.count.get());
}
static class MyThread extends Thread {
//使用 AtomicInteger 对象
private static AtomicInteger count = new AtomicInteger();
public static void addCount(){
for (int i = 0; i < 10000; i++) {
//自增的后缀形式
count.getAndIncrement();
}
System.out.println(Thread.currentThread().getName() + " count=" + count.get());
}
@Override
public void run() {
addCount();
}
}
}
3.5 CAS
使用CAS 实现线程安全的计数器
package com.wkcto.cas;
public class CASTest {
public static void main(String[] args) {
CASCounter casCounter = new CASCounter();
for (int i = 0; i < 100000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(casCounter.incrementAndGet());
}
}).start();
}
}
}
class CASCounter{
//使用 volatile 修饰 value 值,使线程可见
volatile private long value;
public long getValue() {
return value;
}
//定义 comare and swap 方法
private boolean compareAndSwap(long expectedValue, long newValue){
//如果当前value 的值与期望的expectedVAlue 值一样,就把当前的Value 字段替换为newValue 值
synchronized (this){
if ( value == expectedValue){
value = newValue; return true;
}else {
return false;
}
}
}
//定义自增的方法
public long incrementAndGet(){
long oldvalue;
long newValue;
do {
oldvalue = value; newValue = oldvalue+1;
}while (!compareAndSwap(oldvalue, newValue) );
return newValue;
}
}
3.6 原子变量类
3.6.2 AtomicIntegerArray
原子更新数组
package com.wkcto.atomics.atomicarray;
import java.util.concurrent.atomic.AtomicIntegerArray;
public class Test {
public static void main(String[] args) {
//1)创建一个指定长度的原子数组
AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(10);
System.out.println( atomicIntegerArray ); //[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
//2)返回指定位置的元素
System.out.println( atomicIntegerArray.get(0)); //0
System.out.println( atomicIntegerArray.get(1)); //0
//3)设置指定位置的元素
atomicIntegerArray.set(0, 10); //在设置数组元素的新值时, 同时返回数组元素的旧值
System.out.println( atomicIntegerArray.getAndSet(1, 11) ); //0
System.out.println( atomicIntegerArray ); //[10, 11, 0, 0, 0, 0, 0, 0, 0, 0]
//4)修改数组元素的值,把数组元素加上某个值
System.out.println( atomicIntegerArray.addAndGet(0, 22) ); //32
System.out.println( atomicIntegerArray.getAndAdd(1, 33)); //11
System.out.println( atomicIntegerArray ); //[32, 44, 0, 0, 0, 0, 0, 0, 0, 0]
//5)CAS 操作 //如果数组中索引值为 0 的元素的值是 32 , 就修改为 222
System.out.println( atomicIntegerArray.compareAndSet(0, 32, 222)); //true
System.out.println( atomicIntegerArray ); //[222, 44, 0, 0, 0, 0, 0, 0, 0, 0]
System.out.println( atomicIntegerArray.compareAndSet(1, 11, 333)); //false
System.out.println(atomicIntegerArray);
//6)自增/自减
System.out.println( atomicIntegerArray.incrementAndGet(0) ); //223, 相当于前缀
System.out.println( atomicIntegerArray.getAndIncrement(1)); //44, 相当于后缀
System.out.println( atomicIntegerArray ); //[223, 45, 0, 0, 0, 0, 0, 0, 0, 0]
System.out.println( atomicIntegerArray.decrementAndGet(2)); //-1
System.out.println( atomicIntegerArray); //[223, 45, -1, 0, 0, 0, 0, 0, 0, 0]
System.out.println( atomicIntegerArray.getAndDecrement(3)); //0
System.out.println( atomicIntegerArray ); //[223, 45, -1, -1, 0, 0, 0, 0, 0, 0] } }
3.6.3 AtomicIntegerFieldUpdater
User.java
package com.wkcto.atomics.atominintegerfiled;
public class User {
int id;
volatile int age;
public User(int id, int age) {
this.id = id;
this.age = age;
}
@Override
public String toString() {
return "User{" + "id=" + id +", age=" + age + '}';
}
}
SubThread.java
package com.wkcto.atomics.atominintegerfiled;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
//线程类
public class SubThread extends Thread {
private User user; //要更新的 User 对象
//创建 AtomicIntegerFieldUpdater 更新器
private AtomicIntegerFieldUpdater updater = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");
public SubThread(User user) {
this.user = user;
}
@Override public void run() {
//在子线程中对 user 对象的 age 字段自增 10 次
for (int i = 0; i < 10; i++) {
System.out.println( updater.getAndIncrement(user));
}
}
}
Test.java
package com.wkcto.atomics.atominintegerfiled;
public class Test {
public static void main(String[] args) {
User user = new User(1234, 10);
//开启 10 个线程
for (int i = 0; i < 10; i++) {
new SubThread(user).start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println( user );
}
}
3.6.4 AtomicReference
可以原子读写一个对象
package com.wkcto.atomics.atomicreference;
import java.util.Random;
import java.util.concurrent.atomic.AtomicReference;
public class Test01 {
//创建一个 AtomicReference 对象
static AtomicReference atomicReference = new AtomicReference<>("abc");
public static void main(String[] args) throws InterruptedException {
//创建 100 个线程修改字符串
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(new Random().nextInt(20));
} catch (InterruptedException e) {
e.printStackTrace();
}
if (atomicReference.compareAndSet("abc","def")){
System.out.println(Thread.currentThread().getName() + "把字符串 abc 更改为 def");
}
}
}).start();
}
//再创建 100 个线程
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(new Random().nextInt(20));
} catch (InterruptedException e) {
e.printStackTrace();
}
if (atomicReference.compareAndSet("def","abc")){
System.out.println(Thread.currentThread().getName() + "把字符串 还原为 abc");
}
}
}).start();
}
Thread.sleep(1000);
System.out.println(atomicReference.get());
}
}
AtomicReference 可能会出现 CAS 的 ABA 问题。
AtomicStampedReference 原子类解决 CAS 中的 ABA 问题:
AtomicStampedReference类包含了一个有用的 compareAndSet()方法, compareAndSet()方法将一个期望的引用与存储在AtomicStampedReference实例中的值比较,如果引用与数据戳都相等(equals()或者 ==),那么 AtomicStampedReference实例将被设置为新的引用,compareAndSet()如果设置成功将返回ture否则返回false。下面是AtomicStampedReference 的compareAndSet()例子:
String initialRef = "initial value referenced"; int initialStamp = 0; AtomicStampedReferenceatomicStringReference = new AtomicStampedReference ( initialRef, initialStamp ); String newRef = "new value referenced"; int newStamp = initialStamp + 1; boolean exchanged = atomicStringReference .compareAndSet(initialRef, newRef, initialStamp, newStamp); System.out.println("exchanged: " + exchanged); //true exchanged = atomicStringReference .compareAndSet(initialRef, "new string", newStamp, newStamp + 1); System.out.println("exchanged: " + exchanged); //false exchanged = atomicStringReference .compareAndSet(newRef, "new string", initialStamp, newStamp + 1); System.out.println("exchanged: " + exchanged); //false exchanged = atomicStringReference .compareAndSet(newRef, "new string", newStamp, newStamp + 1); System.out.println("exchanged: " + exchanged); //true
例子首先创建了 AtomicStampedReference,然后通过compareAndSet()设置引用和数据戳,第二次在调用 compareAndSet()没有成功,因为第一次的 initialRef 与存储值一样,这时内部存储的是newRef,所以在调用compareAndSet()失败。
第二段,首先initialStamp 与期望值newStamp 不一样更新失败,此时存储的为newStamp ,再次调用compareAndSet()则成功。
package com.wkcto.atomics.atomicreference;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
public class Test03 {
//定义 AtomicStampedReference 引用操作"abc"字符串,指定初始化版本号为 0
private static AtomicStampedReference stampedReference = new AtomicStampedReference<>("abc", 0);
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override public void run() {
stampedReference.compareAndSet("abc", "def", stampedReference.getStamp(), stampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName() + "--" +stampedReference.getReference());
stampedReference.compareAndSet("def", "abc", stampedReference.getStamp(), stampedReference.getStamp()+1);
}
});
Thread t2 = new Thread(new Runnable() {
@Override public void run() {
int stamp = stampedReference.getStamp(); //获得版本号 如果这条语句在这里 对应结果2
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//int stamp = stampedReference.getStamp(); //获得版本号 如果这条语句在这里 对应结果1
System.out.println( stampedReference.compareAndSet("abc", "ggg", stamp, stamp+1));
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println( stampedReference.getReference() );
}
}
结果1:
结果2:



