您的位置:首页 > 编程语言 > Java开发

java synchronized关键字的用法

2017-11-03 15:16 531 查看

背景

最近在工作遇到一种用来校验的资源要定时更新的情况,为了保证在资源更新的时候不被其他的线程所访问,
就学习了synchronized 的同步块来解决这个功能。

原理

在java中,每一个对象都有一把内置锁,当程序中的某一块代码被同步块包起来的时候(synchronized(this){...}),相当于电脑用this指向的对象的内置锁把这块代码锁起来了,只有拥有能解开着这把锁钥匙的线程才能进入到同步块,其他的线程只能在同步块外面排队,只有等拥有钥匙的人执行完同步块归还钥匙的时候,电脑在把钥匙随机分配给外面等待的一个线程。
根据锁的对象不同可以分为两种:对象锁和类锁,对象锁指的是java中的实例对象,类锁指的是Class对象。但归根结底还是一个对象对应一把内置锁。

实例

1.没有加锁在多线程情况下面可能出现的问题
package com.fingard.sych;
public class MyThread implements Runnable {
private Account account;

public MyThread(Account account) {
this.account = account;
}
public void run() {
account.draw(50000);
}
}
class Account{
private int balance;

public Account(int balance) {
this.balance = balance;
}

public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
public void save(int num){
balance+=num;
System.out.println("账户余额:"+balance);
}
public void draw(int num){
if(balance>=num){
balance-=num;
System.out.println(Thread.currentThread().getName()+
"取走了"+num+"元,账户余额:"+balance);
}
}
}

package com.fingard.sych;
public class Experiment {

public static void main(String[] args) {
Account account = new Account(50000);
MyThread myThread = new MyThread(account);
new Thread(myThread,"线程A").start();
new Thread(myThread,"线程B").start();
}
}


线程B取走了50000元,账户余额:-50000
线程A取走了50000元,账户余额:-50000

多次测试可能出现上面的bug情况,账户里面的余额只有5万,但是用户却可以在某些特定情况向拿出10万,这显然是不合理的,原因在与线程A进来的时候通过余额判断确定可以进去if语句块
但是这时候线程A时间片正好执行完了,没时间去执行banlance-=num;然后线程b正好分到了时间片,这时账户余额还是5万,所以也进入到了if语句块,最后两个线程都执行了取款操作,(

日入百万不是梦),那么这个bug怎么解决呢

2.引入synchronize同步块解决线程安全问题
我们对Account这个类的draw方法进行改变
public synchronized void draw(int num){
if(balance>=num){
balance-=num;
System.out.println(Thread.currentThread().getName()+
"取走了"+num+"元,账户余额:"+balance);
}
}
线程B取走了50000元,账户余额:0
Process finished with exit code 0

可以看到不管怎么测试,结果要么是a取走了5万,要么是b取走了5万,但绝对不会出现前面的情况。原因在与被synchronized修饰的方法会被锁对象的内置锁锁起来,只有拥有这个锁对象钥匙的线程(第一个访问这个同步块的线程)才可以访问,其他的线程只能在同步块外面等待。直到拥有钥匙的线程运行完同步块归还钥匙的时候才有机会执行同步块。这里面b线程先拿到account这个实例对象的钥匙,进入到了同步块,这期间a线程由于没有account实例对象的钥匙,只能等,直到b线程执行完归还钥匙之后,a线程才拿到钥匙执行draw方法,但这时候由于余额不足,所以不能取钱了。(synchronized在修饰方法的时候如果没有使用“()”指明被锁的对象,默认是调用这个方法的对象)

3 被synchronized修饰的方法被锁的对象不同,则实际运行中线程之间互不干扰。
现在改一下我们的测试方法,生成两个账户
public class Experiment {

public static void main(String[] args) {
Account accountA = new Account(50000);
Account accountB = new Account(50000);
MyThread myThreadA = new MyThread(accountA);
MyThread myThreadB = new MyThread(accountB);
new Thread(myThreadA,"线程A").start();
new Thread(myThreadB,"线程B").start();
}
}

public synchronized void draw(int num){
System.out.println(Thread.currentThread().getName()+"来取钱了");
if(balance>=num){
balance-=num;
System.out.println(Thread.currentThread().getName()+
"取走了"+num+"元,账户余额:"+balance);
}
}

线程A来取钱了
线程B来取钱了
线程A取走了50000元,账户余额:0
线程B取走了50000元,账户余额:0


结果显示a线程从a账户中取走了5万,b线程从b账户中取走了5万,而且两个线程都进入了draw方法,没有出现排队等待现象。这是由于他们的锁对象不一致,线程a在执行draw方法的实例对象是accountA,线程b在执行draw方法的实例对象是accountB,所以线程a在执行draw方法的时候拿到的时accountA对象的钥匙,线程b则拿的是accountB
对象的钥匙,因此两者不存在竞争关系。都进入了draw方法。

4对于上面的情况我想让不同的账户在执行draw方法的时候也要有同步现象,那要怎么办呢?根据上面的实验,其实只要把synchronized锁的对象具有唯一性就可以了,这就好办了,我们把draw这个方法变成静态方法
public static synchronized void draw(int num){
System.out.println(Thread.currentThread().getName()+"来取钱了");
//        if(balance>=num){
//            balance-=num;
//            System.out.println(Thread.currentThread().getName()+
//                    "取走了"+num+"元,账户余额:"+balance);
//        }
System.out.println(Thread.currentThread().getName()+"取完钱走了");
}
线程B来取钱了
线程B取完钱走了
线程A来取钱了
线程A取完钱走了

可以看到不管怎么测试,ab线程交错的情况是不会出现的,说明起到了同步效果,这是为什么呢,还是那句话(synchronized在修饰方法的时候如果没有使用“()”指明被锁的对象,默认是调用这个方法的对象),一开始是非静态方法,这样的方法每个实例对象各一份,因此锁的是调用这个对象的实例对象,但是变成静态方法之后,由于静态方法可以理解为归类对象所有的,类对象全局唯一,所以调用这个静态方法的就是类对象,所以锁的对象也是类对象,我上面的改法可能会对代码逻辑有一定影响,你可以通过这样的方式进行更改,
public  void draw(int num){
synchronized(Account.class)
97d0
{
System.out.println(Thread.currentThread().getName() + "来取钱了");
if (balance >= num) {
balance -= num;
System.out.println(Thread.currentThread().getName() +
"取走了" + num + "元,账户余额:" + balance);
}
}

结束语

说到底,不管是对象锁还是类锁,其实锁的都是对象,只是类锁锁的对象是全局唯一的。这是我对synchronized的理解,希望对观看这篇文章的读者有所帮助
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息