Java学习笔记——多线程I
本系列文章为自己在java学习过程中的一些心得记录,一些错误、理解上的偏差也希望大家批评指正
概述
使用多线程编程的好处
并发编程核心的目的 —— 让程序运行的更快
具体而言可以从三个层面进行理解
1)更多的处理器核心 —— 一个线程一个时刻只能在一个核心上运行
2)更快的响应时间 —— 对数据一致性要求不高的复杂的业务逻辑交给多线程去做
3)更好的编程模型 —— java本身提供了良好、考究并且一致的编程模型。程序员本身不用在多线程化上浪费过多精力,就可以编写出良好的多线程程序
多线程编程的两个核心问题
多线程编程的两个核心需要关注的问题——线程之间的通信 、 线程之间的同步
线程间的通信
Java通过共享内存模型的方式实现线程通信。
下面简单描述一下Java内存模型(JMM)的机制
线程之间的通信,通过JMM控制线程与之内存之间的交互来实现。
需要注意的问题
内存可见性的问题:
问题引出 —— 从源代码 -》指令序列的重排序
1)编译器优化(不改变单线程执行结果 -> 可以重排序)
2)指令级并行的重排序(ILP,不存在数据依赖性,可能重排序)
3)读写缓冲区 -》 不能保证线程之间刷写主内存的顺序
解决方式 ——
1)底层 —— JMM通过添加内存屏障的方式禁止特定的重排序
2) 从编程的角度来看,通过happens- before规则,保证变量的可见性
线程的同步问题
顺序一致性模型
理论参考模型:
1)一个线程中的所有操作必须按照程序的顺序来执行
2)所有线程都只能看到一个单一的操作执行顺序,每个操作都必须原子执行、并立即对所有线程可见
JMM内存模型对内存一致性的保证
正确同步的程序将具有顺序一致性 —— 程序的执行结果与顺序一致性模型中的执行结果一致
Java内存模型——JMM
如上文中提到,java内存模型对于程序员提供了保障 —— 只要按照规则对于程序进行正确同步,将得到与顺序一致性模型相同的结果。
而java内存模型也对底层进行适当的放松,允许一定的指令重排、读写缓冲区的存在(提高了程序的执行效率)
happens- before原则
happens- before是JMM内存模型中最为重要的一个概念,如果需要保证一个操作的执行结果对另一个操作课件,就必须存在happens- before关系
具体规则如下:
1)单线程,前面的hp后面的
2)一个锁的解锁 hp 后续的加锁
3)volatile的写 hp 后续对这个变量的读
4)传递性
5)线程A调用ThreadB.start(),A中的ThreadB.start() hp B中的任何操作
6)线程A ThreadB.join() B中的任意操作 hp A的ThreadB.join()
三个重要的机制
volatile
volatile 关键字可以理解为一个轻量级的Synchronized 保障了可见性、并禁止与其他变量操作的重排序
可见性 —— 可以理解为对一个volatile的读,总是能看到对这个变量最后的写操作
禁止重排序 —— 可以参考单例模式double-check中volatile的作用
内存语义
读操作 —— 把本地内存中的值设置为无效、去共享内存中读
写操作 —— 将该线程本地内存中的值,刷新到主内存中
典型应用场景(单例模式:double-check、Concurrent包下的多数共享变量)
锁
这里我们简单介绍一下,锁在JMM中的作用
根据h-p原则:一个锁的解锁 hp 后续的加锁
因此,被加锁/通过sychronized同步的方法(代码块)可以保障可见性/原子性/有序性
锁释放与获取的内存语义
线程A释放锁,线程B获取锁 ,实质上是线程A通过主内存向线程B发送消息
线程A释放锁 == 线程A向将要获取这个所得线程发出消息
线程B获取锁 == 线程B接受了某个线程发出的释放锁的消息
以JUC下的Lock包为例,介绍一下锁的释放与获取的内存语义
锁的获取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70// 非公平锁 //调用lock()方法 Lock lock = new ReentrantLock(); lock.lock(); //Reetrantlock 类下 lock方法 public void lock() { sync.lock(); //调用sync的lock方法 } //sync为静态内部类,继承了AQS abstract static class Sync extends AbstractQueuedSynchronizer { abstract void lock(); } //实现类 - 非公平锁的实现 static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L; final void lock() { if (compareAndSetState(0, 1)) // 该方法为AQS类实现的方法 setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } } //AQS类中的cas方法改变state变量 protected final boolean compareAndSetState(int expect, int update) { // See below for intrinsics setup to support this return unsafe.compareAndSwapInt(this, stateOffset, expect, update); } // 公平锁 final void lock() { acquire(1); } //AQS中的acquire方法 public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } //Sync中的tryAcquire方法 protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); //读volatile变量 if (c == 0) { if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
锁的释放
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40Lock lock = new ReentrantLock(true); lock.lock(); try { //相应的业务逻辑 }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); //释放锁的操作 } //ReetrantLock类 public void unlock() { sync.release(1); } //AQS类 public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; } //sync(继承AQS) protected final boolean tryRelease(int releases) { int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); // 本质还是volatile的写操作 return free; }
Concurrent包的实现示意图
CAS同时具有volatile读与写的内存语义
Concurrent包中类的基本实现思路:
1)共享变量声明为volatile
2)使用CAS进行原子更新,实现线程同步
3)配合volatile、CAS内存语义,实现线程间通信
例子见学习笔记中关于ConcurrentHashMap的介绍以及本系列笔记中后续关于Lock接口的介绍
摘自Java并发的编程艺术
final
关于final关键字
final关键字的作用:
用于修饰类(不可以被继承)、方法(不可以被重写)、变量(不可以更改(见代码示例))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34// 局部变量 public class FinalDemo { public static void main(String[] args) { final int[] arr = {2, 3, 5, 7, 11}; // 修饰引用数据类型,地址不可变、具体的值可以改变 // arr = null; 编译器无法通过 arr[1] = 4; System.out.println(arr[1]); // 4 } } // 类变量 class Programmer{ //static final boolean isHandsome; //编译不通过 static final boolean SMART = true; //要么声明时显示赋值 static final boolean isHandsome; static { isHandsome = true; // 要么在静态代码块中赋值 } } // 实例成员变量 class Architect{ //final boolean isHandsome; // 不进行显示赋值,编译不通过 final boolean isHandsome = true; // 声明时赋值 final boolean isSmart ; final boolean isTall ; { isSmart = true; // 非静态代码块中复制 } public Architect(){ isTall = true; //构造器中赋值 } }
Bonus:String、StringBuilder、StringBuffer
三者的区别:
String不可变、StringBuilder、StringBuffer是可变的
从运行速度上讲:StringBuilder > StringBuffer > String
String适合适用于少量的字符串操作的情况;StringBuilder适用于适用于单线程下在字符缓冲区进行大量操作的情况;StringBuffer适用多线程下在字符缓冲区进行大量操作的情况。
1
2
3
4
5
6
7public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[]; //底层为final类型的数组,因此不可变 ... }
关于String的拼接
底层是new了一个StringBuilder 进行相应的拼接操作,最后调用toString()方法,重新赋值给String
关于StringBuilder
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32// 空参构造器(初始化数组) public StringBuilder() { super(16); } // 父类中的方法 AbstractStringBuilder(int capacity) { value = new char[capacity]; //初始化长度为16的数组 } // 其他构造方法 public StringBuilder(String str) { super(str.length() + 16); // char[] 数组会有所余量 append(str); } // append方法 public StringBuilder append(StringBuffer sb) { super.append(sb); return this; } public AbstractStringBuilder append(StringBuffer sb) { if (sb == null) return appendNull(); int len = sb.length(); ensureCapacityInternal(count + len); // 确保是否需要扩容 sb.getChars(0, len, value, count); // 拷贝元素 count += len; return this; }
关于StringBuffer (相关方法添加了synchronized ,线程安全但效率较低)
1
2
3
4
5
6
7@Override public synchronized StringBuffer append(String str) { toStringCache = null; super.append(str); return this; }
final 域的重排序规则
1)构造函数内对于一个final域的写入,域随后把这个被构造对象的引用赋值给一个引用变量,不能重排序
2)初始读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序
1
2
3return this; }
1
2
3
4
5
6
7
8
9
10
11关于StringBuffer (相关方法添加了synchronized ,线程安全但效率较低) ```java @Override public synchronized StringBuffer append(String str) { toStringCache = null; super.append(str); return this; }
final 域的重排序规则
1)构造函数内对于一个final域的写入,域随后把这个被构造对象的引用赋值给一个引用变量,不能重排序
2)初始读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序
核心就是保证,构造函数返回后,任意线程都将保证能看到final域正确初始化的值
最后
以上就是淡然小霸王最近收集整理的关于Java学习笔记——多线程IJava学习笔记——多线程I的全部内容,更多相关Java学习笔记——多线程IJava学习笔记——多线程I内容请搜索靠谱客的其他文章。
发表评论 取消回复