多线程-同步方法及同步块(修改三大不安全案例)

同步方法:只能锁类本身

  由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种用法synchronzed方法和synchronized块

      同步方法:public synchronzed void method(int args){}
   synchronzed方法控制对”对象“的访问,每个对象对应一把锁,每个synchronzed方法都必须获得调用该方法对象的锁才能执行,否则线程会阻塞方法,方法一旦执行,就独占该锁,直到方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。

      缺点:若将一个大的方法申明为synchronzed将会影响效率
复制代码
同步块:当一个大方法中,只有一小部代码需要用同步加锁时,我们可以使用同步块,如:当方法中包含的代码有一部代码只是让你去读取的代码,另一部份代码是用来修改东西的,这只是有修改东西的那个代码需要同步,如果不同步当多人同时修改一个内容时,就会出现问题,那这个时候,我们只需要把同步代码块写到对应代码只即可。

同步块:synchronized((Obj){}:可以锁任何对象
Obj称之为同步监视器:Obj的对象一定是需要增删改的对象

  Obj可以是任何对象,但是推荐使用共享资源作为同步监视器,也就是需要修改的那个对象作为监视器

  同步方法中无需指定同步监视器,因为方法的同步监视器就是this,就是这个对象本身,或者class

同步监视器的执行过程:

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

  第二个线程访问,发现同步监视器被锁定,无方访问

  第一个线程访问完毕,解锁同步监视器

  第二个线程访问,发现同步监视器没有锁,然后锁定并访问
复制代码

 

复制代码
//安全的买票:使用同步方法
//使用同步方法,让他安全
public class Main{
    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 ticketNums=10;
    //创建一个循环停止的属性,外部停止方式
    boolean flag=true;
    public void run(){
        //买票
        while(flag){
            try {//下面抛出异常上面就要处理
                buy();
            } catch(Exception e) {
                System.out.println("异常");
            } 
            
        }
        
    }
    //买票的方法   在买票的方法中加入同步方法关键字,让他变成同步方法即可 
    private  synchronized void buy()throws InterruptedException{
        //判断是否有票
        if (ticketNums<=0){
            flag=false;
            return;
        } 
        //模拟延时
        Thread.sleep(100);
        
        System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
    }
}
复制代码
复制代码
//安全的取钱:使用同步块
//两个人去银行取钱,账户
//sleep():可以放大问题的发生性
public class Main {
    public static void main(String[] args) {
    //账户
    Account acc=new Account(1000,"理财基金");
    //创建银行对象,构造器中,传入账户及取钱金额及名字
    Drawing you=new Drawing(acc,50,"我");
    Drawing gir=new Drawing(acc,100,"他");
    you.start();
    gir.start();
    
    }
}
//账户
class Account{
    int money;//余额
    String name;//卡名
    
    public Account(){}

    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);//获取线程的名字,因为继承了Thread是可以用父类方法来获取的 
        this.account=account;
        this.drawingMoney=drawingMoney;
        
    }
    
    //取钱:重写run,注意:如果我们直接用synchronized 锁run方法,那依旧会有负数,因为run的this类是银行类,但是我们增删改查的操作银行是针对账户的,所以去锁银行,是锁不到的,所以这个方法,我们不能用同步方法去写,而是用同步块
    //synchronized默认锁的对象是:this,方法也就是他本身
    public  void run(){
       
       synchronized(account){//使用同步块,为什么account,可以当做监视器使用呢,我们要锁的是会变化的量,需要增删改的对象
       
        //判断有没有钱
        if (account.money-drawingMoney<0){//如果账户里的钱减去要取的钱小于0,就是不够
        System.out.println(Thread.currentThread().getName()+":你的余额不足");
        return;
        } 
        try {
             //当如果账户有钱,我们进来后,都停一秒钟,这样就会让账户出现负数        //为什么出现问题呢,因为这两个账户的内存都不一致的,当他们看到100时,是用时看到的,所以以为都能取完,这就导致了线程的不安全,只有让一个线程先执行完,再让其他线程进行执行,这样才会安全
        Thread.sleep(1000);
        } catch(InterruptedException e) {
            System.out.println("有异常");
        } 
        
   
        //卡内余额=余额-你取的钱
        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);
        
       }
      
       
        
    }
    
}
复制代码
复制代码
//线程不安全的集合
import java.util.ArrayList;
import java.util.list;

public class Main {
    public static void main(String[] args) {
        List<String>list=new ArrayList<String>();
        for (int i=0;i<1000 ;i++ ){
            new Thread(()->{//创建1000个线程
            //使用同步块锁变化的量
            synchronized(list){
                 list.add(Thread.currentThread().getName());//把线程的名字添加到集合中
            }
               
            }).start();
        } 
        try {
             Thread.sleep(1000);
        } catch(InterruptedException e) {
            System.out.println("no");
        } 
       
        System.out.println(list.sixe());//输出一下集合的大小,但是输出正常是不够,因为有时候会出来两个元素放在一个位置然后会覆盖掉
    }
}
复制代码

 

posted @   hollg  阅读(57)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~
点击右上角即可分享
微信分享提示