ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

java线程同步(一)

2020-11-11 21:31:28  阅读:179  来源: 互联网

标签:Account 同步 java 账户 线程 余额 balance public


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("\t余额 :"+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("\t余额 :"+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("\t余额 :"+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("\t余额 :"+balance);
            }else {
                System.out.println(Thread.currentThread().getName()+"取钱失败,余额不足!");
            }
    }
}

运行结果如下:

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

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

标签:Account,同步,java,账户,线程,余额,balance,public
来源: https://www.cnblogs.com/hequnwang/p/13961075.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有