此博客转载自一篇优秀博客: https://blog.csdn.net/u012449363/article/details/86528529
1. JVM与线程安全
可见性:当多个线程对一个线程进行操作的时候,其中一个线程修改了变量的值,而其他的线程并不知道该值已经被修改
可见性-synchronized
JMM关于synchronized的两条规定:
1、线程解锁前,必须把共享变量的最新值刷新到主内存
2、线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(注意:加锁与解锁是同一把锁)
可见性-volatile
通过加入内存屏障和禁止重排序优化来实现
1、对volatile变量写操作时,会在写操作后加入一条store屏障指令,将本地内存中的共享变量值刷新到主内存
2、对volatile变量读操作时,会在读操作前加入一条load屏障指令,从主内存读取共享变量
原子性:修改操作不可被切割
2. synchronized关键字原理
线程安全概念:如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,
就是线程安全的。
线程安全问题都是又全局变量以及静态变量引起的。
如果每个线程对全局变量、静态变量只有读操作,没有写操作,一般来说是线程安全的。如果多个线程同时执行写操作,一般需要考虑线程同步,否则的话可能会影响线程安全。
synchronized作用:加锁,所有synchronize方法都会顺序执行
执行方式:
尝试获取锁
如果获得了锁,执行synchronized的方法体的内容
如果无法获得锁,则不断的尝试获得锁。一旦锁被释放,则多个线程会同时尝试获得锁,造成锁竞争的问题。【锁竞争在高并发、线程数量高是会引发CPU占用居高不下,甚至直接宕机】
3. 对象锁和类锁
synchronized作用在非静态方法上代表的是对象锁,一个对象一个锁,多个对象之间不会发生锁竞争
synchronized作用在静态方法时,则升级为类锁,所有该类的对象共享一把锁,存在锁竞争。
4. 对象锁的同步和异步
同一个对象中所有的synchronized方法都是同步执行的。非synchronized方法异步执行。
synchronized加锁的最小粒度是对象。
例如:如果一个对象中有两个synchronized方法func1和func2,两个线程分别对应func1和func2,这两个方法之间也会存在锁竞争。
5. 脏读问题
多个线程访问同一个资源,在一个线程修改数据的过程中,有另外一个线程来读取数据,就会引起脏读。
为了避免脏读,在操作时要保证数据修改操作的原子性,并且对读操作也要进行同步控制
6.锁重入
同一个线程得到了一个对象锁之后,再次请求此对象是可以再次获取该对象的锁。
同一个对象的多个synchronized方法可以重入
synchronized A()在调用synchronized B()方法时, A与B不会存在锁竞争,而是存在锁重入。
父子类的锁可以重入
7. 抛出异常释放锁
一个线程在获得锁之后执行操作,如果发生错误抛出异常,则走自动释放锁。
- 可以利用抛出异常,主动释放锁
- 程序异常时,防止资源被死锁,无法释放
- 异常释放锁可能导致数据不一致
8. synchronized代码块
相同类型的锁互斥,不同类型的锁互不干扰。
如果在线程内修改了锁的引用,则会导致锁失效。
修改锁对象的属性不会导致锁失效,修改锁对象的引用会导致锁失效。
final关键字的含义?
final在Java中是一个保留的关键字,可以声明成员变量、方法、类以及本地变量。一旦你将引用声明作final,你将不能改变这个引用了,编译器会检查代码,如果你试图将变量再次初始化的话,编译器会报编译错误。
修改锁引用导致锁失效的DEMO
package com.jimmy.test;
/**
* 修改锁的引用导致锁失效
*/
public class Test {
private String lock = "object lock";
private void method() {
synchronized (lock) {
try {
System.out.println("start - " + Thread.currentThread().getName() + " use " + lock);
// 如果在这里修改了锁的引用,则锁会失效
lock = "changed object lock";
Thread.sleep(2000);
System.out.println("end - " + Thread.currentThread().getName() + " use " + lock);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
final Test test = new Test();
Thread t1 = new Thread(() -> test.method(), "t1");
Thread t2 = new Thread(() -> test.method(), "t2");
t1.start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
锁不失效的结果:
锁失效的结果:
在声明锁的时候使用final关键字,可以避免锁被修改
9. 死锁
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
10. 线程之间的通信
object类中的wait/notify 可以实现线程通信
wait/notify必须要synchronized关键字一起使用
public final void wait() throws InterruptedException
Causes the current thread to wait until it is awakened, typically by being notified or interrupted.
In all respects, this method behaves as if wait(0L, 0) had been called. See the specification of the wait(long, int) method for details.
Throws:
IllegalMonitorStateException - if the current thread is not the owner of the object’s monitor
InterruptedException - if any thread interrupted the current thread before or while the current thread was waiting. The interrupted status of the current thread is cleared when this exception is thrown.
wait方法在获取锁之前会一直阻塞当前的线程。
public final void notify()
Wakes up a single thread that is waiting on this object’s monitor. If any threads are waiting on this object, one of them is chosen to be awakened. The choice is arbitrary and occurs at the discretion of the implementation. A thread waits on an object’s monitor by calling one of the wait methods.
The awakened thread will not be able to proceed until the current thread relinquishes the lock on this object. The awakened thread will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened thread enjoys no reliable privilege or disadvantage in being the next thread to lock this object.
This method should only be called by a thread that is the owner of this object’s monitor. A thread becomes the owner of the object’s monitor in one of three ways:
By executing a synchronized instance method of that object.
By executing the body of a synchronized statement that synchronizes on the object.
For objects of type Class, by executing a synchronized static method of that class.
Only one thread at a time can own an object’s monitor.
Throws:
IllegalMonitorStateException - if the current thread is not the owner of this object’s monitor.
notify 只会通知一个wait中的线程,并把锁给他,不会产生锁竞争的问题。但是该线程处理完毕之后,必须再次notify或者notifyAll,完成类似链式操作。
notify 会通知所有wait中的线程,会产生锁竞争问题。
notify 和 notifyAll的区别
notify方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。notifyAll 会唤醒所有等待(对象的)线程,尽管哪一个线程将会第一个处理取决于操作系统的实现。如果当前情况下有多个线程需要被唤醒,推荐使用notifyAll 方法。
实例:实现一个线程安全的阻塞队列
- 线程安全:在高并发的场景下,队列的数据的【读写】是线程安全的。
- 阻塞队列:在队列空时阻塞【读线程】,在队列满的时候阻塞【写线程】
队列代码
import java.util.ArrayList;
import java.util.List;
public class MQueue {
private int maxSize;
private List<String> list = new ArrayList<>();
private final Object lock = new Object();
public MQueue(int maxSize) {
this.maxSize = maxSize;
System.out.println("线程-" + Thread.currentThread().getName() + "已初始化长度为" + this.maxSize + "的队列");
}
public void put(String element) {
synchronized (lock) {
if (this.list.size() == this.maxSize) {
try {
System.out.println("线程-" + Thread.currentThread().getName() + "队列已满 put waiting");
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.list.add(element);
System.out.println("线程-" + Thread.currentThread().getName() + "向队列中加入元素:" + element);
lock.notifyAll();
}
}
public String take() {
synchronized (lock) {
if (this.list.size() == 0) {
try {
System.out.println("线程-" + Thread.currentThread().getName() + "队列已空 take waiting");
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
String result = this.list.remove(0);
System.out.println("线程-" + Thread.currentThread().getName() + "从队列中取出元素:" + result);
lock.notifyAll();
return result;
}
}
}
演示代码
public class Test {
public static void main(String[] args) {
MQueue queue = new MQueue(5);
new Thread(() -> {
queue.put("1");
queue.put("2");
queue.put("3");
queue.put("4");
queue.put("5");
queue.put("6");
}, "put-thread-1").start();
new Thread(() -> {
queue.put("11");
queue.put("12");
queue.put("13");
queue.put("14");
queue.put("15");
queue.put("16");
}, "put-thread-1").start();
new Thread(() -> {
queue.take();
queue.take();
queue.take();
queue.take();
queue.take();
}, "take-thread-1").start();
new Thread(() -> {
queue.take();
queue.take();
queue.take();
queue.take();
queue.take();
}, "take-thread-2").start();
}
}
演示结果
一旦队列为空,就会阻塞读进程;一旦队列满了,就阻塞写进程