首页 > 编程语言 >阅读《java并发编程实战》第十章

阅读《java并发编程实战》第十章

时间:2023-05-28 11:45:54浏览次数:44  
标签:java synchronized void 编程 第十章 amount location new public

例1: 简单的加锁顺序导致的死锁:

public class LeftRightDeadlock {
    private final Object left = new Object();
    private final Object right = new Object();
    
    public void leftRight() {
        synchronized (left) {
            synchronized (right) {
                System.out.println("do something");
            }
        }
    }
            
    public void rightLeft() {
        synchronized (right) {
            synchronized (left) {
                System.out.println("do something else");
            }
        }
    }
}

例2: 动态加锁顺序,导致的死锁:

class Account {
    private BigDecimal balance = new BigDecimal("1000");

    BigDecimal getBalance() {
        return balance;
    }

    public void debit(BigDecimal amount) {
        this.balance.subtract(amount);
    }

    public void credit(BigDecimal amount) {
        this.balance.add(amount);
    }
}

class InsufficientFundsException extends Exception {
}

public class DynamicTransferDeadlock {
    public static void transfer(Account from, Account to, BigDecimal amount) throws InsufficientFundsException {
        synchronized (from) {
            synchronized (to) {
                if (from.getBalance().compareTo(amount) < 0) {
                    throw new InsufficientFundsException();
                } else {
                    from.debit(amount);
                    to.credit(amount);
                }
            }
        }
    }

    // demo deadlock
    public static void main(String[] args) {
        final int numAccounts = 5;
        final int numTransactions = 20;
        final int numThreads = 20;
        final Random random = new Random();
        final Account[] accounts = new Account[numAccounts];
        for (int i = 0; i < numAccounts; i++) {
            accounts[i] = new Account();
        }
        Runnable transTask = () -> {
            for (int i = 0; i < numTransactions; i++) {
                int from = random.nextInt(numAccounts);
                int to = random.nextInt(numAccounts);
                BigDecimal amount = BigDecimal.valueOf(random.nextInt(1000));
                try {
                    transfer(accounts[from], accounts[to], amount);
                } catch (InsufficientFundsException e) {
                    throw new RuntimeException(e);
                }
            }
        };

        for (int i = 0; i < numThreads; i++) {
            new Thread(transTask).start();
        }
    }
}

运行之后发生死锁,使用jps查看进程号,然后jstack + 进程号打印线程堆栈信息,能看到检测到死锁。原因是:如果账户A 和 B 相互给对方转账,那么就会造成死锁。transfer方法的参数,它传入的参数顺序,影响了加锁的顺序。

例3:通过指定加锁顺序避免死锁

    public static Object tieLock = new Object();

    public static void safeTransfer(Account from, Account to, BigDecimal amount) throws InsufficientFundsException {
        int hashFrom = System.identityHashCode(from);
        int hashTo = System.identityHashCode(to);
        if (hashFrom < hashTo) {
            synchronized (from) {
                synchronized (to) {
                    helperTransfer(from, to, amount);
                }
            }
        } else if (hashFrom > hashTo){
            synchronized (to) {
                synchronized (from) {
                    helperTransfer(from, to, amount);
                }
            }
        } else { // same.
            synchronized (tieLock) {
                synchronized (from) {
                    synchronized (to) {
                        helperTransfer(from, to, amount);
                    }
                }
            }
        }
    }

    private static void helperTransfer(Account from, Account to, BigDecimal amount) throws InsufficientFundsException {
        if (from.getBalance().compareTo(amount) < 0) {
            throw new InsufficientFundsException();
        } else {
            from.debit(amount);
            to.credit(amount);
        }
    }

例4:两个对象之间协作,互相持有对方的锁,造成死锁。比较隐晦,难以发现。

class Point {}
class Image {
    public void drawMarker(Point location) {
        System.out.println("Driver in location: " + location);
    }
}
class Dispatcher {
    private final Set<Taxi> taxis = new HashSet<>();
    private final Set<Taxi> availableTaxis = new HashSet<>();

    public synchronized void notifyAvailable(Taxi taxi) {
        this.availableTaxis.add(taxi);
    }

    public synchronized Image getImage() {
        Image image = new Image();
        for (Taxi t : taxis) {
            image.drawMarker(t.getLocation());
        }
        return image;
    }
}

class Taxi {
    private Point location, destination;
    private final Dispatcher dispatcher;

    public Taxi(Dispatcher dispatcher) {
        this.dispatcher = dispatcher;
    }

    public synchronized Point getLocation() {
        return location;
    }

    public synchronized void setLocation(Point location) {
        this.location = location;
        if (location.equals(destination)) {
            dispatcher.notifyAvailable(this);
        }
    }
}

分析:Taxi的setLocation方法是同步的,需要获取Taxi的锁,然后内部调用dispatch.notifyAvailable()这个也是同步方法,需要获取dispatch实例的锁。

然后Dispatcher的getImage方法,是同步的,先获取dispatch实例的锁,然后内部执行时,调用t.getLocation(),因此又要获取taxi实例的锁。从而多线程下,同时执行 Taxi.setLocation() 和 Dispatcher.getImage() 方法有很大可能会造成死锁。

例5:两个对象之间协作导致死锁,例4的优化方案,通过公开调用来消除死锁

