java解决共享资源竞争

Posted 真的,秋风来了

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java解决共享资源竞争相关的知识,希望对你有一定的参考价值。

由于多线程的实现,在运行一个程序的时候可能会有很多的线程在同时运行,但是线程的调度并不是可见的,所以不会知道一个线程什么时候在运行,比如说 你坐在桌子前手拿着叉子,正要去叉盘中的最后一片食物,当你的叉子就要够得着他的时候,这片食物突然消失了,因为你的线程挂起了,另一个用餐者进入并吃掉了它,所以这就是在使用多线程时会出现的问题,对于并发的任务,你需要一种方式来防止两个线程同时访问一段资源,基本上所有的并发模式在解决线程冲突上都是采用序列化访问共享资源的方案,这就意味着在cpu给定的时刻,只允许一个线程访问共享资源,通常是在代码前加上一条锁定语句来实现的,一旦执行到锁定语句上的时候,就会产生一种互相排斥的效果,所以这种机制常常成为互斥量,

第一种 关键字synchronizedjava为放置资源冲突提供了内置的支持 关键字synchronized,当代码执行到被synchronized保护的代码片段的时候,它会检查锁是否可用,然后获取锁,释放锁。

注意 :* 共享资源一般是以对象形存在的内存片段,但也可以是文件、输入\\输出端口,或者打印机,要控制对共享资源的访问,得先把它包进一个对象,然后把所有要访问这个资源的方法标记为synchronized,如果某个线程处于一个对标记synchronized的方法的调用中,那么在这个方法返回前。其他所有要调用这个类中任何一个标记为synchronized的方法的线程都会被阻塞。 

 

synchronized的缺陷:当某个线程进入同步方法获得对象锁,那么其他线程访问这里对象的同步方法时,必须等待或者阻塞,这对高并发的系统是致命的,这很容易导致系统的崩溃。如果某个线程在同步方法里面发生了死循环,那么它就永远不会释放这个对象锁,那么其他线程就要永远的等待。这是一个致命的问题。

 

当然同步方法和同步代码块都会有这样的缺陷,只要用了synchronized关键字就会有这样的风险和缺陷。既然避免不了这种缺陷,那么就应该将风险降到最低。这也是同步代码块在某种情况下要优于同步方法的方面。例如在某个类的方法里面:这个类里面声明了一个对象实例,SynObject so=new SynObject();在某个方法里面调用了这个实例的方法so.testsy();但是调用这个方法需要进行同步,不能同时有多个线程同时执行调用这个方法。

 

这时如果直接用synchronized修饰调用了so.testsy();代码的方法,那么当某个线程进入了这个方法之后,这个对象其他同步方法都不能给其他线程访问了。假如这个方法需要执行的时间很长,那么其他线程会一直阻塞,影响到系统的性能。

 

如果这时用synchronized来修饰代码块:synchronized(so){so.testsy();},那么这个方法加锁的对象是so这个对象,跟执行这行代码的对象没有关系,当一个线程执行这个方法时,这对其他同步方法时没有影响的,因为他们持有的锁都完全不一样。

 

所有的对象自动含有单一的锁也叫做监视器,当在对象上调用任意synchronized方法的时候,此对象都被加锁,这时该对象上的其他synchronized方法只有等到前一个方法调用完毕并且释放了所之后才能被调用,还有一个需要注意的 将共享资源设置为private是很有必要的,为了防止其他线程直接访问资源,那么加了锁也没有用,

一个类的对象锁和另一个类的对象锁是没有关联的,当一个线程获得A类的对象锁时,它同时也可以获得B类的对象锁。



然后一个线程可以多次获得对象的锁,比如一个访问的这个被synchronized标记的方法当中在同一个对象上调用了第二个被synchronized标记的方法,后者又调用了另一个同一个对象上被synchronized标记的方法,就会发生这种情况,虚拟机负责跟踪对象被加锁的次数,如果一个对象被完全解锁,其计数器变为0,在线程第一次给对象加锁的时候,计数器变为1,然后每当这个相同的线程在这个对象上获得锁的时候。依次累加1

 

java的对象锁和类锁:java的对象锁和类锁在锁的概念上基本上和内置锁是一致的,但是,两个锁实际是有很大的区别的,对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态方法或者一个类的class对象上的。我们知道,类的对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定实例方法和静态方法的区别的

 

先定义一个共享资源的对象 演示一下对象锁

复制代码
package test.thread.sx;
public class BankAccount {
    //余额  
    private int banlance = 500;  
    //查询  
    public int getBalance(){  
        return banlance;  
    }  
    //取款  
    public void withdraw(int amount){  
        banlance = banlance - amount;  
    }  
    //存款  
    public void deposit(int amount){  
        banlance = banlance + amount;  
    }  
}
复制代码

