目录
根据上篇文章多线程基础(二)——线程安全我们了解到在实际的开发中,会出现线程不安全现象,但是通过加锁就可以避免线程不安全,主要应用关键字Synchronized来解决。
1.解决线程不安全
class Counter02 {
public int count = 0;
synchronized void increase() {
count++;
}
}
public class Demo02 {
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(counter.count);
}
}
2.关键字Synchronized特性
1.互斥
synchronized会起到互斥的作用,某个线程执行到某个对象的synchronized中时,此时如果其他线程也执行到同一个对象synchronized就会阻塞等待
- 进入synchronized修饰的代码块,相当于加锁
- 退出synchronized修饰的代码块,相当于解锁
synchronized用的锁是存在对象头里的
- 可以粗略理解成, 每个对象在内存中存储的时候, 都存有一块内存表示当前的 "锁定" 状态
- 如果当前是 "无人" 状态, 那么就可以使用, 使用时需要设为 "有人" 状态.
- 如果当前是 "有人" 状态, 那么其他人无法使用, 只能排队
注意:
- 上一个线程解锁之后, 下一个线程并不是立即就能获取到锁. 而是要靠操作系统来 "唤醒". 这也就是操作系统线程调度的一部分工作.
- 假设有 A B C 三个线程, 线程 A 先获取到锁, 然后 B 尝试获取锁, 然后 C 再尝试获取锁, 此时 B和 C 都在阻塞队列中排队等待. 但是当 A 释放锁之后, 虽然 B 比 C 先来的, 但是 B 不一定就能获取到锁, 而是和 C 重新竞争, 并不遵守先来后到的规则.
2.刷新内存
synchronized实现内存可见机制是通过原子性完成的
synchronized 的工作过程:
- 获得互斥锁
- 从主内存拷贝变量的最新副本到工作的内存
- 执行代码
- 将更改后的共享变量的值刷新到主内存
- 释放互斥锁
在一系列工作完成之后,下一个线程读到的值是上一个线程写入的最新值。
3.可重入
已经获得锁对象的线程针对当前锁加了多次锁,不产生互斥现象,这就是可重入特性
static class Counter {
public int count = 0;
synchronized void increase() {
count++;
}
synchronized void increase2() {
increase();
}
}
- increase 和 increase2 两个方法都加了 synchronized, 此处的 synchronized 都是针对 this 当前对象加锁的.
- 在调用 increase2 的时候, 先加了一次锁, 执行到 increase 的时候, 又加了一次锁. (上个锁还没释放, 相当于连续加两次锁)
但是此时代码正确。
在可重入锁的内部, 包含了 "线程持有者" 和 "计数器" 两个信息:
- 如果某个线程加锁的时候, 发现锁已经被人占用, 但是恰好占用的正是自己, 那么仍然可以继续获取到锁, 并让计数器自增.
- 解锁的时候计数器递减为 0 的时候, 才真正释放锁. (才能被别的线程获取到)
3.synchronized的使用
1.修饰实例方法
作用于当前实例加锁:
public synchronized void method(){ // todo }
此时加锁为锁定了整个方法的内容。
synchronied关键字不能继承,虽然可以定义方法,但是并不属于方法定义的一部分,因此不能被继承。如果父类方法使用synchronied修饰,而在子类方法中重写这个方法,此时是不同步的,必须显示的在子类方法中添加 synchronied关键字。还可以在子类调用父类的方法,此时子类方法中不同步,但是子类调用了父类的同步方法,因此子类方法相当于同步。
子类方法加上synchronied:
class Parent { public synchronized void method() { } } class Child extends Parent { public synchronized void method() { } }
子类方法调用父类方法:
class Parent { public synchronized void method() { } } class Child extends Parent { public void method() { super.method(); } }
注意:
- 在定义接口方法时不能使用synchronized关键字。
- 构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步。
2.修饰代码块
指定加锁对象,给给定对象加锁
锁当前对象:public void method(){ synchronized(this) { // todo } }
3.修饰静态方法
作用于当前类对象加锁:
public synchronized static void method() { //TODO }
4.锁对象
- 一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞,竞争同一把锁,线程安全
public class Demo_405 {
private static int num = 50000;
public static void main(String[] args) throws InterruptedException {
Counter06 counter = new Counter06();
// 创建线程完成自增
// 创建两个线程
Thread t1 = new Thread(() -> {
for (int i = 0; i < num; i++) {
// 这里调用了counter1的自增方法
counter.increase();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < num; i++) {
// 这里调用了counter2的自增方法
counter.increase();
}
});
// 启动线程
t1.start();
t2.start();
// 等待两个线程执行完成
t1.join();
t2.join();
// 获取自增后的count值
System.out.println("count结果 = " + counter.count);
}
}
class Counter06 {
public int count = 0;
/**
* 执行自增操作
*/
public void increase() {
// 指定自定义的锁对象
synchronized (this) {
count++;
}
}
}
- 没有明确的对象作为锁,可以创建一个特殊的对象作为锁,同一个对象使用同一把锁,线程安全
public class Demo_405 {
private static int num = 50000;
public static void main(String[] args) throws InterruptedException {
Counter06 counter = new Counter06();
// 创建线程完成自增
// 创建两个线程
Thread t1 = new Thread(() -> {
for (int i = 0; i < num; i++) {
// 这里调用了counter1的自增方法
counter.increase();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < num; i++) {
// 这里调用了counter2的自增方法
counter.increase();
}
});
// 启动线程
t1.start();
t2.start();
// 等待两个线程执行完成
t1.join();
t2.join();
// 获取自增后的count值
System.out.println("count结果 = " + counter.count);
}
}
class Counter06 {
public int count = 0;
// 自定义一个锁对象
Object locker = new Object();
/**
* 执行自增操作
*/
public void increase() {
// 指定自定义的锁对象
synchronized (locker) {
count++;
}
}
}
- 静态方法是属于类的而不属于对象的。同样的,synchronized修饰的静态方法锁定的是这个类的所有对象,线程安全
public class Demo_407 {
private static int num = 50000;
public static void main(String[] args) throws InterruptedException {
Counter07 counter = new Counter07();
Counter07 counter1 = new Counter07();
// 创建线程完成自增
// 创建两个线程
Thread t1 = new Thread(() -> {
for (int i = 0; i < num; i++) {
// 这里调用了counter1的自增方法
counter.increase();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < num; i++) {
// 这里调用了counter2的自增方法
counter.increase();
}
});
// 启动线程
t1.start();
t2.start();
// 等待两个线程执行完成
t1.join();
t2.join();
// 获取自增后的count值
System.out.println("count结果 = " + counter.count);
}
}
class Counter07 {
public static int count = 0;
// 自定义一个锁对象
// public Object locker = new Object();
/**
* 执行自增操作
*/
public synchronized void increase() {
// 指定自定义的锁对象
count++;
}
}
- 自定义一个静态锁对象,也是全局唯一,线程安全
public class Demo_408 {
private static int num = 50000;
public static void main(String[] args) throws InterruptedException {
Counter08 counter = new Counter08();
Counter08 counter1 = new Counter08();
counter.count = 1;
// System.out.println(counter.count);
// System.out.println(counter1.count);
// System.out.println(counter);
// System.out.println(counter1);
// System.out.println(counter.locker);
// System.out.println(counter1.locker);
// 创建线程完成自增
// 创建两个线程
Thread t1 = new Thread(() -> {
for (int i = 0; i < num; i++) {
// 这里调用了counter1的自增方法
counter.increase();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < num; i++) {
// 这里调用了counter2的自增方法
counter1.increase();
}
});
// 启动线程
t1.start();
t2.start();
// 等待两个线程执行完成
t1.join();
t2.join();
// 获取自增后的count值
System.out.println("count结果 = " + counter.count);
}
}
class Counter08 {
public static int count = 0;
// 自定义一个锁对象
public static Object locker = new Object();
/**
* 执行自增操作
*/
public void increase() {
// 指定自定义的锁对象
synchronized (locker) {
count++;
}
}
}
- 使用类对象持有的所信息,全局唯一,存在锁竞争,线程安全
public class Demo_410 {
private static int num = 50000;
public static void main(String[] args) throws InterruptedException {
Counter10 counter = new Counter10();
Counter10 counter1 = new Counter10();
// 创建线程完成自增
// 创建两个线程
Thread t1 = new Thread(() -> {
for (int i = 0; i < num; i++) {
// 这里调用了counter1的自增方法
counter.increase();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < num; i++) {
// 这里调用了counter2的自增方法
counter1.increase();
}
});
// 启动线程
t1.start();
t2.start();
// 等待两个线程执行完成
t1.join();
t2.join();
// 获取自增后的count值
System.out.println("count结果 = " + counter.count);
}
}
class Counter10 {
public static int count = 0;
public void increase() {
// 使用这个类对象持有锁信息
synchronized (Counter10.class) {
count++;
}
}
}
5.总结:
只有一个线程要获取锁,直接可以拿到,没有锁竞争
两个线程同时竞争一把锁,谁先拿到就执行自己的逻辑,另一个线程就是阻塞等待,等释放锁之后,再次竞争锁
两个线程竞争的不是同一把锁,两者没有竞争关系
要保证线程之间存在锁竞争,必须要使用相同的锁
synchronized可以修饰方法,可以修饰代码块:
修饰静态方法,用到的锁对象是类对象.class
修饰普通方法,用到的锁对象是当前的实例对象,new
修饰代码块,用到的锁对象是当前对象this new
在 synchronized(locker){},此处locker可以为任意对象