文章目录
系列文章
1.锁的应用场景
先来看一个例子,线程A、线程B或者多个线程同时操作count,进行++操作,结果有可能和预期不同
测试代码
在正常情况下,以下代码的结果应该为1000,循环1001次,每次+1;
public class AtomicDemo {
private static int count = 0;
public static void incr() {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i <= 1000; i++) {
new Thread(() -> {
AtomicDemo2.incr();
}).start();
}
Thread.sleep(5000);
System.out.println(count);
}
}
实际运行结果(而且每次都可能不一样):
原因
线程A对count++之后,count的值需要同步到共享资源(主内存)中,然后线程B再去获取共享资源中的count,这样得到的值才是正确的
解决方法
在线程访问共享资源之前加一把锁
synchronized
线程A先获得这把锁,操作count++,在count值同步到共享资源(主内存)过程中,线程B如果想要操作count,也需要获得这把锁才能操作,必须要等线程A释放锁,此时线程B处于阻塞状态,当线程A将值同步到共享资源(主内存)中后,释放锁,线程B拿到锁才能操作count。
2.锁的粒度
synchronized 有三种使用方法
- 1.修饰实例方法
- 2.修饰静态方法
- 3.修饰代码块
2.1修饰实例方法
对类的实例方法加锁
public synchronized void demo() {
//TODO
}
测试代码
public class AtomicDemo3 {
private static int count = 0;
public synchronized void incr() {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
}
public static void main(String[] args) throws InterruptedException {
AtomicDemo3 demo3 = new AtomicDemo3();
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
demo3.incr();
}).start();
}
Thread.sleep(5000);
System.out.println(count);
}
}
结果
锁粒度
只有一个对象demo3,在这个实例内,incr()
方法是加锁同步的,不会有线程不安全的情况
错误演示
public class AtomicDemo3 {
private static int count = 0;
public synchronized void incr() {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
}
public static void main(String[] args) throws InterruptedException {
AtomicDemo3 demo3 = new AtomicDemo3();
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
new AtomicDemo3().incr();
}).start();
}
Thread.sleep(5000);
System.out.println(count);
}
}
结果:
这里每开启一个Thread线程,就会新new一个实例,在不同实例内,方法还是不安全的
2.2修饰静态方法
对类的静态方法加锁
public synchronized static void demo3() {
//TODO
}
和2.1例子的区别只是
方法前增加了static
public synchronized static void incr() {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
}
几种调用方式结果一样:
锁粒度
全局级别和2.3.1 的加锁方式等价
2.3修饰代码块
对类的方法中代码块加锁
2.3.1 全局级别
public void demo4() {
//TODO
synchronized (SyncDemo.class) {
//保护存在线程安全的代码
}
//TODO
}
锁粒度
这种类型的加锁和synchronized (obj)
中的obj有关系,SyncDemo.class
存放在方法区中
所以是全局级别,但是在代码块中可以进行粒度的控制(只对存在线程安全的代码进行加锁)
2.3.2 实例内
public void demo4() {
//TODO
synchronized (this) {
//保护存在线程安全的代码
}
//TODO
}
this指向这个类的实例
锁粒度
在这个实例内,demo4()
方法是加锁同步的
2.3.3 lock的实例内
public class SyncDemo {
Object lock = new Object;
public void demo2() {
//TODO
synchronized (lock) {
//保护存在线程安全的代码
}
//TODO
}
}
lock对象,看这个对象的使用范围
锁粒度
在这个实例内,demo2()
方法是加锁同步的
2.3.4 lock的跨实例
public class AtomicDemo4 {
Object lock;
public AtomicDemo4(Object lock) {
this.lock = lock;
}
public void demo2() {
//TODO
synchronized (lock) {
//保护存在线程安全的代码
}
//TODO
}
public static void main(String[] args) {
Object lock = new Object();
SyncDemo sd1 = new SyncDemo(lock);
SyncDemo sd2 = new SyncDemo(lock);
new Thread(() -> {
sd1.demo2();
}).start();
new Thread(() -> {
sd2.demo2();
}).start();
}
}
锁粒度
在sd1 和sd2两个实例内,demo2()
方法是加锁同步的
3.参考
腾讯课堂->咕泡学院->mic老师->并发编程基础