线程安全问题的产生前提:多个线程并发访问共享数据。
解决方案:将并发访问转换为串行访问,锁就是按照这种思路保证线程安全。
锁可以实现对共享数据的安全访问,保障线程的原子性,可见性与有序性。
注意:使用锁保障线程的安全性,必须满足下面条件
- 必须使用同一个锁
- 即使是读取共享数据的线程,也需要上同步锁
一个线程持有该锁的时候,能够再次申请该锁,为可重入,否则为不可重入
java中内部锁属于非公平锁,显示锁及支持公平锁,又支持非公平锁
一个锁可以保护的共享数据的数量大小,称为锁的粒度。
粒度粗,共享数据量大;粒度细,反之
java中的每个对象都有一个与之关联的内部锁,也称为监视器(Monitor),是一种排它锁,可以保障原子性,可见性,有序性。
内部锁是通过synchronized关键字实现
package com.day04;
/**
* @author: Xu TaoSong
* @date: 2021/2/3 20:37
* @description: TODO
* @modifiedBy:
*/
public class Test01 {
public static void main(String[] args) {
Test01 test = new Test01();
new Thread(new Runnable() {
@Override
public void run() {
test.mm();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
test.mm();
}
}).start();
}
public void mm(){
synchronized (this) {
for (int i = 0; i < 99; i++) {
System.out.println(Thread.currentThread().getName() + "->" + i);
}
}
}
}
在代码块中,可以使用一个常量作为锁对象
public static final Object o = new Object();
public void mm(){
synchronized (o) {
for (int i = 0; i < 99; i++) {
System.out.println(Thread.currentThread().getName() + "->" + i);
}
}
}
package com.day04;
/**
* @author: Xu TaoSong
* @date: 2021/2/3 20:37
* @description: TODO
* @modifiedBy:
*/
public class Test01 {
public static void main(String[] args) {
Test01 test = new Test01();
new Thread(new Runnable() {
@Override
public void run() {
test.mm();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
test.mm();
}
}).start();
}
synchronized public void mm(){
for(int i = 0;i<99;i++){
System.out.println(Thread.currentThread().getName()+"->"+i);
}
}
}
修饰代码块的锁可以自定义指定
修饰实例方法的锁是当前对象this
修饰静态方法的锁是当前类的class,称为“类锁”。
对方法上加synchronized比较笼统,使用代码块可以更加灵活的配置资源的并发。
同步方法,锁的粒度粗,执行效率比较低
同步代码块,锁的粒度细,执行效率比较高
根据实际情况来配置
使得变量在多个线程之间可见,解决变量的可见性
将会强制线程在公共内存中读取变量的值,而不是从工作内存中读取。
package com.volitale;
/**
* @author: Xu TaoSong
* @date: 2021/2/5 14:53
* @description: TODO
* @modifiedBy:
*/
public class Test02 {
public static void main(String[] args) {
for(int i = 0;i<10;i++){
new Thread(new MyThread()).start();
}
}
static class MyThread extends Thread{
volatile public static int count = 0;
public static void add(){
for(int i = 0;i<1000;i++){
count++;
}
System.out.println(Thread.currentThread().getName()+"count= " +count);
}
@Override
public void run() {
super.run();
add();
}
}
}
CAS(Compare And Swap)是由硬件实现的。
CAS可以将read-modify-write这些的操作转换为原子操作。
在把数据更新到主内存之前,再次读取主内存中的值,如果当前值和期望值一致,才进行更新操作。否则,就撤销该次操作,或者重新提交。
优点:提高了并发性能。
缺点:
自旋:表示CAS更新失败,重复提交操作