class Point {}
class Image {
    public void drawMarker(Point location) {
        System.out.println("Driver in location: " + location);
    }
}
class Dispatcher {
    private final Set<Taxi> taxis = new HashSet<>();
    private final Set<Taxi> availableTaxis = new HashSet<>();

    public synchronized void notifyAvailable(Taxi taxi) {
        this.availableTaxis.add(taxi);
    }

    public Image getImage() {
        Set<Taxi> copy;
        synchronized (this) {
             copy = new HashSet<>(taxis);
        }

        Image image = new Image();
        for (Taxi t : copy) {
            image.drawMarker(t.getLocation());
        }
        return image;
    }
}

class Taxi {
    private Point location, destination;
    private final Dispatcher dispatcher;

    public Taxi(Dispatcher dispatcher) {
        this.dispatcher = dispatcher;
    }

    public synchronized Point getLocation() {
        return location;
    }

    public void setLocation(Point location) {
        boolean reachDestination;
        synchronized (this) {
            this.location = location;
            reachDestination = location.equals(destination);
        }
        if (reachDestination) {
            dispatcher.notifyAvailable(this);
        }
    }
}

分析:Dispatcher的getImage方法变成非同步方法了,只是在内部加了一个同步块。这种方法叫开放调用,不需要首先获取对象的锁。在方法内部是按需获取。Taxi的setLocation也是同理。

标签:java,synchronized,void,编程,第十章,amount,location,new,public
From: https://www.cnblogs.com/xianzhon/p/17437997.html

相关文章

  • Java:Jenv多版本管理工具(自由切换Java版本)
    文档https://www.jenv.be/https://github.com/jenv/jenv安装gitclonehttps://github.com/jenv/jenv.git~/.jenv编辑配置文件vim~/.bash_profile写入环境变量#jenvexportPATH="$HOME/.jenv/bin:$PATH"eval"$(jenvinit-)"#重启shellexec$SHELL-l......
  • 优先级队列的实现详解( Java 实现)
    前言优先级队列是在队列的基础上,每个元素都带有一个优先级,可以实现按照优先级高低进行存储和访问。Java提供了许多实现优先级队列的方法,例如使用堆来实现。在本篇博客中,我将介绍Java实现优先级队列实现的具体方法,以及如何使用它来解决实际问题。一、优先级队列的概念优先级队列......
  • java XML字符串和bean实体类互转
    pom引入依赖<dependency><groupId>com.fasterxml.jackson.dataformat</groupId><artifactId>jackson-dataformat-xml</artifactId><version>2.13.1</version></dependency>实体类p......
  • 用Java语言springboot框架开发工艺管理系统
    技术架构技术框架:SpringBoot2.0.0+Mybatis1.3.2+Shiro+jpa+lombok+Vue2+Mysql5.7+redis+nodejs16运行环境:jdk8+IntelliJIDEA+maven+宝塔面板宝塔部署教程回到IDEA,点击编辑器右侧maven图标,切换至prod,执行package,完成后就会在根目录里生成一个target目录,......
  • Java语言实现的springBoot汽车销售管理系统vue前端
    技术架构技术框架:springboot+mybatis+Mysql5.7+vue2+npm+node运行环境:jdk8+IntelliJIDEA+maven+宝塔面板宝塔部署教程解析一个域名,使用vscode打开front目录,修改/config/prod.env.js文件里的BASE_API字段为解析好的线上域名,执行npmrunbuild:prod打包出......
  • java 面试题目
    1:子类和父类的实例变量和方法有什么区别?2:重载和覆盖的区别,返回类型不同,可以重载吗?为什么?底层如何实现的?3:抽象类与接口的区别4:悲观锁和乐观锁5:线程安全的解决方法有哪些?读写锁6:hashcode和equals?7:java泛型8:ThreadLocal,Concurrent下面的包,原理?9:AtomicInteger原理是什......
  • 用Java语言和Springboot框架实现宿舍管理系统
    技术架构技术框架:SpringBoot+SpringMVC+MyBatis+Layui+Mysql5.7+Axios+Echarts+POI运行环境:jdk8+IntelliJIDEA+maven+宝塔面板宝塔部署教程回到IDEA,点击编辑器右侧maven图标,执行package,完成后就会在根目录里生成一个target目录,在里面会打包出一个jar文件......
  • java面试 关于红黑树
    红黑树(Red-BlackTree):是一种自平衡的二叉搜索树,它在实际的软件开发中广泛应用。红黑树的特点是具有高效的插入、删除和查找操作,并且保持树的平衡,以保证这些操作的时间复杂度为O(logn)。红黑树与AVL树有什么区别?红黑树和AVL树都是自平衡的二叉搜索树,但它们在维护平衡方......
  • 阅读《java并发编程实战》第五章
    阅读《java并发编程实战》第五章Semaphore的应用举例Semaphore的应用举例:实现一个固定大小的Set。当容器满了之后,无法add,线程阻塞。publicclassBoundedHashSet{//invariant:sizeofSetalwayslessthanorequaltogivensizeprivatefinalSet<Integer>s......
  • java面试 (12)- Valiolate原理?是线程安全的吗?
    1:导致线程问题的原因:抢占式执行多个线程同时修改了同一个变量非原子性操作内存可见性问题指令重排问题2:并发编程三大特性可见性原子性有序性3:volatile关键字3.1volatile解决了内存可见性和指令重排序的问题写volatile变......