同步方法与同步块
同步方法:public synchronized void method(int args){}
public synchronized void method(int args){}锁住的是包含该方法的类,也就是this。当我们锁某一个类时,业务需求就是对这个类中的已赋值的属性(我们称为多个线程的“统一资源”)进行**增删改操作**。如果A类调用了B类,B类仅仅是一个包含数据的对象(如银行取钱案例中的account),相应的取钱业务存在于A类中,直接锁A类中的取钱方法会导致同步失败,因此需要同步块来锁住那些必要的代码块,就是B类(锁住account),这些必要的需要修改的代码我们称为“目标”。在线程同步中,最重要的就是发现目标,即需要增删改的是类什么。
一般情况下,如果此”目标“为this,目标是同一个类,那么同步方法和同步块没有太大的区别。另外,通过反编译可以看出,同步块比同步方法多了两个指令。因此同步方法是比同步块要快一些。
同步块:synchronized (Obj){/注意同步块中的方法必须是目标所有增删改的业务代码以及用到目标的打印提示语句/},一个线程使用同步块需要等到同步块内容执行完毕之后才会释放资源。
案例1、买票
public class UnsafeBuyTicket {
public static void main(String[] args) {
buyTicket buyticket = new buyTicket();
new Thread(buyticket,"小明").start();
new Thread(buyticket,"小红").start();
new Thread(buyticket,"小刚").start();
}
}
class buyTicket implements Runnable{
private int ticketnum=20;
boolean flag = true;
@Override
public void run() {
while (flag){
buy();
}
}
//因为这里是buyTicket类中的属性ticketnum在做增删改操作,目标是buyTicket类,并且使用synchronized void buy()
//锁住的this恰恰是目标,因此可以使用同步方法
// public synchronized void buy(){
// try {
// if(ticketnum<=0){
// flag = false;
// return;
// }
// System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketnum+"张票");
// ticketnum--;
// Thread.sleep(100);
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }
// }
public void buy(){
try {
//当然也可以使用同步块来锁
synchronized (this){
if(ticketnum<=0){
flag = false;
return;
}
System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketnum+"张票");
ticketnum--;
Thread.sleep(100);
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
案例2、银行取钱
//模拟你和你老婆同时从你的账户里取钱
public class UnsafeBank {
public static void main(String[] args) {
Account account = new Account(100);
drawing drawing1 = new drawing(account,50);
drawing drawing2 = new drawing(account,80);
new Thread(drawing1,"你").start();//你想取50块
new Thread(drawing2,"你老婆").start();//你老婆想取80块
}
}
class Account{
int money;//你的账户里有多少钱
public Account(int money){
this.money=money;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
}
//模拟从银行取钱的线程
class drawing implements Runnable{
private final int drgmoney;
private Account account;
public drawing(Account account,int drawingmoney){
this.account=account;
this.drgmoney=drawingmoney;
}
@Override
//如果这里锁的是run()方法,public synchronized void run(),
// 则所在的是drawing类,然而drawing类内的属性并没有发生增删改,因此我们的目标找错了
public void run(){
//是account类中的money发生了增删改,因此需要锁住的目标是account
synchronized (account){
if(account.getMoney()-drgmoney<0){
System.out.println(Thread.currentThread().getName()+"用户:账户余额不足!!!");
return;
}
try {
//设置延时,放大问题
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
account.setMoney(account.getMoney()-drgmoney);
System.out.println(Thread.currentThread().getName()+"用户您好,已取出"+drgmoney+",目前余额为"+account.getMoney());
}
}
}
案例3、Arraylist
//测试ArrayList的不安全性
public class UnsafeArrayList {
public static void main(String[] args) throws InterruptedException {
ArrayList<String> arrayList = new ArrayList<>();
int InsertThreadNum=10000;//需要插入list中的线程数量
//arraylist的size()发生了增删改,因此目标是arrayList
for(int i=0;i<InsertThreadNum;i++) {
new Thread(() -> {
synchronized (arrayList) {
arrayList.add(Thread.currentThread().getName());
}
}).start();
}
Thread.sleep(100);
//没有上锁前,发现arrayList.size()的数量并不是我们指定的InsertThreadNum
System.out.println(arrayList.size());
}
}