首页 > 其他 > 详细

三大不安全案例及解决过程

时间:2020-09-19 20:41:23      阅读:38      评论:0      收藏:0      [点我收藏+]

三大不安全案例及解决过程

一:买票案例

package com.xxgc.synchronize;
?
//不安全的买票
//线程不安全,有负数的票或者拿到相同的票
public class UnsafeBuyTicket {
   public static void main(String[] args) {
       BuyTicket station = new BuyTicket();
?
       new Thread(station,"小明").start();
       new Thread(station,"Teacher").start();
       new Thread(station,"黄牛党").start();
  }
?
}
?
class BuyTicket implements Runnable{
   //票数
   private int ticketNums = 10;
   boolean flag = true; //外部停止方式
   @Override
   public void run() {
       //买票
       while (flag){
           try {
               buy();
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
      }
  }
?
   private void buy() throws InterruptedException {
       //判断是否有票
       if(ticketNums<=0){
           flag = false;
           return;
      }
       //模拟延时,放大问题的发生性
       Thread.sleep(100);
       //买票
       System.out.println(Thread.currentThread().getName()+"--->拿到了第"+ticketNums--+"张票");
  }
}
?

二:银行取钱案例

package com.xxgc.synchronize;
?
//不安全的取钱
//两个人去银行取钱,账户
public class UnsafeBank {
   public static void main(String[] args) {
       //账户
       Account account = new Account(100,"结婚基金");
       //你要取的钱
       Drawing you = new Drawing(account,50,"你");
       //你妻子要取的钱
       Drawing girlFriend = new Drawing(account,100,"girlFriend");
       //启动线程
       you.start();
       girlFriend.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);
       //手中剩余金额
       //Thread.currentThread().getName() = this.getName()
       System.out.println(this.getName()+"手里的钱:"+nowMoney);
?
  }
}

三:线程不安全的集合案例

package com.xxgc.synchronize;
?
import java.util.ArrayList;
import java.util.List;
?
//线程不安全的集合
//原因:同一时间操作到同一位置,覆盖了
public class UnsafeList {
   public static void main(String[] args) {
       List<String> list = new ArrayList<String>();
       for (int i = 0; i < 10000; i++) {
           new Thread(()->{
               list.add(Thread.currentThread().getName());
          }).start();
      }
       try {
           Thread.sleep(2000);
      } catch (InterruptedException e) {
           e.printStackTrace();
      }
       System.out.println(list.size());
  }
}
?

synchronized借鉴

synchronized(修饰方法和代码块)

1. 含义

  • synchronized 是同步锁,用来实现互斥同步。

  • 在 Java 中,关键字 synchronized 可以保证在同一个时刻,只有一个线程可以执行某个方法或者某个代码块(主要是对方法或者代码块中存在共享数据的操作)。

  • synchronized 还可以保证一个线程的变化(主要是共享数据的变化)被其他线程所看到(保证可见性,完全可以替代 volatile 功能,但是 volatile 更轻量,还是要分场景使用)。


2. 用法

synchronized 包括三种用法:

  • 修饰实例方法

  • 修饰静态方法

  • 修饰代码块

2.1 修饰实例方法

所谓的实例对象锁就是用 synchronized 修饰实例对象中的实例方法,注意是实例方法不包括静态方法,如下:

public synchronized void increase() {
   i++;
}

2.2 修饰静态方法

当 synchronized 作用于静态方法时,其锁就是当前类的 class 对象锁。由于静态成员不专属于任何一个实例对象,是类成员,因此通过 class 对象锁可以控制静态成员的并发操作。需要注意的是如果一个线程 A 调用一个实例对象的非 static synchronized 方法,而线程 B 需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的 class 对象,而访问非静态 synchronized 方法占用的锁是当前实例对象锁,二者的锁并不一样,所以不冲突。

public static synchronized void increase() {
   i++;
}

2.3 修饰代码块

在某些情况下,我们编写的方法体可能比较大,同时存在一些比较耗时的操作,而需要同步的代码又只有一小部分,如果直接对整个方法进行同步操作,可能会得不偿失,此时我们可以使用同步代码块的方法对需要同步的代码进行包裹,这样就无需对整个方法进行同步操作了。

