多线程-同步方法及同步块(修改三大不安全案例)
同步方法:只能锁类本身 由于我们可以通过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());//输出一下集合的大小,但是输出正常是不够,因为有时候会出来两个元素放在一个位置然后会覆盖掉 } }
分类:
java之多线程
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~