目录
7.synchronized - 监视器锁monitor lock
一、线程安全(重点)
1.线程安全演示
/**
* 线程安全演示
*/
public class Text03 {
public static void main(String[] args) throws InterruptedException {
// 初始化累加对象
Counter counter = new Counter();
// 创建两个线程对一个变量进时累加
// 线程1
Thread t1 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
counter.increase();
}
});
// 线程2
Thread t2 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
counter.increase();
}
});
// 启动线程
t1.start();
t2.start();
// 等待线程完成
t1.join();
t2.join();
// 查看运行结果
System.out.println("count = " + counter.count);
}
}
// 专门用来累加的类
class Counter {
// 初始值是0
public int count = 0;
/**
* 累加方法
*/
public void increase () {
count++;
}
}
//count = 68419
程序运行结果与预期值不一致,而且是一个错误的结果,而且我们的逻辑是正确的,这个现象所表现的问题称为线程安全问题
2.线程不安全的原因
1.线程是抢占式执行的(执行顺序是随机的)
由于线程地执行顺序无法人为控制,抢占式执行是造成线程安全问题的主要原因,而且我i们解决不了,完全是CPU自己调度,而且和CPU内核数有关
2.多个线程同时修改了同一个变量
多个线程修改同一个变量,会出现线程安全问题
多个线程修改不同变量,不会出行线程安全问题
一个线程修改一个变量,不会出现线程安全问题
3.原子性
要么全部执行,要么全部不执行
写的count++对应多条CPU指令
1.从内存或寄存器中读取count的值 LOAD
2.执行自增 ADD
3.把计算结果写回寄存器中 STORE
CPU执行指令,和代码没关系
由于执行CPU指令不是原子性的,导致这三条指令没有执行完就被CPU调度走了
另外的线程加载到一个原始值
当两个线程分别自增完成后,把值写回内存时发生覆盖现象
4.内存可见性
1.Java线程首先是从主内存读取变量的值到自己工作内存
2.每个线程都有自己的工作内存,且工作内存间是隔离的
3.线程在自己的工作内存中把自己的值修改完成之后再把修改后的值写回主内存
以上执行的count++操作,由于是两个线程在在执行,每个线程都有自己的工作内存,且相互之间不可见,最终导致了线程安全问题
工作内存与线程之间是一一对应的(这是JVM规定的)
外存(磁盘)-->内存(运行过程被加载到内存)--> 寄存器(封装到CPU中)
工作内存是JAVA层面对物理层面的关于程序所使用的到了寄存器的抽象
如果通过某种方式让线程之间可以相互通信,称之为内存可见性
5.指令重排序(有序性)
我们写的代码在编译之后可能会与代码对应的指令执行顺序不同,这个过程就是指令重排序
JVM层面可能会重排, CPU执行指令时也可以重排
指令重排序必须要保证程序运行的结果是正确的 单线程的环境里是没有任何问题的 指令重排序在逻辑上互不影响
二、解决线程不安全的问题
事务的隔离级别是通过锁和MVCC机制保证的
1.锁的概念
线程A拿到了锁,别的线程如果执行被锁住的代码,必须要等到线程A释放锁,如果线程A没有释放锁,那么别的线程只能阻塞等待,这个状态就是BLOCK
先拿锁 --> 执行代码 --> 释放锁 --> 下一个线程再拿锁...
2.synchronized
可以为方法加锁也 可以为代码加锁
只解决原子性问题,它所修改的代码有并行变成了串行
public class Text01 {
public static void main(String[] args) throws InterruptedException {
// 初始化累加对象
Counter counter = new Counter();
// 创建两个线程对一个变量进时累加
// 线程1
Thread t1 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
counter.increase();
}
});
// 线程2
Thread t2 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
counter.increase();
}
});
// 启动线程
t1.start();
t2.start();
// 等待线程完成
t1.join();
t2.join();
// 查看运行结果
System.out.println("count = " + counter.count);
}
}
// 专门用来累加的类
class Counter {
// 初始值是0
public int count = 0;
/**
* 累加方法
*/
public synchronized void increase () {
count++;
}
}
//count = 100000
t1先获得了锁,执行方法, 方法执行完成之后其它线程在获取锁
这样的情况是一个单线程运行状态
是把多线程转成了单线程,从而解决线程安全问题
解决方法单线程的执行问题,可以修改代码块 把对共享变量的修改加锁执行
由于线程在执行逻辑之前要拿到锁,当拿到锁时,上一个线程已经执行完了所有的指令,并把修改的值刷回了主内存,当前线程读到了永远都是上一个线程修改后的值
t1释放锁之后,也有可能第二次循环时t1先于t2拿到锁,因为线程时抢占式执行的
3.synchronized的特性
1.保证了原子性(通过加锁来实现)
2.保证了内存有序性(通过串行执行实现)
3.不保证有序性
4.关于synchronized
1.被synchronized修饰的代码会变成串行执行
2.synchronized可以修饰方法,也可以修饰代码块
3.被synchronized修饰的代码并不是一次性在CPU执行完,而是中途可能被CPU调度走,当所有指令执行完成之后才会释放锁
4.只给一个线程加锁,也会出现线程不安全问题
public class Text01 {
public static void main(String[] args) throws InterruptedException {
// 初始化累加对象
Counter01 counter = new Counter01();
// 创建两个线程对一个变量进时累加
// 线程1
Thread t1 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
counter.increase();
}
});
// 线程2
Thread t2 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
counter.increase1();
}
});
// 启动线程
t1.start();
t2.start();
// 等待线程完成
t1.join();
t2.join();
// 查看运行结果
System.out.println("count = " + counter.count);
}
}
// 专门用来累加的类
class Counter01 {
// 初始值是0
public int count = 0;
/**
* 累加方法
*/
public synchronized void increase () {
count++;
}
public void increase1 () {
count++;
}
}
//count = 81072
线程获取锁:
1.如果只有与一个线程A,那么直接可以获取锁,没有锁竞争
2.线程A,B共同抢一把锁的是时候,存在锁竞争,谁先拿到就先执行自己的逻辑,另一个线程阻塞等待,等到持有锁的线程释放所之后,再参与竞争锁
3.线程A,B竞争的不是同一把所的时候,他们没有竞争关系
5.使用单独的锁对象
public class Text02 {
public static void main(String[] args) throws InterruptedException {
Counter02 counter = new Counter02();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
counter.increase();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
counter.increase();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("count:" + counter.count);
}
}
class Counter02 {
// 初始值为0
public static int count = 0;
// 单独定义一个对象作为锁对象用
Object locker = new Object();
/**
* 累加方法
*
*/
public void increase () {
// 只定义锁代码块
synchronized (locker) {
count++;
}
}
}
// count : 100000
Counter中有一个locker,每创建一个counter都会初始化一个对象内部的成员变量locker
/**
* 在多个实例中在使用锁对象
*/
public class Text03 {
public static void main(String[] args) throws InterruptedException {
Counter03 counter1 = new Counter03();
Counter03 counter2 = new Counter03();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
counter1.increase();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
counter2.increase();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("count:" + counter1.count);
}
}
class Counter03 {
// 初始值为0
public static int count = 0;
// 单独定义一个对象作为锁对象用
Object locker = new Object();
/**
* 累加方法
*
*/
public void increase () {
// 只定义锁代码块
synchronized (locker) {
count++;
}
}
}
//count:95487
每个counter中都有一个locker 两个线程的锁对象是不同的,不存在锁竞争关系
/**
* 单个实例中,创建两个方法,使用同一个锁对象
*/
public class Text04 {
public static void main(String[] args) throws InterruptedException {
Counter04 counter = new Counter04();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
counter.increase();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
counter.increase1();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("count:" + counter.count);
}
}
class Counter04 {
// 初始值为0
public static int count = 0;
// 单独定义一个对象作为锁对象用
Object locker = new Object();
/**
* 累加方法
*
*/
public void increase () {
// 只定义锁代码块
synchronized (locker) {
count++;
}
}
public void increase1 () {
// 只定义锁代码块
synchronized (locker) {
count++;
}
}
}
//count:100000
locker是同一个对象,会产生锁竞争关系
/**
* 使用静态全局变量
*/
public class Text05 {
public static void main(String[] args) throws InterruptedException {
Counter05 counter = new Counter05();
Counter05 counter1 = new Counter05();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
counter.increase();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
counter1.increase();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("count:" + counter.count);
}
}
class Counter05 {
// 初始值为0
public static int count = 0;
// 全局变量,属于类对象
static Object locker = new Object();
/**
* 累加方法
*
*/
public void increase () {
// 只定义锁代码块
synchronized (locker) {
count++;
}
}
}
//count:100000
类对象是全局唯一,产生锁竞争
public class Text06 {
public static void main(String[] args) throws InterruptedException {
Counter05 counter = new Counter05();
Counter05 counter1 = new Counter05();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
counter.increase();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
counter1.increase();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("count:" + counter.count);
}
}
class Counter06 {
// 初始值为0
public static int count = 0;
/**
* 累加方法
*
*/
public void increase () {
// 只定义锁代码块
synchronized (Counter06.class) {
count++;
}
}
}
//count:100000
public class Text07 {
public static void main(String[] args) throws InterruptedException {
Counter05 counter = new Counter05();
Counter05 counter1 = new Counter05();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
counter.increase();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
counter1.increase();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("count:" + counter.count);
}
}
class Counter07 {
// 初始值为0
public static int count = 0;
/**
* 累加方法
*
*/
public void increase () {
// 只定义锁代码块
synchronized (String.class) {
count++;
}
}
}
//count:100000
任何一个对象都可以作为锁对象
只能多个线程访问的锁对象是同一个,那么他们就存在竞争关系,否则就没有竞争关系
6.synchronized使用示例
7.synchronized - 监视器锁monitor lock
7.1synchronized的特性
1.互斥
一个线程获取了锁之后,其他线程必须要阻塞等待
只有当持有锁的线程把锁释放了之后,所有的线程再去竞争锁
2.可重入
package demo3;
public class Text1 {
public static void main(String[] args) throws InterruptedException {
Counter1 counter1 = new Counter1();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
counter1.increase();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
counter1.increase();
}
});
t1.start();
t2.start();
t1.join();;
t2.join();
System.out.println("count = " + counter1.count);
}
}
class Counter1 {
public static int count = 0;
/**
* 累加方法
*/
public synchronized void increase () {
increase1();
}
private synchronized void increase1() {
increase2();
}
private void increase2() {
synchronized (this) {
count++;
}
}
}
// count = 10000
3.可见性
通过结果来看达到内存可见性的目的,但是是通过原子性来实现的
8.volatile 关键字
package demo3;
import java.util.Scanner;
/**
* 创建两个线程
* 1. 第一个线程, 不停的执行自己的任务
* 2. 第二个线程,输入一个停止标识,使第一个线程退出
*/
public class Text2 {
// 退出标识
static int flag = 0;
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "线程启动...");
while (flag == 0) {
// 不停的去循环, 处理任务
}
System.out.println(Thread.currentThread().getName() + "线程退出...");
}, "t1");
// 启动线程
t1.start();
// 输入停止标识
Thread t2 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "'线程启动...");
Scanner scanner = new Scanner(System.in);
System.out.println("请输入一个整数:>");
flag = scanner.nextInt();
System.out.println(Thread.currentThread().getName() + "线程退出...");
}, "t2");
// 启动线程
t2.start();
}
}
/*
t2'线程启动...
t1线程启动...
请输入一个整数:>
1
t2线程退出...
*/
t2线程正常结束,并且修改了flag变量的值
但是t1线程没有结束,整个进程页没有结束
结果不及预期,线程安全问题产生
package demo3;
import java.util.Scanner;
/**
* 创建两个线程
* 1. 第一个线程, 不停的执行自己的任务
* 2. 第二个线程,输入一个停止标识,使第一个线程退出
*/
public class Text2 {
// 退出标识
static volatile int flag = 0;
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "线程启动...");
while (flag == 0) {
// 不停的去循环, 处理任务
}
System.out.println(Thread.currentThread().getName() + "线程退出...");
}, "t1");
// 启动线程
t1.start();
// 输入停止标识
Thread t2 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "'线程启动...");
Scanner scanner = new Scanner(System.in);
System.out.println("请输入一个整数:>");
flag = scanner.nextInt();
System.out.println(Thread.currentThread().getName() + "线程退出...");
}, "t2");
// 启动线程
t2.start();
}
}
/*
t2'线程启动...
t1线程启动...
请输入一个整数:>
1
t2线程退出...
t1线程退出...
*/
解决了内存可见性
解决了有序性
不保证原子性
多个线程之间涉及的共享变量,如果只存在修改的逻辑,只管加volatile
面试题:JMM如何实现原子性,可见性,有序性
synchronized实现了原子性,由于是串行从而也实现了可见性
volatile真正实现了内存可见性,有序性(使用了内存屏障)