Synchronized解决线程不安全

32 篇文章 0 订阅

目录

一、线程不安全的三个示例

二、synchronized关键字

三、解决线程不安全

四、总结


一、线程不安全的三个示例

1、买票问题:该情况下很容易出现同一张票被重复购买,是线程不安全的。

package syn;

public class UnsafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket station = new BuyTicket();
        new Thread(station,"我").start();
        new Thread(station,"你们").start();
        new Thread(station,"黄牛党").start();
    }
}
class BuyTicket implements Runnable{

    private int ticketNum = 10;
    boolean flag = true;

    @Override
    public void run(){
        while(flag){
            buy();
        }
    }

    private synchronized void buy(){
        if (ticketNum <= 0){
            flag=false;
            return;
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"买到第"+ticketNum--+"张");
    }

}

以上代码运行后,发现第6张、第2张票被重复购买,但是现实生活中,是不能存在这样的问题的。

2、银行取钱:同一张卡,不同的两个人去取,一个想取50,一个想取100,大家看卡里有100,都是满足的,但是两个人同时去取,就会出现,明明两个人要取得钱加起来卡里是不够的,但是两个人却都取到了。

package syn;

public class UnsafeBank {
    public static void main(String[] args) {
        Account account = new Account(100,"存款");
        Drawing me = new Drawing(account,50,"小红");
        Drawing you = new Drawing(account,100,"大黑");
        me.start();
        you.start();
    }
}
//账户
class Account{
    int money; //余额
    String name; //卡名
    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }
}
//银行
class Drawing extends Thread{
    Account account;
    int drawingMoney;
    int nowMoney;
    public Drawing(Account account,int drawingMoney,String name){
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
    }
    @Override
    public void run(){
        if (account.money-drawingMoney<0){
            System.out.println(Thread.currentThread().getName()+"钱不够");
            return;
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        account.money = account.money-drawingMoney;
        nowMoney = nowMoney + drawingMoney;
        System.out.println(account.name+"余额为:"+account.money);
        System.out.println(this.getName()+"手里的钱有:"+nowMoney);
    }
}

运行以上代码,有如下结果:

3、不安全的集合:创建一个ArrayList,把每次运行的线程的名称加入到ArrayList里面,总共运行10000次。

package syn;

import java.util.ArrayList;

public class UnsafeList {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

执行代码后,发现最后打印出的list大小是不足10000的,所以ArrayList是线程不安全的

二、synchronized关键字

synchronized关键字包括两种用法:synchronized方法和synchronized块,也就是同步方法与同步代码块。

1. synchronized方法:public synchronized void fun ( ) { }

控制对“对象”的访问,每个对象对应一把锁,每个synchronized方法要获得调用该方法的对象的锁才鞥执行,否则就线程阻塞,方法一旦被执行,就应当独占锁,直到方法返回才释放锁资源,被阻塞的线程获得锁后继续执行,但是线程安全的同时意味着效率的降低。

2.synchronized块:synchronized ( Obj ) { }

Obj 称为同步监视器,可以是任意对象,但一般为共享资源作为同步监视器,同步方法中不用指定同步监视器, 因为其对象本身,也就是this就是同步监视器,或者是class(反射)也可以。

同步监视器执行过程:

(1)第一个线程访问,锁定同步监视器,并执行其中代码;

(2)第二个线程访问,发现同步监视器被锁,无法访问;

(3)第一个线程访问执行完毕,释放同步监视器;

(4)第二个线程访问,锁定同步监视器,并执行其中代码。

三、解决线程不安全

1. 针对买票问题,我们可以对买票这个公共方法加上synchronized关键字,让买票变成线程安全的操作,其中synchronized为同步方法,所以它锁的是this,如下:

package syn;

public class UnsafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket station = new BuyTicket();
        new Thread(station,"我").start();
        new Thread(station,"你们").start();
        new Thread(station,"黄牛党").start();
    }
}
class BuyTicket implements Runnable{

    private int ticketNum = 10;
    boolean flag = true;

    @Override
    public void run(){
        while(flag){
            buy();
        }
    }

    private synchronized void buy(){
        if (ticketNum <= 0){
            flag=false;
            return;
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"买到第"+ticketNum--+"张");
    }

}

2. 针对取钱问题,首先对run()方法加锁,但是发现还是线程不安全,可以发现run()锁的是this,也就是银行,但是我们要从操作的是账户,所以要使用同步代码块进行加锁,锁住的是银行的账户,修改如下:

package syn;

public class UnsafeBank {
    public static void main(String[] args) {
        Account account = new Account(100,"存款");
        Drawing me = new Drawing(account,50,"小红");
        Drawing you = new Drawing(account,100,"大黑");
        me.start();
        you.start();
    }
}
//账户
class Account{
    int money; //余额
    String name; //卡名
    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }
}
//银行
class Drawing extends Thread{
    Account account;
    int drawingMoney;
    int nowMoney;
    public Drawing(Account account,int drawingMoney,String name){
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
    }
    @Override
    public void run(){
        synchronized (account){
            if (account.money-drawingMoney<0){
                System.out.println(Thread.currentThread().getName()+"钱不够");
                return;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            account.money = account.money-drawingMoney;
            nowMoney = nowMoney + drawingMoney;
            System.out.println(account.name+"余额为:"+account.money);
            System.out.println(this.getName()+"手里的钱有:"+nowMoney);
        }
    }
}

3.针对不安全的集合,一直在改变的是list,所以我们要对list对象进行加锁。

package syn;

import java.util.ArrayList;

public class UnsafeList {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                synchronized (list){
                    list.add(Thread.currentThread().getName());
                }
            }).start();
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

四、总结

1.针对同步方法,锁的对象是this。

2.锁的对象是变化的量,即需要进行增删改的对象。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值