我们可以使用如下几种对象来作为锁的对象:

成员锁

锁的对象是变量

public Object synMethod(Object a1) {
   synchronized(a1) {
       // 操作
  }
}
实例对象锁

this 代表当前实例

synchronized(this) {
   for (int j = 0; j < 100; j++) {
i++;
  }
}
当前类的 class 对象锁
synchronized(AccountingSync.class) {
   for (int j = 0; j < 100; j++) {
       i++;
  }
}

3. 什么是可重入锁

含义

所谓可重入锁,指的是以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的。(同一个加锁线程自己调用自己不会发生死锁情况)

意义

防止死锁。

实现原理

通过为每个锁关联一个请求计数和一个占有它的线程。当计数为 0 时,认为锁是未被占有的。线程请求一个未被占有的锁时,jvm 将记录锁的占有者,并且将请求计数器置为 1 。如果同一个线程再次请求这个锁,计数将递增;每次占用线程退出同步块,计数器值将递减。直到计数器为0,锁被释放。

应用

synchronized 和 ReentrantLock 都是可重入锁。

ReentrantLock 表现为 API 层面的互斥锁(lock() 和 unlock() 方法配合 try/finally 语句块来完成),synchronized 表现为原生语法层面的互斥锁。


4. 互斥同步的缺点

互斥同步最主要的问题就是进行线程阻塞和唤醒所带来的性能问题,因此这种同步也被称为阻塞同步。而且加锁方式属于悲观锁(不管操作是否成功都加锁)。


5. 参考

解决后的代码

synchronized 方法(买票案例)

package com.xxgc.synchronize;
?
//不安全的买票
//线程不安全,有负数的票或者拿到相同的票
public class UnsafeBuyTicket {
   public static void main(String[] args) {
       BuyTicket station = new BuyTicket();
?
       new Thread(station,"小明").start();
       new Thread(station,"Teacher").start();
       new Thread(station,"黄牛党").start();
  }
?
}
?
class BuyTicket implements Runnable{
   //票数
   private int ticketNums = 10;
   boolean flag = true; //外部停止方式
   @Override
   public void run() {
       //买票
       while (flag){
           try {
               buy();
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
      }
  }
?
   private synchronized void buy() throws InterruptedException {
       //判断是否有票
       if(ticketNums<=0){
           flag = false;
           return;
      }
       //模拟延时,放大问题的发生性
       Thread.sleep(100);
       //买票
       System.out.println(Thread.currentThread().getName()+"--->拿到了第"+ticketNums--+"张票");
  }
}
?

synchronized代码块

银行和集合代码的解决
package com.xxgc.synchronize;
?
//不安全的取钱
//两个人去银行取钱,账户
public class UnsafeBank {
   public static void main(String[] args) {
       //账户
       Account account = new Account(100,"结婚基金");
       //你要取的钱
       Drawing you = new Drawing(account,50,"你");
       //你妻子要取的钱
       Drawing girlFriend = new Drawing(account,100,"girlFriend");
       //启动线程
       you.start();
       girlFriend.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;
  }
?
   //取钱
   //synchronized 默认锁的是this.
   @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);
           //手中剩余金额
           //Thread.currentThread().getName() = this.getName()
           System.out.println(this.getName()+"手里的钱:"+nowMoney);
      }
  }
}
package com.xxgc.synchronize;
?
import java.util.ArrayList;
import java.util.List;
?
//线程不安全的集合
//原因:同一时间操作到同一位置,覆盖了
public class UnsafeList {
   public static void main(String[] args) {
       List<String> list = new ArrayList<String>();
       for (int i = 0; i < 10000; i++) {
           new Thread(()->{
               synchronized (list ){
                   list.add(Thread.currentThread().getName());
              }
          }).start();
      }
       try {
           Thread.sleep(2000);
      } catch (InterruptedException e) {
           e.printStackTrace();
      }
       System.out.println(list.size());
  }
}
?

 

三大不安全案例及解决过程

原文:https://www.cnblogs.com/999520hzy/p/13697277.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!