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("\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对像是唯一的!

上一篇:Spring学习-6-Spring事务管理


下一篇:Java线程同步