zoukankan      html  css  js  c++  java
  • java线程同步(一)

    java线程同步

    一、使用线程进行数据传递

    回调函数进行数据传递

    这里举个例子,新建一个ThreadTest07.java代码:

    import java.util.Random;
    
    class data{
        public int value = 0;
    }
    class work{
        public void progress(data data,int n1, int n2, int n3){
            {
                int sum = 0;
                sum += n1;
                sum += n2;
                sum += n3;
                data.value = sum ;
            }
        }
    }
    public class ThreadTest07 implements Runnable{
        private work work;
        public ThreadTest07(work work){
            this.work = work;
        }
        @Override
        public void run() {
            Random random = new Random();
            data data = new data();
            int a = random.nextInt(1000);
            int b = random.nextInt(1000);
            int c = random.nextInt(1000);
            work.progress(data,a,b,c);
            System.out.println(String.valueOf(a) + "+" + String.valueOf(b) + "+"
                    + String.valueOf(c) + "=" + data.value);
        }
    
        public static void main(String[] args) {
            new Thread(new ThreadTest07(new work())).start();
        }
    }
    
    

    运行结果:

    658+83+459=1200
    

    这是一个使用线程来进行数据处理的例子,但是我们需要注意线程同步的问题,由一个例子引出线程同步的概念。

    二、线程同步

    java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查), 将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用, 从而保证了该变量的唯一性和准确性。解决办法:在线程使用一个资源的时候,我们为其加锁即可。访问资源的第一个线程为其加上锁以后,其它线程便不能访问那个资源,除非获得那个资源的线程对其解锁!

    (一)、银行取钱算法:

    这里我们介绍一下银行取钱的算法,也是很多教程都在用的关于多线程的例子。

    我们大致的说一下代码流程:

    1. 新建一个账户的类,里面有账户信息、账户余额、创建账户、取钱方法,修改账户余额。
    2. 新建一个取钱的线程类,这个线程主要来进行取钱的操作。
    3. 主方法,主方法里创建账户,设置账户信息存款,创建两个用户,分别对同一个银行账户进行存取。

    1、创建银行账户Account

    class Account {
        //账户号码
        private String accountNo;
        //账户余额
        private double balance;
        //获取账户号码
        public String getAccountNo() {
            return accountNo;
        }
    	//获取账户余额
        public double getBalance() {
            return balance;
        }
        //创建账户的构造器
        public Account(String accountNo, double balance) {
            this.accountNo = accountNo;
            this.balance = balance;
        }
        //取钱的方法
        public  void draw (double drawAmount){
            //账户余额大于取钱数目
            if(balance >=drawAmount){
                //吐出钞票---打印正则取钱的的线程的名字+取钱数额
                System.out.println(Thread.currentThread().getName() +" 取钱成功,吐出钞票"+drawAmount);
                //修改余额
                balance=balance-drawAmount;
                System.out.println("	余额 :"+balance);
            }else {
                System.out.println(Thread.currentThread().getName()+"取钱失败,余额不足!");
            }
        }
    
    }
    

    2、取钱的线程DrawThread

    class DrawThread implements Runnable{
        //银行账户
        private Account account;
        //取钱数额
        private double drawAmonut;
    	//取钱线程的构造器
        public DrawThread(Account account,double drawAmonut){
            this.account = account;
            this.drawAmonut = drawAmonut;
        }
        //这里进行取钱的操作
        @Override
        public void run() {
            //获得账户,然后取钱。
            account.draw(drawAmonut);
        }
    }
    

    3、主线程Threadbank

    public class Threadbank {
        public static void main(String[] args) {
            //创建一个银行账户
            Account account= new Account("whq", 1000);
            //模拟两个线程对同一个账户操作,开启两个线程
            Thread t1=new Thread(new DrawThread(account,800),"甲");
            Thread t2=new Thread(new DrawThread(account,800),"乙");
            t1.start();
            t2.start();
        }
    }
    

    4、完整的代码如下:

    class Account {
        //账户号码
        private String accountNo;
        //账户余额
        private double balance;
        //获取账户号码
        public String getAccountNo() {
            return accountNo;
        }
    	//获取账户余额
        public double getBalance() {
            return balance;
        }
        //创建账户的构造器
        public Account(String accountNo, double balance) {
            this.accountNo = accountNo;
            this.balance = balance;
        }
        //取钱的方法
        public  void draw (double drawAmount){
            //账户余额大于取钱数目
            if(balance >=drawAmount){
                //吐出钞票---打印正则取钱的的线程的名字+取钱数额
                System.out.println(Thread.currentThread().getName() +" 取钱成功,吐出钞票"+drawAmount);
                //修改余额
                balance=balance-drawAmount;
                System.out.println("	余额 :"+balance);
            }else {
                System.out.println(Thread.currentThread().getName()+"取钱失败,余额不足!");
            }
        }
    }
    
    class DrawThread implements Runnable{
        //银行账户
        private Account account;
        //取钱数额
        private double drawAmonut;
    	//取钱线程的构造器
        public DrawThread(Account account,double drawAmonut){
            this.account = account;
            this.drawAmonut = drawAmonut;
        }
        //这里进行取钱的操作
        @Override
        public void run() {
            //获得账户,然后取钱。
            account.draw(drawAmonut);
        }
    }
    
    public class Threadbank {
        public static void main(String[] args) {
            //创建一个银行账户
            Account account= new Account("whq", 1000);
            //模拟两个线程对同一个账户操作,开启两个线程
            Thread t1=new Thread(new DrawThread(account,800),"甲");
            Thread t2=new Thread(new DrawThread(account,800),"乙");
            t1.start();
            t2.start();
        }
    }
    

    运行结果如下(这里列举几个示例):

    第一种:
    甲 取钱成功,吐出钞票800.0
    乙 取钱成功,吐出钞票800.0
    	余额 :-600.0
    	余额 :200.0
    
    第二种:
    甲 取钱成功,吐出钞票800.0
    	余额 :200.0
    乙 取钱成功,吐出钞票800.0
    	余额 :-600.0
    
    第三种:
    甲 取钱成功,吐出钞票800.0
    乙 取钱成功,吐出钞票800.0
    	余额 :200.0
    	余额 :-600.0
    
    第四种(设计的正确答案):
    甲 取钱成功,吐出钞票800.0
    	余额 :200.0
    乙取钱失败,余额不足!
    

    分析:

    主线程这里是先让甲取出800,然后乙再去取出800,如果按照流程来思考,最终显示的正确结果应该为第四种,甲取钱成功,乙取不出钱。但是这里我们还列举了前三种结果,为什么会出现这种结果呢?有的都显示取钱成功,最终余额为-600。这是因为两个线程同时对同一块资源使用,可能在CPU进行分配的时候,甲线程刚刚开始,程序还没有运行结束,乙线程就开始了,同样是对同一个银行账户进行取钱。甲乙“同时”对同一个账户取钱,同时进行,所以出现了这种错误结果。所以由这个结果我们可以看到如果不对线程进行限制,那么有可能出现这种情况。我们需要对此进行改进,毕竟银行不能做亏本的买卖对吧!

    (二)、解决方案

    1、join()的方法

    之前的文章了我们介绍过join(),他表示等待该线程终止,然后继续其他线程,我们修改一下Threadbank代码:

    public class Threadbank {
        public static void main(String[] args) {
            //创建一个账户
            Account account= new Account("whq", 1000);
            //模拟两个线程对同一个账户操作
            Thread t1=new Thread(new DrawThread(account,800),"甲");
            Thread t2=new Thread(new DrawThread(account,800),"乙");
            t1.start();
            try {
                t1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            t2.start();
            try {
                t2.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    

    运行结果为:

    甲 取钱成功,吐出钞票800.0
    	余额 :200.0
    乙取钱失败,余额不足!
    

    这里表示先让甲执行完,然后再让乙去取钱。这也算是一种方案了。

    2、线程同步synchronized--上锁

    我们使用synchronized关键字对银行账户对象进行加锁,问题解决。因为:当甲对这个银行账户取钱时,就获得了这个账户的对象,然后对这个账户进行加锁,知道这个账户的取钱动作完成(或者是其它的原因导致程序退出),甲用户将释放这个锁,此后乙就可以对这个银行账户进行操作,然后加锁,再继续完成一系列的取钱操作!,思想就是先让一个人做完,然后释放资源,另一个在继续上锁,使用资源,用完再释放资源。

    a、修改一下Account代码:这个synchronized称之为synchronized同步代码块
    class Account {
        //账户号码
        private String accountNo;
        //账户余额
        private double balance;
        public String getAccountNo() {
            return accountNo;
        }
        public double getBalance() {
            return balance;
        }
        public Account(String accountNo, double balance) {
            this.accountNo = accountNo;
            this.balance = balance;
        }
        //取钱的方法
        public  void draw (double drawAmount){
            synchronized (accountNo){
                //账户余额大于取钱数目
                if(balance >=drawAmount){
                    //吐出钞票
                    System.out.println(Thread.currentThread().getName() +" 取钱成功,吐出钞票"+drawAmount);
                    balance=balance-drawAmount;
                    System.out.println("	余额 :"+balance);
                }else {
                    System.out.println(Thread.currentThread().getName()+"取钱失败,余额不足!");
                }
            }
    
        }
    
    }
    

    在这里我们在判断取钱前,首先将银行账户上锁,只允许一个人对此账户操作,操作完成之后在释放此资源。

    运行结果如下:

    甲 取钱成功,吐出钞票800.0
    	余额 :200.0
    乙取钱失败,余额不足!
    
    b、修改一下Account代码:这个synchronized称之为synchronized同步方法
    class Account {
        //账户号码
        private String accountNo;
        //账户余额
        private double balance;
        public String getAccountNo() {
            return accountNo;
        }
        public double getBalance() {
            return balance;
        }
        public Account(String accountNo, double balance) {
            this.accountNo = accountNo;
            this.balance = balance;
        }
        //取钱的方法
        public synchronized void draw (double drawAmount){
                //账户余额大于取钱数目
                if(balance >=drawAmount){
                    //吐出钞票
                    System.out.println(Thread.currentThread().getName() +" 取钱成功,吐出钞票"+drawAmount);
                    //修改余额
                    balance=balance-drawAmount;
                    System.out.println("	余额 :"+balance);
                }else {
                    System.out.println(Thread.currentThread().getName()+"取钱失败,余额不足!");
                }
        }
    }
    

    运行结果如下:

    甲 取钱成功,吐出钞票800.0
    	余额 :200.0
    乙取钱失败,余额不足!
    

    运行结果和synchronized修饰的同步代码块效果是一样的.有一点需要注意的是:如果一个对象中的所有方法都用synchronized关键字修饰的话,则这个对象就称为同步锁!当调用一个对像的一个synchronized方法时,就会给这个对象上锁!其它对象就无法访问这个对象的synchronized方法!如果某个synchronized方法是static 方法的话,那么当线程访问该方法时,它锁的并不是synchronized所在的对象,而是synchronized方法所在对象的所对应的Class对象! Class对象是唯一的,不管你new 了多少个对像,Class对像是唯一的!

  • 相关阅读:
    Ubuntu16.04更新源后apt-get update报错的解决方法
    安装Ubuntu16.04 64bit系统时出错的解决方案
    采用Python-Qt5制作置顶透明桌面提醒词/座右铭/便签
    如何手动解析Keras等框架保存的HDF5格式的权重文件
    读取yml配置文件中的值
    添加20位随机数,不重复,可以用来作为发票申请流水等功能
    java 从json串中取出某个字段的值
    Spring事务的两种方式
    (附表设计)超级全面的权限系统设计方案
    nfs 测试
  • 原文地址:https://www.cnblogs.com/hequnwang/p/13961075.html
Copyright © 2011-2022 走看看