线程安全问题

111 阅读6分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第29天,点击查看活动详情

哈喽,大家好!我是Why,一名在读学生,目前刚刚开始进入自己的编程学习生涯。虽然学习起步较晚,但我坚信做了才有0或1的可能。学了一段时间以后也是选择在掘金上分享自己的日常笔记,也希望能够在众多道友的大家庭中打成一片。 本文主要讲解线程安全问题,如果大家读后觉得有用的话,还请大家多多支持博主:欢迎 ❤️点赞👍、收藏⭐、留言💬 ✨✨✨个人主页:JinHuan

线程安全问题

线程实例--什么时候出现线程安全问题

Account

 /*
 银行账户
     不使用线程同步机制,多线程对同一个账户进行取款,出现线程安全问题。
  */
 public class Account {
     // 账号
     private String actno;
     // 余额
     private double balance;
 ​
     public Account() {
     }
 ​
     public Account(String actno, double balance) {
         this.actno = actno;
         this.balance = balance;
     }
 ​
     public String getActno() {
         return actno;
     }
 ​
     public void setActno(String actno) {
         this.actno = actno;
     }
 ​
     public double getBalance() {
         return balance;
     }
 ​
     public void setBalance(double balance) {
         this.balance = balance;
     }
 ​
     //取款的方法
     public void withdraw(double money){
         // t1和t2并发这个方法。。。。(t1和t2是两个栈。两个栈操作堆中同一个对象。)
         // 取款之前的余额
         double before = this.getBalance(); // 10000
         // 取款之后的余额
         double after = before - money;
 ​
         // 在这里模拟一下网络延迟,100%会出现问题
         try {
             Thread.sleep(1000);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
 ​
         // 更新余额
         // 思考:t1执行到这里了,但还没有来得及执行这行代码,t2线程进来withdraw方法了。此时一定出问题。
         this.setBalance(after);
     }
 }

AccountThread

 public class AccountThread extends Thread {
 ​
     // 两个线程必须共享同一个账户对象。
     private Account act;
 ​
     // 通过构造方法传递过来账户对象
     public AccountThread(Account act) {
         this.act = act;
     }
 ​
     public void run(){
         // run方法的执行表示取款操作。
         // 假设取款5000
         double money = 5000;
         // 取款
         // 多线程并发执行这个方法。
         act.withdraw(money);
 ​
         System.out.println(Thread.currentThread().getName() + "对"+act.getActno()+"取款"+money+"成功,余额" + act.getBalance());
     }
 }
 ​

Test

 public class Test {
     public static void main(String[] args) {
         // 创建账户对象(只创建1个)
         Account act = new Account("act-001", 10000);
         // 创建两个线程
         Thread t1 = new AccountThread(act);
         Thread t2 = new AccountThread(act);
         // 设置name
         t1.setName("t1");
         t2.setName("t2");
         // 启动线程取款
         t1.start();
         t2.start();
     }
 }

线程实例修改--让其变得安全Synchornized(一)

Account

 public class Account {
     // 账号
     private String actno;
     // 余额
     private double balance;
 ​
     public Account() {
     }
 ​
     public Account(String actno, double balance) {
         this.actno = actno;
         this.balance = balance;
     }
 ​
     public String getActno() {
         return actno;
     }
 ​
     public void setActno(String actno) {
         this.actno = actno;
     }
 ​
     public double getBalance() {
         return balance;
     }
 ​
     public void setBalance(double balance) {
         this.balance = balance;
     }
 ​
     //取款的方法
     /*
     在实例方法上可以使用synchronized吗?可以的。
         synchronized出现在实例方法上,一定锁的是this。
         没得挑。只能是this。不能是其他的对象了。
         所以这种方式不灵活。
 ​
         另外还有一个缺点:synchronized出现在实例方法上,
         表示整个方法体都需要同步,可能会无故扩大同步的
         范围,导致程序的执行效率降低。所以这种方式不常用。
 ​
         synchronized使用在实例方法上有什么优点?
             代码写的少了。节俭了。
 ​
         如果共享的对象就是this,并且需要同步的代码块是整个方法体,
         建议使用这种方式。
      */
     public synchronized void withdraw(double money){
         double before = this.getBalance(); // 10000
         double after = before - money;
         try {
             Thread.sleep(1000);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         this.setBalance(after);
     }
 }

线程实例修改--让其变得安全Synchornized(二)

Account

 public class Account {
     // 账号
     private String actno;
     // 余额
     private double balance; //实例变量。
 ​
     //对象
     Object obj = new Object(); // 实例变量。(Account对象是多线程共享的,Account对象中的实例变量obj也是共享的。)
 ​
     public Account() {
     }
 ​
     public Account(String actno, double balance) {
         this.actno = actno;
         this.balance = balance;
     }
 ​
     public String getActno() {
         return actno;
     }
 ​
     public void setActno(String actno) {
         this.actno = actno;
     }
 ​
     public double getBalance() {
         return balance;
     }
 ​
     public void setBalance(double balance) {
         this.balance = balance;
     }
 ​
     //取款的方法
     public void withdraw(double money){
 ​
         //int i = 100;
         //i = 101;
 ​
         // 以下这几行代码必须是线程排队的,不能并发。
         // 一个线程把这里的代码全部执行结束之后,另一个线程才能进来。
         /*
         线程同步机制的语法是:
             synchronized(){
                 // 线程同步代码块。
             }
             synchronized后面小括号中传的这个“数据”是相当关键的。
             这个数据必须是多线程共享的数据。才能达到多线程排队。
 ​
             ()中写什么?
                 那要看你想让哪些线程同步。
                 假设t1、t2、t3、t4、t5,有5个线程,
                 你只希望t1 t2 t3排队,t4 t5不需要排队。怎么办?
                 你一定要在()中写一个t1 t2 t3共享的对象。而这个
                 对象对于t4 t5来说不是共享的。
 ​
             这里的共享对象是:账户对象。
             账户对象是共享的,那么this就是账户对象吧!!!
             不一定是this,这里只要是多线程共享的那个对象就行。
 ​
             在java语言中,任何一个对象都有“一把锁”,其实这把锁就是标记。(只是把它叫做锁。)
             100个对象,100把锁。1个对象1把锁。
 ​
             以下代码的执行原理?
                 1、假设t1和t2线程并发,开始执行以下代码的时候,肯定有一个先一个后。
                 2、假设t1先执行了,遇到了synchronized,这个时候自动找“后面共享对象”的对象锁,
                 找到之后,并占有这把锁,然后执行同步代码块中的程序,在程序执行过程中一直都是
                 占有这把锁的。直到同步代码块代码结束,这把锁才会释放。
                 3、假设t1已经占有这把锁,此时t2也遇到synchronized关键字,也会去占有后面
                 共享对象的这把锁,结果这把锁被t1占有,t2只能在同步代码块外面等待t1的结束,
                 直到t1把同步代码块执行结束了,t1会归还这把锁,此时t2终于等到这把锁,然后
                 t2占有这把锁之后,进入同步代码块执行程序。
 ​
                 这样就达到了线程排队执行。
                 这里需要注意的是:这个共享对象一定要选好了。这个共享对象一定是你需要排队
                 执行的这些线程对象所共享的。
          */
         //Object obj2 = new Object();
         //synchronized (this){
         //synchronized (obj) {
         //synchronized ("abc") { // "abc"在字符串常量池当中。
         //synchronized (null) { // 报错:空指针。
         //synchronized (obj2) { // 这样编写就不安全了。因为obj2不是共享对象。
         double before = this.getBalance();
         double after = before - money;
         try {
             Thread.sleep(1000);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         this.setBalance(after);
         //}
     }
 }

AccountThread

 public class AccountThread extends Thread {
 ​
     // 两个线程必须共享同一个账户对象。
     private Account act;
 ​
     // 通过构造方法传递过来账户对象
     public AccountThread(Account act) {
         this.act = act;
     }
 ​
     public void run(){
         // run方法的执行表示取款操作。
         // 假设取款5000
         double money = 5000;
         // 取款
         // 多线程并发执行这个方法。
         //synchronized (this) { //这里的this是AccountThread对象,这个对象不共享!
         synchronized (act) { // 这种方式也可以,只不过扩大了同步的范围,效率更低了。
             act.withdraw(money);
         }
 ​
         System.out.println(Thread.currentThread().getName() + "对"+act.getActno()+"取款"+money+"成功,余额" + act.getBalance());
     }
 }
 ​

Test

 public class Test {
     public static void main(String[] args) {
         // 创建账户对象(只创建1个)
         Account act = new Account("act-001", 10000);
         // 创建两个线程
         Thread t1 = new AccountThread(act);
         Thread t2 = new AccountThread(act);
 ​
         // 设置name
         t1.setName("t1");
         t2.setName("t2");
         // 启动线程取款
         t1.start();
         t2.start();
     }
 }
 ​

死锁

 public class DeadLock {
     public static void main(String[] args) {
         Object o1 = new Object();
         Object o2 = new Object();
 ​
         // t1和t2两个线程共享o1,o2
         Thread t1 = new MyThread1(o1,o2);
         Thread t2 = new MyThread2(o1,o2);
 ​
         t1.start();
         t2.start();
     }
 }
 ​
 class MyThread1 extends Thread{
     Object o1;
     Object o2;
     public MyThread1(Object o1,Object o2){
         this.o1 = o1;
         this.o2 = o2;
     }
     public void run(){
         synchronized (o1){
             try {
                 Thread.sleep(1000);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             synchronized (o2){
 ​
             }
         }
     }
 }
 ​
 class MyThread2 extends Thread {
     Object o1;
     Object o2;
     public MyThread2(Object o1,Object o2){
         this.o1 = o1;
         this.o2 = o2;
     }
     public void run(){
         synchronized (o2){
             try {
                 Thread.sleep(1000);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             synchronized (o1){
 ​
             }
         }
     }
 }
 ​