多线程(初阶四:synchronized关键字)

目录

一、加锁的目的

二、加锁和解锁

三、加锁后是否会出现线程安全问题

1、两个线程,针对不同对象加锁

2、一个线程加锁,一个线程不加锁

3、针对加锁操作的一些混淆理解

(1)多个线程调用同一个类的方法,对其方法里面的变量加锁

(2)Test类里的add方法里面,加锁的对象换成Test.class

四、联系其他的相关知识点

[1、StringBuffer 和 StringBuilder](#1、StringBuffer 和 StringBuilder)

2、C++加锁、解锁和java的区别


一、加锁的目的

因为加锁具有互斥 的特性,给一段代码加锁,当运行这段代码时,这里的代码在系统上的指令就会就会被打包在一起,等这些指令,执行完了,其他的指令操作才能进行。而这,也是加锁的目的:把几个操作打包成一个原子的操作。


二、加锁和解锁

我们想让一个变量自增10_0000次,用两个线程来实现这一操作,分工各一半,

没有加锁的操作,是有线程问题的,因为两个线程修改同一个变量的原因。代码如下:

java 复制代码
public class ThreadDemo1 {
    private static int count = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                count++;
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                count++;
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count);
    }
}

执行效果:

count并不是我们预期的10_0000。

当我们给count++加上锁操作后的代码:

java 复制代码
public class ThreadDemo1 {
    private static int count = 0;
    public static void main(String[] args) throws InterruptedException {
        Object locker = new Object();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                synchronized(locker) {
                    count++;
                }
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                synchronized(locker){
                    count++;
                }
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count);
    }
}

执行效果:

是我们预期的效果。

加锁的最核心规则: 对于一个线程,针对一个对象进行加锁,但也有其他线程,也尝试对这个对象进行加锁,就会产生阻塞,这时,我们就把这已现象称为锁竞争 / 锁冲突

例如下图,都是针对locker对象进行加锁,这时就会产生锁竞争

加锁和解锁的执行过程,针对上述代码,简单的画图展示一下:

也就是把t1先上锁,把t1的这几个操作打包成一个原子,执行完它们才能执行t2。

注意: 给加锁的对象是任意的引用类型都可以的,我们也可以随便起个对象,但是要记住加锁的核心,两个线程之间加锁的对象,是否是同一个对象,是同一个对象,就会产生竞争,反之则不会。


三、加锁后是否会出现线程安全问题

1、两个线程,针对不同对象加锁

如下下代码,加锁用的不是同一个对象,则还是会存在线程安全问题

java 复制代码
public class ThreadDemo1 {
    private static int count = 0;
    public static void main(String[] args) throws InterruptedException {
        Object locker1 = new Object();
        Object locker2 = new Object();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                synchronized(locker1) {
                    count++;
                }
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                synchronized(locker2){
                    count++;
                }
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count);
    }
}

执行结果:

我们可以看到,结果和我们预期的10_0000不同,所以,还是存在线程安全问题。

2、一个线程加锁,一个线程不加锁

如下代码,和上面代码差不多,做一些小小的改动

java 复制代码
public class ThreadDemo1 {
    private static int count = 0;
    public static void main(String[] args) throws InterruptedException {
        Object locker1 = new Object();
//        Object locker2 = new Object();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                synchronized(locker1) {
                    count++;
                }
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                count++;
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count);
    }
}

执行结果:

可以看到,和我们预期的结果不同,所以还是存在线程安全问题。

3、针对加锁操作的一些混淆理解

(1)多个线程调用同一个类的方法,对其方法里面的变量加锁

还是之前的代码模板,不过做了一些改动,把count放到Test t 对象中,在这里面count++,并且对其加锁,加锁对象是 this,其他线程再来调用Test中的方法。

java 复制代码
class Test {
    public int count = 0;
    public void add() {
        synchronized(this) {
            count++;
        }
    }
}
public class ThreadDemo2 {
    public static void main(String[] args) throws InterruptedException {
        Test t = new Test();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                t.add();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                t.add();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(t.count);
    }
}

执行效果:

和我们的预期效果一样,这说明,这种情况是线程安全的。

解释: 要解决这种情况的线程安全问题,核心还是上面所说的,看加锁是不是同一个对象,这里的Test类,add方法里对其加锁引用的对象是this,也就是当前Test类的实例对象,所有两个线程调用者方法的时候会产生锁竞争,结果也就可以达到我们的预期效果了。

(2)Test类里的add方法里面,加锁的对象换成Test.class

模板和之前差不多,只有一点小改动。代码如下:

java 复制代码
class Test {
    public int count = 0;
    public void add() {
        synchronized(Test.class) {
            count++;
        }
    }
}
public class ThreadDemo2 {
    public static void main(String[] args) throws InterruptedException {
        Test t = new Test();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                t.add();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                t.add();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(t.count);
    }
}

执行结果:

结果和我们预期的原因,是线程安全的情况。

解释: 这里的 Test.class 是类对象(反射那一块的内容),而 t1 和 t2 拿到的都是同一个对象,就会有锁竞争,还是能保障线程安全的。

(3)还可以把synchronized加到方法上

如图:

不过比较少用。


四、联系其他的相关知识点

1、StringBuffer 和 StringBuilder

StringBuffer是线程安全的,就是在一些关键方法上,加上了synchronized;StringBuilder不是线程安全的,就是在一些关键方法上,没加synchronized。

其实通俗的说是不是线程安全,并不严谨,例如上面的代码例子,一个线程加锁,一个线程不加锁,或者两个线程给不同的对象加锁,任然是线程不安全的。具体还是要看代码怎么写。

2、C++加锁、解锁和java的区别

C++: 它的加锁和解锁和java是不同的,在C++里,加锁:locker.lock() 解锁:locker.unlock(),他们的加锁和解锁是分开执行的,C++这种写法可能导致程序猿忘记调用unlock,或者unlock没执行到,这时就会产生很严重的bug,没解锁,其他加锁用和它一样对象的线程,就会一直等待。

java: 它的是使用synchronized方法进行加锁,解锁的,这些操作已经打包好了,当synchronized代码块执行完后,就会自动释放锁,就不会有忘记或者没的解锁这种情况。

相关推荐
Scott9999HH10 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
腻害兔10 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
码智社11 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海11 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖12 小时前
JDK 26 新特性详解
java·开发语言
马优晨12 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区14 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大15 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai15 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
维天说15 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json