synchronized

线程安全的主要诱因

  • 存在共享数据
  • 存在多条线程共同操作这些共享数据
  • 根本方法:同一时刻有且只有一个线程在操作共享数据,其他线程必须等到该线程处理完数据后再对共享数据进行操作

互斥锁的特性

  • 互斥性:同一时间只允许一个线程持有某个对象锁
  • 可见性:锁被释放前,对共享变量所做的修改,对于随后获得该锁的线程是可见的
  • synchronized锁的是对象而不是代码

获取的锁的分类:获取对象锁和获取类锁

获取对象锁的两种方法:

  1. 同步代码块,synchronized(this),synchronized(类实例对象)
  2. 同步非静态方法(synchronized method),锁的是当前对象的实例对象
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
public class SyncThread implements Runnable {

@Override
public void run() {
String threadName = Thread.currentThread().getName();
if (threadName.startsWith("A")) {
async();
} else if (threadName.startsWith("B")) {
syncObjectBlock1();
} else if (threadName.startsWith("C")) {
syncObjectMethod1();
} else if (threadName.startsWith("D")) {
syncClassBlock1();
} else if (threadName.startsWith("E")) {
syncClassMethod1();
}

}

/**
* 异步方法
*/
private void async() {
try {
System.out.println(Thread.currentThread().getName() + "_Async_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "_Async_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}

/**
* 方法中有 synchronized(this|object) {} 同步代码块
*/
private void syncObjectBlock1() {
System.out.println(Thread.currentThread().getName() + "_SyncObjectBlock1: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
synchronized (this) {
try {
System.out.println(Thread.currentThread().getName() + "_SyncObjectBlock1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "_SyncObjectBlock1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

/**
* synchronized 类锁
*/
private synchronized void syncObjectMethod1() {
System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod1: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
try {
System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}

/**
* synchronized 修饰非静态方法
*/
private void syncClassBlock1() {
System.out.println(Thread.currentThread().getName() + "_SyncClassBlock1: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
synchronized (SyncThread.class) {
try {
System.out.println(Thread.currentThread().getName() + "_SyncClassBlock1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "_SyncClassBlock1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

/**
* synchronized 修饰静态方法
*/
private synchronized static void syncClassMethod1() {
System.out.println(Thread.currentThread().getName() + "_SyncClassMethod1: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
try {
System.out.println(Thread.currentThread().getName() + "_SyncClassMethod1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "_SyncClassMethod1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

对象锁和类锁的总结

synchronized底层实现原理

实现synchronized的基础

  • Java对象头

对象在内存中的布局(对象头,实例数据,对齐填充)

  • Monitor 每个Java对象天生自带了一把看不见的锁,存在Java对象的对象头中

ObjectMonitor实现:

每个对象锁的线程会被封装成ObjectWaiter存入EntryList

synchronized历史

  • 自旋锁:等待锁释放,不让出cpu

  • 自适应自旋锁:自旋锁线程的等待时间和次数不固定

  • 锁消除

  • 锁粗化

synchronized的四种状态

synchronized和reentrantLock

ReentrantLock(再入锁)

synchronized和reentrantLock公平性的设置

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
public class ReentrantLockDemo implements  Runnable{
private static ReentrantLock lock = new ReentrantLock(false);
@Override
public void run(){
while (true){
try{
lock.lock();
System.out.println(Thread.currentThread().getName() + " get lock");
Thread.sleep(1000);
} catch (Exception e){
e.printStackTrace();
} finally {
lock.unlock();
}
}
}

public static void main(String[] args) {
ReentrantLockDemo rtld = new ReentrantLockDemo();
Thread thread1 = new Thread(rtld);
Thread thread2 = new Thread(rtld);
thread1.start();
thread2.start();
}
}
//输出交替

总结

JMM的内存可见性

java内存模型

内存模型规定了多线程程序读写操作规范

java内存模型(java memory model)描述的是一组规范,定义了程序中各个变量的访问方式

线程对变量的操作必须在本地内存中执行,线程先把变量从主内存拷贝到工作内存,然后对变量进行操作,再拷贝到主内存中,线程之间的通信必须通过主内存

JMM中的主内存

  • 存储Java实例对象
  • 包括成员变量,类信息,常量,静态变量等
  • 属于数据共享的区域,多线程并发操作时会引发线程安全问题

JMM中的工作内存

  • 储存当前方法的所有本地变量信息,#本地变量对其他线程不可见
  • 字节码行号指示器,Native方法信息
  • 数据线程私有数据区域,不存在线程安全问题

JMM与java内存划分是不同的概念层次

  • JMM描述的是一组规则,围绕原子性,有序性,可见性展开
  • 相似点:存在共享区域和私有区域

主内存与工作内存的数据存储类型以及操作方式归纳

  • 方法里的基本数据类型本地变量将直接存储在工作内存的栈帧结构中

  • 引用类型的本地变量:引用存储在工作内存中,实例存储在主内存中

  • 成员变量,static变量,类信息均会被储存到主内存中

  • 主内存共享的方式是线程各拷贝一份数据到工作内存中,操作完成后再刷新回主内存

JMM怎么解决可见性问题

指令重新排序需要满足的条件

A操作的结果对B操作可见,则A与B存在happens-before的关系

happens-before的概念

volatile:JVM提供的轻量级同步机制

  • volatile修饰的变量对所有线程总是可见的
  • 禁止指令重排

多线程操作可能引发线程安全=>

synchronized也具有可见性,可以省略volatile=>

volatile如何实现可见性

volatile如何禁止重排优化-(内存屏障,以及保证内存可见性)

单例是双重检测实现

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

public class Singleton{
// 所以要加volatile关键字
private volatile static Singleton instance;

private Singleton(){}
public static Singleton getInstance(){
//问题:A判断为空后进入但还未实例化B也判断为空
if(intstance==null){
instance=new Singleton();
}

//问题:AB都判断为空,A先实例化,B拿到锁又实例化
if(instance==null){
synchronized(Singleton.class){
instance=new Singleton();
}
}
//问题:1.指令重排后,线程A未初始化对象,但instance指向了刚分配的内存空间,instance!=null,B线程可能拿到一个未成品 2:线程A初始化完对象,但还未刷入主存,线程B可能还会初始化一个实例

if(instance==null){
synchronized(Singleton.class){
if(instance==null){
instance= new Singleton();
}
}
}

}
}

CAS(Compare and Swap)

java线程池

利用Executors创建不同的线程池满足不同场景的需求

使用双端队列实现

为什么要使用线程池

  • 降低资源消耗
  • 提高线程的可管理性

Executor的框架


线程池的基本组成:

  • corePoolSize:核心线程数量(长期驻留的线程数)
  • maximumPoolSize:线程不够用时能创建的最大线程数
  • workQueue:任务等待队列
  • keepAliveTime:非核心线程等待时间
  • threadFactory:创建新线程
  • handler:线程池饱和策略(阻塞队列满了并且没有空闲线程)

线程池的状态

流程图

生命周期

线程池的大小如何选定

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×