然后定义任务方法

复制代码
package test.thread.sx;

public class TesMony implements Runnable {
      //所有Thread多线程线程都共享Runnable(接口对象)和account对象  
    private BankAccount account = new BankAccount();  
    @Override  
    public void run() {  
        for(int i = 0; i< 5; i++){           //总共取款5次  
            makeWithdraw(100);          //每次取款100  
            if(account.getBalance() < 0){  
                System.out.println("☆"+Thread.currentThread().getName()+"   透支了!");  
            }  
        }  
    }  
  
    /** 
     * makeWithdraw 账户取款 
     * @param amount 取款金额<br /> 
     * 打印log记录取款过程 
     * */  
    private  synchronized void makeWithdraw(int amount){  
        if(account.getBalance() >= amount){          //如果余额足够则取款  
            System.out.println("☆"+Thread.currentThread().getName()+"   准备取款!");  
            try {  
                Thread.sleep(500);  
            } catch (InterruptedException e) {  
                System.out.println(Thread.currentThread().getName()+"   准备取款,等待0.5s线程中断!"+e.getMessage());  
            }  
            account.withdraw(amount);  
            System.out.println("☆"+Thread.currentThread().getName()+"   完成"+amount+"取款!余额为"+account.getBalance());      }else{          //余额不足则提示  
            System.out.println("☆"+"余额不足以支付"+Thread.currentThread().getName()+amount+"   的取款,余额为"+account.getBalance());  
        }  
    }  

}
复制代码

然后测试一下

复制代码
package test.thread.sx;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Tess {
   
    public static void main(String[] args) {  
        //定义任务的时候 我自己不理解线程的时候犯了一个错误,就是每一个新线程都会给它一个新的任务 也就是新的TesMony对象  那其实就不存在资源竞争了
//因为那根本就是两个对象 资源竞争是针对一个对象来说的 而且互斥锁也是锁在同一个对象上才会出现互斥, TesMony target = new TesMony(); //创建李琦和他老婆两个线程实现取款(同时) Thread lq = new Thread(target); lq.setName("罗密欧"); Thread lqwf = new Thread(target); lqwf.setName("朱丽叶"); //调用Thread对象的start()方法,启动线程,执行run()方法(OS) lq.start(); lqwf.start(); } }
复制代码

第二种 java又提供了另一种方式显示LOCK的方式 这也是以恶搞被互斥调用的锁,并使用lock和unlock方法标识临界资源,它和synchronized关键字的区别,当我们使用synchronized关键字标记的代码片段出现某些错误的时候 会抛出一个异常 但是不会有机会去做任何的补救工作,但是使用lock对象的方法去加锁 解锁,的时候我们可以使用finnaly字句去做一些处理。那么上面的代码可以这样他修改

复制代码
package test.thread.sx;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TesMony implements Runnable {
    private Lock lock = new ReentrantLock();
    // 所有Thread多线程线程都共享Runnable(接口对象)和account对象

    private BankAccount account = new BankAccount();

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) { // 总共取款5次
            makeWithdraw(100); // 每次取款100
            if (account.getBalance() < 0) {
                System.out.println("☆" + Thread.currentThread().getName() + "   透支了!");
            }
        }
    }

    /**
     * makeWithdraw 账户取款
     * 
     * @param amount
     *            取款金额<br />
     *            打印log记录取款过程
     */
    private void makeWithdraw(int amount) {
        lock.lock();
        try {
            if (account.getBalance() >= amount) { // 如果余额足够则取款
                System.out.println("☆" + Thread.currentThread().getName() + "   准备取款!");
                Thread.sleep(500);
                account.withdraw(amount);
                System.out.println(
                        "☆" + Thread.currentThread().getName() + "   完成" + amount + "取款!余额为" + account.getBalance());
            } else { // 余额不足则提示
                System.out.println("☆" + "余额不足以支付" + Thread.currentThread().getName() + amount + "   的取款,余额为"
                        + account.getBalance());
            }
        } catch (InterruptedException e) {
            System.err.println("中断了请等待");
        } finally {
            lock.unlock();
        }

    }

}
复制代码

以上是关于java解决共享资源竞争的主要内容,如果未能解决你的问题,请参考以下文章

Swift新async/await并发中利用Task防止指定代码片段执行的数据竞争(Data Race)问题

Java编程思想——第21章 并发

互斥与同步

java 如何编写多线程的代码

试述死锁产生的原因和解决办法

解决共享资源竞争