本文章主要对synchronized
锁机制进行讲述
本人第一次写博客,如有不足,还请大家包含,谢谢~~
那么接下来就开始吧~
当多个线程同时进行时,线程的调度是由操作系统决定的,也就是说各个线程执行的先后顺序是无法确定的
如果多个线程同时读写共享变量,读取的数据就会不一致:
public class Main {
public static void main(String[] args) throws Exception {
AddThread add = new AddThread();
DevThread dev = new DevThread();
add.start();
dev.start();
add.join();
dev.join();
System.out.println("count = " + Counter.count);
}
}
class Counter{
public static int count = 0;
}
class AddThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
Counter.count++;
}
}
}
class DevThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
Counter.count--;
}
}
}
当然,这里的count
从逻辑上来讲应该是0 的
但多次运行之后可以发现,有时结果会是0,也会出现-259,-1228,422 … 之类很奇怪的数
要使结果正确,必须保证是原子操作
原子操作是指不能被中断的一个或一系列的操作
举个例子:
i = i + 1;
这样一个简单的语句其实对应3 个操作:
i
的值例如上述程序执行时可能会像这样:
这样count
的值会因为线程执行时被操作系统中断而使得最终结果发生变化
同时还需要保证可见性:
可见性:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看到被修改的值
通过加锁和解锁的操作,可以保证某一线程执行时,其他线程处于等待状态:
使用synchronized
关键字对一个对象进行加锁,即可保证代码块在任意时刻最多只有一个线程能执行:
synchronized(lock) {
i = i + 1;
}
在使用synchronized
的时候,在代码块结束的时候都会正确释放锁,不必担心是否会出现异常
当然,这里的lock
可以是客户端锁(随便用一个对象作为锁,不推荐 --> 代码逻辑容易混乱,也不利于封装):
public static final Object lock = new Object();
...
synchronized(lock) {
...
}
也可以是内置锁(this
即当前实例,每一个对象都有一个内置锁):
synchronized(this) {
...
}
如果锁住的是this
实例时,等同于用synchronized
修饰这个方法(这个方法就是同步方法,表示整个方法都必须用this
加锁):
public synchronized void test() { // 锁住this
...
} // 解锁
那如果这个方法是静态方法呢?静态方法是没有this
实例的
这时其实锁住的是该类的class
实例(类锁),相当于:
public class Student {
public static void test() {
synchronized(Student.class) {
...
}
}
}
于是,通过使用synchronized
,最开始遇到的问题就可以得到解决了:
class AddThread extends Thread{
@Override
public synchronized void run() {
for (int i = 0; i < 10000; i++) {
Counter.count++;
}
}
}
class DevThread extends Thread{
@Override
public synchronized void run() {
for (int i = 0; i < 10000; i++) {
Counter.count--;
}
}
}
用synchronized
去修饰着两个方法,结果就会正确了~
使用synchronized
的时候,获取到的是哪个锁很重要,锁对象如果不对,代码逻辑就不对
另外还需要注意一些不需要synchronized
的操作
JVM 规范定义了几种原子操作:
long
和double
除外)赋值(多行赋值语句必须保证是同步操作)这里long
和double
是64 位数据,但在x64 平台的JVM 是把long
和double
的赋值作为原子操作实现的,所以这个问题一般忽略
使用synchronized
解决了多线程同步访问共享变量的问题
但因为synchronized
代码块无法并发执行,加锁和解锁需要消耗一定的时间,它也带来了性能的下降
如果文章有什么不足,欢迎大家评论指出;如果本文对大家有什么帮助的话,也希望能点个赞,谢谢大家~