我是靠谱客的博主 淡然小霸王,这篇文章主要介绍Java学习笔记——多线程IJava学习笔记——多线程I,现在分享给大家,希望可以做个参考。

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
40
Lock 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
7
public 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
3
return 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内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(54)

评论列表共有 0 条评论

立即
投稿
返回
顶部