zoukankan      html  css  js  c++  java
  • Java多线程:线程安全问题

    一、线程安全问题案例

    模拟银行账户存取款

    1、账户类

    package com.lyq.java.threadsafe;
    /*
    银行账户
        不使用线程同步机制,多线程对同一个账户进行取款,出现线程安全问题。
     */
    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);
        }
    }

     2、实现类

    package com.lyq.java.threadsafe;
    
    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());
        }
    }

    3、测试

    package com.lyq.java.threadsafe;
    
    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();
        }
    }

    4、结果

    二、使用synchronized同步代码块

    1、账户类及线程实现类

    package com.bjpowernode.java.threadsafe2;
    /*
    银行账户
        使用线程同步机制,解决线程安全问题。
     */
    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);
            //}
        }
    }
    package com.lyq.java.threadsafe2;
    
    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());
        }
    }

     2、测试

    package com.lyq.java.threadsafe2;
    
    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();
        }
    }

     3、结果

    三、实例方法上可以使用synchronized

    1、账户类

    package com.lyq.java.threadsafe3;
    
    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);
        }
    }

     2、实现类

    package com.lyq.java.threadsafe3;
    
    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());
        }
    }

     3、测试

    package com.lyq.java.threadsafe3;
    
    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();
        }
    }

     4、结果

  • 相关阅读:
    《DSP using MATLAB》Problem 6.17
    一些老物件
    《DSP using MATLAB》Problem 6.16
    《DSP using MATLAB》Problem 6.15
    《DSP using MATLAB》Problem 6.14
    《DSP using MATLAB》Problem 6.13
    《DSP using MATLAB》Problem 6.12
    《DSP using MATLAB》Problem 6.11
    P1414 又是毕业季II
    Trie树
  • 原文地址:https://www.cnblogs.com/omgliyq/p/14428099.html
Copyright © 2011-2022 走看看