并发编程[4]_synchronized关键字
1. 概念
同步代码块(Synchronized Block)是Java提供的一种内置锁机制。用关键字synchronized来修饰的方法,就是同步代码块。线程在进入同步代码块的时候会自动获取锁,退出代码块时会自动释放锁。内置锁是互斥锁,最多只有一个线程能持有,被锁保护的同步代码块会以原子方式执行,保证了多个线程在执行该代码块时不会互相干扰。
synchronized关键字可以使用在代码块和方法中。
根据锁不同的位置,被锁的对象是不同的
锁的修饰部分 | 锁内容 | 说明 | 例子 |
---|---|---|---|
普通实例方法 | 类的实例对象 | 等同于this锁 | synchronized void method(){ ...... } |
静态方法 | 类对象 | 等同于锁A.class | static synchronized void method(){ ...... } |
代码块 | 任意对象锁 | synchronized(任意对象) | synchronized(任意对象){ ...... } |
2. 不加锁的情况
新建Count类,新建两个线程对同一count实例的num变量进行一万次加一和一万次减一的修改。
例子1:
public class Test1 {
private static final Logger log = LoggerFactory.getLogger(Test1.class);
public static void main(String[] args) throws InterruptedException {
Count myCount = new Count();
Thread thread1 = new Thread("thread1") {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
myCount.increase();
}
}
};
Thread thread2 = new Thread("thread2") {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
myCount.decrease();
}
}
};
thread1.start();
thread2.start();
// 保证两个线程都执行完再输出
thread1.join();
thread2.join();
log.debug("{}", myCount.getNum());
}
// 自定义Count类
static class Count {
private int num;
public void increase() {
this.num++;
}
public void decrease() {
this.num--;
}
public int getNum() {
return num;
}
}
}
运行结果:
2021-04-18 11:11:13.100 [main] - -149
[注:运行结果跟cpu当时的调度有关,所以每次运行结果可能不同]
单线程情况下加一万再减一万的结果肯定是0,但是多线程情况下,结果就不一定为0了。
情况分析:
- 线程1获取cpu时间片时,要执行+1的操作,首先取值num,此时num为x,计算加一结果为x+1,在要将结果x+1写入到num时,此时cpu时间片恰好用完,线程2执行代码
- 线程2获取cpu时间片时,要执行-1的操作,首先取值num,因为线程1的+1操作的结果还未赋值给num,所以num此时也为x,计算结果x-1,x-1赋值给num,此时cpu时间片用完,线程1执行代码
- 线程1将x+1写入到num
这样的情况,经过线程1加1,线程2减1,结果不是0却是x+1。
同样的,根据不同的线程调度方法结果也会出现x-1,和0的这三种情况。
解决这种情况,可以用上synchronized关键字为代码加锁。
解决办法1:3. synchronized 修饰普通方法
解决办法2:5. synchronized 修饰代码块
3. synchronized 修饰普通方法
将例子1中Count类中的方法做调整,给三个方法同时加锁。
static class Count {
private int num;
public synchronized void increase() {
this.num++;
}
public synchronized void decrease() {
this.num--;
}
public synchronized int getNum() {
return num;
}
}
此时锁是加在类的实例对象上的(main 方法中的 myCount),相当于:
static class Count {
private int num;
public void increase() {
synchronized (this){
this.num++;
}
}
public void decrease() {
synchronized (this){
this.num--;
}
}
public synchronized int getNum() {
synchronized (this){
return num;
}
}
}
运行结果:
2021-04-18 11:24:52.542 [main] - 0
例子2:
新建一个Display类,创建两个实例对象,给print方法加synchronized关键字,运行
public class Test1 {
private static final Logger log = LoggerFactory.getLogger(Test1.class);
public static void main(String[] args) throws InterruptedException {
Display myDisplay1 = new Display();
Display myDisplay2 = new Display();
Thread thread1 = new Thread("thread1") {
@Override
public void run() {
myDisplay1.print();
}
};
Thread thread2 = new Thread("thread2") {
@Override
public void run() {
myDisplay2.print();
}
};
thread1.start();
thread2.start();
thread1.join();
thread2.join();
}
static class Display {
public synchronized void print(){
try {
log.debug("print start...");
Thread.sleep(5000);
log.debug("print end...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果:
2021-04-18 11:41:04.698 [thread1] - print start...
2021-04-18 11:41:04.698 [thread2] - print start...
2021-04-18 11:41:09.699 [thread1] - print end...
2021-04-18 11:41:09.699 [thread2] - print end...
从输出的时间点可以看出,thread1和thread2两个线程都是没有出现互相阻塞的情况,因为synchronized修饰的是普通方法,所以锁的对象分别是myDisplay1和myDisplay2两个实例。
4. synchronized 修饰静态方法
修改例子2,将print方法改为静态方法
static class Display {
public synchronized static void print(){
try {
log.debug("print start...");
Thread.sleep(5000);
log.debug("print end...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
2021-04-18 11:45:19.601 [thread1] - print start...
2021-04-18 11:45:24.602 [thread1] - print end...
2021-04-18 11:45:24.602 [thread2] - print start...
2021-04-18 11:45:29.603 [thread2] - print end...
从运行结果可以看出,在thread1调用完成print方法后,thread2才执行print方法。
因为synchronized 修饰静态方法,所得是整个Display类,相当于
static class Display {
public static void print(){
synchronized (Display.class){
try {
log.debug("print start...");
Thread.sleep(5000);
log.debug("print end...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
5. synchronized 修饰代码块
synchronized 修饰代码块,obj为具体的锁的对象
synchronized (obj){
......
}
我们可以用synchronized修饰代码块,解决例子1出现的问题
public class Test1 {
private static final Logger log = LoggerFactory.getLogger(Test1.class);
public static void main(String[] args) throws InterruptedException {
Count myCount = new Count();
// 新建一个对象,待会加锁
Object obj = new Object();
Thread thread1 = new Thread("thread1") {
@Override
public void run() {
// 这边演示锁obj对象,实际上不用新建obj对象,直接锁myConut更方便
synchronized (obj){
for (int i = 0; i < 10000; i++) {
myCount.increase();
}
}
}
};
Thread thread2 = new Thread("thread2") {
@Override
public void run() {
synchronized (obj){
for (int i = 0; i < 10000; i++) {
myCount.decrease();
}
}
}
};
thread1.start();
thread2.start();
// 保证两个线程都执行完再输出
thread1.join();
thread2.join();
log.debug("{}", myCount.getNum());
}
static class Count {
......
}
}
运行结果:
2021-04-18 12:04:15.201 [main] - 0