同步方法
我们通过private关键字来保证数据对象只能被方法访问,针对此提出synchronized关键字,包括两种用法:synchronized方法和synchronized块
//同步方法
public synchronized void method(int args){}
synchronized方法控制对“对象“的访问,每个对象一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞。方法一旦执行,独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行
缺陷:若将一个大的方法申明为synchronized将会影响效率
同步块
synchronized(Obj){} 锁住增删改的对象
Obj称之为同步监视器
- Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
- 同步方法中无需指定同步监视器,this就是,即这个对象本身,或者是class
同步监视器的执行过程:
- 第一个线程访问,锁定同步监视器,执行其中代码
- 第二个线程访问,发现同步监视器被锁定,无法访问
- 第一个线程访问完毕,解锁同步监视器
- 第二个线程访问,发现同步监视器没有锁,然后锁定并访问
修改上一节的不安全案例
//synchronized同步方法,锁的是this
private synchronized void buy(){
输出:我拿到了第10张票
我拿到了第9张票
我拿到了第8张票
他拿到了第7张票
你拿到了第6张票
你拿到了第5张票
他拿到了第4张票
他拿到了第3张票
他拿到了第2张票
我拿到了第1张票
//取钱
//synchronized 默认锁的是this
public void run(){
//同步块
synchronized (account){//锁的对象就是变化的量,需要增删改的对象
//判断有没有钱
if(account.money-drawingMoney<0){
System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
return;
}
//sleep放大问题的发生性
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡内余额=余额-你取的钱
account.money=account.money-drawingMoney;
//你手里的钱
nowMoney = nowMoney+drawingMoney;
System.out.println(account.name+"余额为"+account.money);
//Thread.currentThread().getName()=this.getName()
System.out.println(this.getName()+"手里的钱"+nowMoney);
}
}
输出:旅游基金余额为50
你手里的钱50
他钱不够,取不了
new Thread(()->{
synchronized(list){ list.add(Thread.currentThread().getName());}
}).start();
输出:10000