3-3-Java多线程-案例-银行存钱&同步函数

银行存钱案例

  • 两个客户到一个银行去存钱,每个客户一次存100,存三次。
  • 问题:改程序是否有安全问题,如果有写出分析过程并定义解决方案

代码示例-未修改前

代码

class Bank{
    private int sum;
    private Object obj = new Object();
    public void add(int num)
    {
        sale(num);
    }

    /**
     * 需要同步的同步函数
     * @param num
     */
    private /*synchronized*/ void sale(int num)
    {
        sum = sum+num;
        /* 此处可能发生CPU切换 */
        System.out.println("sum " + sum);
    }
}
class Consumer implements Runnable{
    private Bank bank = new Bank();
    public void run() {
        for (int i = 0 ; i < 3 ; i ++)
        {
            bank.add(100);
        }
    }
}

public class ThreadDemo{
    public static void main(String[] args) {
        Consumer consumer = new Consumer();
        Thread t1 = new Thread(consumer);
        Thread t2 = new Thread(consumer);

        t1.start();
        t2.start();
    }
}

运行结果

sum 200
sum 200
sum 300
sum 400
sum 500
sum 600

分析

问题来源
  • 既然是多线程问题,必然发生在线程任务内
  • 任务代码中是否有共享数据呢? 操作此数据的代码时候有多条任务呢?
private Bank bank = new Bank();
public void run() {
    for (int i = 0 ; i < 3 ; i ++)
    {
        bank.add(100);
    }
}

private int sum;

private /*synchronized*/ void sale(int num)
{
    sum = sum+num;
         /* 此处可能发生CPU切换 */
    System.out.println("sum " + sum);
}

bank是共享数据,具体的讲,bank中的sum是共享数据,并且有多条代码操作此数据.

所以我们需要给操作共享变量的函数加同步

程序示例

class Bank{
    private int sum;
    private Object obj = new Object();
    public void add(int num)
    {
        sale(num);
    }

    /**
     * 同步函数
     * @param num
     */
    private synchronized void sale(int num)
    {
        sum = sum+num;
        /* 此处不再发生CPU切换 因为其他线程得不到锁*/
        System.out.println("sum " + sum);
    }
}
class Consumer implements Runnable{
    private Bank bank = new Bank();
    public void run() {
        for (int i = 0 ; i < 3 ; i ++)
        {
            bank.add(100);
        }
    }
}

public class ThreadDemo{
    public static void main(String[] args) {
        Consumer consumer = new Consumer();
        Thread t1 = new Thread(consumer);
        Thread t2 = new Thread(consumer);

        t1.start();
        t2.start();
    }
}

 同步函数

  • 同步的另外一个体现形式:同步函数 private synchronized void sale(int num)
  • 同步函数使用this做锁 , 因为函数必须被对象调用
    (可写一个同步代码块,写一个同步函数,同步代码块以this为锁,如果同步代码块中锁对象是同一个
    ,那么这一块代码不可被同步执行.)

class Ticket implements Runnable
{
    private int tickets = 100;
    private Object obj = new Object();
    boolean flag = true;
    public void run()
    {
        if(flag){
            while(true){
                synchronized(this){
                    if(tickets>0){
                        try{Thread.sleep(10);}catch(InterruptedException e){}
                        System.out.println(Thread.currentThread().getName()+"...obj..."+tickets--);//打印线程名称。
                    }
                }
            }
        }
        else{
            while(true){
                this.sale();
            }
        }
    }

    public synchronized void sale()//同步函数,使用的锁对象 this。
    {
        if(tickets>0)
        {
            try{Thread.sleep(10);}catch(InterruptedException e){}
            System.out.println(Thread.currentThread().getName()+"...sale..."+tickets--);//打印线程名称。
        }
    }
}
class ThreadDemo4 
{
    public static void main(String[] args) 
    {
        Ticket t = new Ticket();

        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);

        t1.start();
        try{Thread.sleep(10);}catch(InterruptedException e){}
        //切换标记,之前,让主线程停一会,这时就只有一个t1线程在,它就会执行同步代码块。
        t.flag = false;

        t2.start();
    }
}

同步函数和同步代码块有什么区别

  • 同步函数使用的锁是固定的this。当线程任务中只需要一个同步时完全可以使用同步函数
  • 同步代码块使用的锁是任意的对象。当线程任务中需要多个同步时,必须通过锁来区分
  • 静态同步函数使用的锁不是this而是字节码文件对象 类名.class
    (一般当只有一个锁时只用同步函数就可以)

静态同步函数的锁

静态同步函数使用.class对象做锁



/*
static 同步函数,使用的锁不是this,而是字节码文件对象, 类名.class   
*/
class Ticket implements Runnable
{
    private static int tickets = 100;
    private Object obj = new Object();
    boolean flag = true;
    public void run()
    {
        if(flag){
            while(true){
                synchronized(Ticket.class){
                    if(tickets>0){
                        try{Thread.sleep(10);}catch(InterruptedException e){}
                        System.out.println(Thread.currentThread().getName()+"...obj..."+tickets--);//打印线程名称。
                    }
                }
            }
        }
        else{
            while(true){
                this.sale();
            }
        }
    }

    public static synchronized void sale()//
    {
        if(tickets>0)
        {
            try{Thread.sleep(10);}catch(InterruptedException e){}
            System.out.println(Thread.currentThread().getName()+"...sale..."+tickets--);//打印线程名称。
        }
    }
}
class ThreadDemo5 
{
    public static void main(String[] args) 
    {
        Ticket t = new Ticket();

        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);

        t1.start();
        try{Thread.sleep(10);}catch(InterruptedException e){}
        //切换标记,之前,让主线程停一会,这时就只有一个t1线程在,它就会执行同步代码块。
        t.flag = false;

        t2.start();
    }
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 多线程中的同步访问是指多个线程同时访问共享资源时,需要通过一定的机制来保证数据的正确性和一致性。常见的同步访问机制包括锁、信号量、条件变量等。在Java中,可以使用synchronized关键字来实现同步访问,也可以使用Lock接口及其实现类来实现。同步访问的目的是避免多个线程同时修改同一个共享资源,从而导致数据不一致或者程序出现异常。 ### 回答2: 多线程是一种被广泛应用的编程技术,其可以使用多个线程同时执行不同的任务,以提高程序的运行效率和响应速度。但同时使用多个线程也会带来一些问题,其中之一就是线程之间的同步访问问题。本文将从同步访问的概念、线程安全同步方式等方面介绍多线程同步访问问题。 一、同步访问的概念 多线程中的同步访问是指多个线程在访问共享资源时,为了避免竞争条件和数据不一致问题,需要进行协调和同步。例如,多个线程同时访问同一个对象中的方法或属性,就需要进行同步处理,避免出现数据不一致或错乱的情况。 二、线程安全 线程安全是指在多线程环境中,一个对象能够保证经过多个线程访问后仍能保持其状态和正确性。线程安全的实现需要考虑以下两个方面: 1、互斥访问:互斥访问指的是,在某个线程访问对象时,其他线程不能同时访问该对象,需要进行排队等待。 2、共享对象的状态安全:共享对象的状态安全指的是,共享对象在多个线程中被访问时,能够保持其状态的一致性,避免出现数据不一致或错乱的情况。 三、同步方式 实现多线程同步访问可以使用以下方式: 1、同步方法:将需要同步访问的代码块封装在一个 synchronized 修饰的方法中,保证在任意时刻最多只有一个线程执行该代码块。但是需要注意,同步方法会影响程序的执行效率,因为其他线程必须等待当前线程执行完毕后才能执行。 2、同步块:使用 synchronized 关键字和任意对象实现同步块。同步块将需要同步访问的代码块括到 synchronized 关键字指定的对象中,保证在任意时刻最多只有一个线程执行该代码块。与同步方法相比,同步块的粒度更细,执行效率也更高。 3、Lock 锁:Lock 锁是 java.util.concurrent.locks 包中提供的一种线程锁,通过 Lock 的 lock() 和 unlock() 方法实现同步访问。与 synchronized 关键字相比,Lock 锁具有更灵活、更可靠的特性,如可重入、可中断、timeout 等。但是需要注意,使用 Lock 锁时必须手动释放锁,否则会导致死锁等问题。 四、总结 多线程同步访问是提高程序运行效率和响应速度的必要手段,同时也是保障程序正确性和安全性的重要措施。在实现多线程同步访问时,需要考虑互斥访问和共享对象的状态安全问题,并选择合适的同步方式,如同步方法、同步块和 Lock 锁等。通过合理的同步处理,可以避免出现数据不一致或错乱的情况,提高程序的稳定性和可靠性。 ### 回答3: 在Java编程中,多线程是一项非常重要的概念。Java提供了各种各样的线程操作,可以使程序员更自由地控制线程的执行。在使用多线程的过程中,同步访问是一项非常核心的技术,它能够帮助程序员解决线程并发访问时可能引发的各种问题。 多线程可能涉及到多个线程对同一资源的操作,例如内存或是磁盘文件。如果多个线程同时访问同一个资源,就可能导致数据的不一致性、竞争条件和死锁等问题。解决这类问题的一种方法就是同步访问。 同步访问的原理是在多个线程使用同一个资源的时候,使用锁机制来保证各个线程对资源的访问顺序,确保数据一致性。Java中提供了关键字synchronized来实现同步访问,这个关键字可以应用于方法和代码块。 通过使用synchronized,我们可以使多个线程在访问同一对象时有序地执行,从而避免竞争条件的产生。在使用synchronized时,要注意一些细节,例如避免死锁,确保锁的粒度正确等。 另外,Java的并发包中提供了各种各样的同步访问机制,例如ReentrantLock、Semaphore、CountDownLatch等等。这些机制可以更加灵活地控制线程的访问,使多线程编程更加方便和安全。 总之,同步访问是多线程编程中非常重要的一个概念。通过使用同步访问,我们可以保证多个线程对同一资源的访问有序地进行,从而避免竞争条件和死锁等问题。在Java中,我们可以使用synchronized关键字或是并发包中的各种机制来实现同步访问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值