显示锁,隐式锁 (管程、monitor 、操作系统)

发布时间 2023-08-03 19:51:50作者: zno2

无锁时并发出问题

import java.util.*;

/**
 * 无锁
 * @author witas
 *
 */
public class Bank {
    
    
    public static void main(String[] args) {
        
        final int NACCOUNTS = 100;
        final double INITIAL_BALANCE = 1000;
        final double MAX_AMOUNT = 1000;
        final int DELAY = 10;
        
        Bank bank = new Bank(NACCOUNTS, INITIAL_BALANCE);
        for (int i = 0; i < NACCOUNTS; i++) {
            int fromAccount = i;
            Runnable r = () -> {
                try {
                    while (true) {
                        int toAccount = (int) (bank.size() * Math.random());
                        double amount = MAX_AMOUNT * Math.random();
                        bank.transfer(fromAccount, toAccount, amount);
                        Thread.sleep((int) (DELAY * Math.random()));
                    }
                } catch (InterruptedException e) {
                }
            };
            Thread t = new Thread(r,String.format("[线程%3d]", i));
            t.start();
        }
    }
    
    
    private final double[] accounts;

    /**
     * @param n              账户数
     * @param initialBalance 每个账户的余额
     */

    public Bank(int n, double initialBalance) {
        accounts = new double[n];
        Arrays.fill(accounts, initialBalance);
    }

    /**
     * 转账
     * 
     * @param from
     * @param to
     * @param amount 转账金额
     */

    public void transfer(int from, int to, double amount) {
        if (accounts[from] < amount) {
            return;
        }
        accounts[from] -= amount;
        accounts[to] += amount;
        System.out.printf("%s[%2d->%2d,%10.2f][总余额:%10.2f]%n", Thread.currentThread().getName(), from, to, amount, getTotalBalance());
    }

    /**
     * 获取所有账户总余额
     * 
     * @return
     */

    public double getTotalBalance() {
        double sum = 0;
        for (double a : accounts) {
            sum += a;
        }
        return sum;
    }

    /**
     * 获取账户个数
     * 
     * @return
     */
    public int size() {
        return accounts.length;
    }
}

 

通过concurrent 包下工具类可重入锁

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

/**
 * 显示锁
 * @author witas
 *
 */
public class Bank2 {
    
    
    public static void main(String[] args) {
        
        final int NACCOUNTS = 100;
        final double INITIAL_BALANCE = 1000;
        final double MAX_AMOUNT = 1000;
        final int DELAY = 10;
        
        Bank2 bank = new Bank2(NACCOUNTS, INITIAL_BALANCE);
        for (int i = 0; i < NACCOUNTS; i++) {
            int fromAccount = i;
            Runnable r = () -> {
                try {
                    while (true) {
                        int toAccount = (int) (bank.size() * Math.random());
                        double amount = MAX_AMOUNT * Math.random();
                        bank.transfer(fromAccount, toAccount, amount);
                        Thread.sleep((int) (DELAY * Math.random()));
                    }
                } catch (InterruptedException e) {
                }
            };
            Thread t = new Thread(r,String.format("[线程%3d]", i));
            t.start();
        }
    }
    
    private ReentrantLock lock = new ReentrantLock();
    
    private final double[] accounts;

    /**
     * @param n              账户数
     * @param initialBalance 每个账户的余额
     */

    public Bank2(int n, double initialBalance) {
        accounts = new double[n];
        Arrays.fill(accounts, initialBalance);
    }

    /**
     * 转账
     * 
     * @param from
     * @param to
     * @param amount 转账金额
     */

    public void transfer(int from, int to, double amount) {
        lock.lock();
        try {
            if (accounts[from] < amount) {
                return;
            }
            accounts[from] -= amount;
            accounts[to] += amount;
            System.out.printf("%s[%2d->%2d,%10.2f][总余额:%10.2f]%n", Thread.currentThread().getName(), from, to, amount, getTotalBalance());
        } finally {
            lock.unlock();
        }
    }

    /**
     * 获取所有账户总余额
     * 
     * @return
     */

    public double getTotalBalance() {
        double sum = 0;
        for (double a : accounts) {
            sum += a;
        }
        return sum;
    }

    /**
     * 获取账户个数
     * 
     * @return
     */
    public int size() {
        return accounts.length;
    }
}

 

通过concurrent包下可重入锁和等待条件

import java.util.*;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 显示锁,带条件
 * @author witas
 *
 */
public class Bank3 {
    
    
    public static void main(String[] args) {
        
        final int NACCOUNTS = 100;
        final double INITIAL_BALANCE = 1000;
        final double MAX_AMOUNT = 1000;
        final int DELAY = 10;
        
        Bank3 bank = new Bank3(NACCOUNTS, INITIAL_BALANCE);
        for (int i = 0; i < NACCOUNTS; i++) {
            int fromAccount = i;
            Runnable r = () -> {
                try {
                    while (true) {
                        int toAccount = (int) (bank.size() * Math.random());
                        double amount = MAX_AMOUNT * Math.random();
                        bank.transfer(fromAccount, toAccount, amount);
                        Thread.sleep((int) (DELAY * Math.random()));
                    }
                } catch (InterruptedException e) {
                }
            };
            Thread t = new Thread(r,String.format("[线程%3d]", i));
            t.start();
        }
    }
    
    private ReentrantLock lock = new ReentrantLock();
    private Condition sufficientFunds = lock.newCondition();
    
    private final double[] accounts;

    /**
     * @param n              账户数
     * @param initialBalance 每个账户的余额
     */

    public Bank3(int n, double initialBalance) {
        accounts = new double[n];
        Arrays.fill(accounts, initialBalance);
    }

    /**
     * 转账
     * 
     * @param from
     * @param to
     * @param amount 转账金额
     * @throws InterruptedException 
     */

    public void transfer(int from, int to, double amount) throws InterruptedException {
        lock.lock();
        try {
            while (accounts[from] < amount) {
                sufficientFunds.await();
            }
            accounts[from] -= amount;
            accounts[to] += amount;
            System.out.printf("%s[%2d->%2d,%10.2f][总余额:%10.2f]%n", Thread.currentThread().getName(), from, to, amount, getTotalBalance());
            sufficientFunds.signalAll();
        } finally {
            lock.unlock();
        }
    }

    /**
     * 获取所有账户总余额
     * 
     * @return
     */

    public double getTotalBalance() {
        double sum = 0;
        for (double a : accounts) {
            sum += a;
        }
        return sum;
    }

    /**
     * 获取账户个数
     * 
     * @return
     */
    public int size() {
        return accounts.length;
    }
}

 

synchronized 表达式

import java.util.Arrays;

/**
 * 隐式锁(statement)
 * @author witas
 *
 */
public class Bank4 {
    
    
    public static void main(String[] args) {
        
        final int NACCOUNTS = 100;
        final double INITIAL_BALANCE = 1000;
        final double MAX_AMOUNT = 1000;
        final int DELAY = 10;
        
        Bank4 bank = new Bank4(NACCOUNTS, INITIAL_BALANCE);
        for (int i = 0; i < NACCOUNTS; i++) {
            int fromAccount = i;
            Runnable r = () -> {
                try {
                    while (true) {
                        int toAccount = (int) (bank.size() * Math.random());
                        double amount = MAX_AMOUNT * Math.random();
                        bank.transfer(fromAccount, toAccount, amount);
                        Thread.sleep((int) (DELAY * Math.random()));
                    }
                } catch (InterruptedException e) {
                }
            };
            Thread t = new Thread(r,String.format("[线程%3d]", i));
            t.start();
        }
    }
    
    private Object lock = new Object();
    
    private final double[] accounts;

    /**
     * @param n              账户数
     * @param initialBalance 每个账户的余额
     */

    public Bank4(int n, double initialBalance) {
        accounts = new double[n];
        Arrays.fill(accounts, initialBalance);
    }

    /**
     * 转账
     * 
     * @param from
     * @param to
     * @param amount 转账金额
     */

    public void transfer(int from, int to, double amount) {
        synchronized(lock) {
            if (accounts[from] < amount) {
                return;
            }
            accounts[from] -= amount;
            accounts[to] += amount;
            System.out.printf("%s[%2d->%2d,%10.2f][总余额:%10.2f]%n", Thread.currentThread().getName(), from, to, amount, getTotalBalance());
        }
    }

    /**
     * 获取所有账户总余额
     * 
     * @return
     */

    public double getTotalBalance() {
        double sum = 0;
        for (double a : accounts) {
            sum += a;
        }
        return sum;
    }

    /**
     * 获取账户个数
     * 
     * @return
     */
    public int size() {
        return accounts.length;
    }
}

 

import java.util.Arrays;

/**
 * 隐式锁(statement),带条件
 * @author witas
 *
 */
public class Bank5 {
    
    
    public static void main(String[] args) {
        
        final int NACCOUNTS = 100;
        final double INITIAL_BALANCE = 1000;
        final double MAX_AMOUNT = 1000;
        final int DELAY = 10;
        
        Bank5 bank = new Bank5(NACCOUNTS, INITIAL_BALANCE);
        for (int i = 0; i < NACCOUNTS; i++) {
            int fromAccount = i;
            Runnable r = () -> {
                try {
                    while (true) {
                        int toAccount = (int) (bank.size() * Math.random());
                        double amount = MAX_AMOUNT * Math.random();
                        bank.transfer(fromAccount, toAccount, amount);
                        Thread.sleep((int) (DELAY * Math.random()));
                    }
                } catch (InterruptedException e) {
                }
            };
            Thread t = new Thread(r,String.format("[线程%3d]", i));
            t.start();
        }
    }
    
    
    private final double[] accounts;
    private Object lock = new Object();

    /**
     * @param n              账户数
     * @param initialBalance 每个账户的余额
     */

    public Bank5(int n, double initialBalance) {
        accounts = new double[n];
        Arrays.fill(accounts, initialBalance);
    }

    /**
     * 转账
     * 
     * @param from
     * @param to
     * @param amount 转账金额
     * @throws InterruptedException 
     */

    public void transfer(int from, int to, double amount) throws InterruptedException {
        synchronized(lock) {
            while (accounts[from] < amount) {
                lock.wait();
            }
            accounts[from] -= amount;
            accounts[to] += amount;
            System.out.printf("%s[%2d->%2d,%10.2f][总余额:%10.2f]%n", Thread.currentThread().getName(), from, to, amount, getTotalBalance());
            lock.notifyAll();
        }
    }

    /**
     * 获取所有账户总余额
     * 
     * @return
     */

    public double getTotalBalance() {
        double sum = 0;
        for (double a : accounts) {
            sum += a;
        }
        return sum;
    }

    /**
     * 获取账户个数
     * 
     * @return
     */
    public int size() {
        return accounts.length;
    }
}

 

synchronized 方法

import java.util.Arrays;

/**
 * 隐式锁(method)
 * @author witas
 *
 */
public class Bank6 {
    
    
    public static void main(String[] args) {
        
        final int NACCOUNTS = 100;
        final double INITIAL_BALANCE = 1000;
        final double MAX_AMOUNT = 1000;
        final int DELAY = 10;
        
        Bank6 bank = new Bank6(NACCOUNTS, INITIAL_BALANCE);
        for (int i = 0; i < NACCOUNTS; i++) {
            int fromAccount = i;
            Runnable r = () -> {
                try {
                    while (true) {
                        int toAccount = (int) (bank.size() * Math.random());
                        double amount = MAX_AMOUNT * Math.random();
                        bank.transfer(fromAccount, toAccount, amount);
                        Thread.sleep((int) (DELAY * Math.random()));
                    }
                } catch (InterruptedException e) {
                }
            };
            Thread t = new Thread(r,String.format("[线程%3d]", i));
            t.start();
        }
    }
    
    
    private final double[] accounts;

    /**
     * @param n              账户数
     * @param initialBalance 每个账户的余额
     */

    public Bank6(int n, double initialBalance) {
        accounts = new double[n];
        Arrays.fill(accounts, initialBalance);
    }

    /**
     * 转账
     * 
     * @param from
     * @param to
     * @param amount 转账金额
     */

    public synchronized void transfer(int from, int to, double amount) {
        if (accounts[from] < amount) {
            return;
        }
        accounts[from] -= amount;
        accounts[to] += amount;
        System.out.printf("%s[%2d->%2d,%10.2f][总余额:%10.2f]%n", Thread.currentThread().getName(), from, to, amount, getTotalBalance());
    }

    /**
     * 获取所有账户总余额
     * 
     * @return
     */

    public double getTotalBalance() {
        double sum = 0;
        for (double a : accounts) {
            sum += a;
        }
        return sum;
    }

    /**
     * 获取账户个数
     * 
     * @return
     */
    public int size() {
        return accounts.length;
    }
}

 

synchronized method wait 

import java.util.Arrays;

/**
 * 隐式锁(method),带条件 
 * @author witas
 *
 */
public class Bank7 {
    
    
    public static void main(String[] args) {
        
        final int NACCOUNTS = 100;
        final double INITIAL_BALANCE = 1000;
        final double MAX_AMOUNT = 1000;
        final int DELAY = 10;
        
        Bank7 bank = new Bank7(NACCOUNTS, INITIAL_BALANCE);
        for (int i = 0; i < NACCOUNTS; i++) {
            int fromAccount = i;
            Runnable r = () -> {
                try {
                    while (true) {
                        int toAccount = (int) (bank.size() * Math.random());
                        double amount = MAX_AMOUNT * Math.random();
                        bank.transfer(fromAccount, toAccount, amount);
                        Thread.sleep((int) (DELAY * Math.random()));
                    }
                } catch (InterruptedException e) {
                }
            };
            Thread t = new Thread(r,String.format("[线程%3d]", i));
            t.start();
        }
    }
    
    
    private final double[] accounts;

    /**
     * @param n              账户数
     * @param initialBalance 每个账户的余额
     */

    public Bank7(int n, double initialBalance) {
        accounts = new double[n];
        Arrays.fill(accounts, initialBalance);
    }

    /**
     * 转账
     * 
     * @param from
     * @param to
     * @param amount 转账金额
     * @throws InterruptedException 
     */

    public synchronized void transfer(int from, int to, double amount) throws InterruptedException {
        while (accounts[from] < amount) {
            this.wait();
        }
        accounts[from] -= amount;
        accounts[to] += amount;
        System.out.printf("%s[%2d->%2d,%10.2f][总余额:%10.2f]%n", Thread.currentThread().getName(), from, to, amount, getTotalBalance());
        this.notifyAll();
    }

    /**
     * 获取所有账户总余额
     * 
     * @return
     */

    public double getTotalBalance() {
        double sum = 0;
        for (double a : accounts) {
            sum += a;
        }
        return sum;
    }

    /**
     * 获取账户个数
     * 
     * @return
     */
    public int size() {
        return accounts.length;
    }
}

 

 

问:synchronized 加锁对象怎么判断?

 答:synchronized 实例方法 是this,synchronized 类方法是当前类,synchronized 表达式是被引用对象。同步方法分为实例方法和静态方法互不干扰,线程请求加锁对象的任意同步方法均需获得锁。

被synchronized 锁定的对象,里面的所有同步方法属一个“管程”(monitor 监视器),理解成所有同步方法会进入到监视器

 

 

问:锁是什么?

答:锁是管程中某线程执行的必要条件 

Monitors an operating system structuring concept.pdf.png 

这是一个附件,把png移除就是pdf文件。

 https://dl.acm.org/doi/pdf/10.1145/355620.361161

 

引申出一个概念“管程”,这是操作系统层面的东西,java 只是实现了这个东西,java的synchronized加锁和解锁是原子操作

管程 (Moniters,也称为监视器)
一.管程的概念
是一种程序结构,结构内的多个子程序(对象或模块)形成的多个工作线程互斥访问共享资源。这些共享资源一般是硬件设备或一群变量。管程实现了在一个时间点,最多只有一个线程在执行管程的某个子程序。
与那些通过修改数据结构实现互斥访问的并发程序设计相比,管程实现很大程度上简化了程序设计。
管程提供了一种机制,线程可以临时放弃互斥访问,等待某些条件得到满足后,重新获得执行权恢复它的互斥访问。即:在管程中的线程可以临时放弃管程的互斥访问,让其他线程进入到管程中来。
管程包含:多个彼此可以交互并共用资源的线程、多个与资源使用有关的变量、一个互斥锁、一个用来避免竞态条件的不变量。
一个管程的程序在运行一个线程前会先取得互斥锁,直到完成线程或是线程等待某个条件被满足才会放弃互斥锁。若每个执行中的线程在放弃互斥锁之前都能保证不变量成立,则所有线程皆不会导致竞态条件成立。
管程是一种高级的同步原语。任意时刻管程中只能有一个活跃进程。它是一种编程语言的组件,所以编译器知道它们很特殊,并可以采用与其他过程调用不同的方法来处理它们。典型地,当一个进程调用管程中的过程,前几条指令将检查在管程中是否有其他的活跃进程。如果有,调用进程将挂起,直到另一个进程离开管程。如果没有,则调用进程便进入管程。
对管程的实现互斥由编译器负责。在Java中,只要将关键字synchronized加入到方法声明中,Java保证一旦某个线程执行该方法,就不允许其他线程执行该方法,就不允许其他线程执行该类中的任何其他方法。
注意:管程是一个编程语言概念。编译器必须要识别出管程并用某种方式对互斥做出安排。C、Pascal及多数其他语言都没有管程,所以指望这些编译器来实现互斥规则是不可靠的。
管程可以看做一个软件模块,它是将共享的变量和对于这些共享变量的操作封装起来,形成一个具有一定接口的功能模块,进程可以调用管程来实现进程级别的并发控制。
进程只能互斥得使用管程,即当一个进程使用管程时,另一个进程必须等待。当一个进程使用完管程后,它必须释放管程并唤醒等待管程的某一个进程。
在管程入口处的等待队列称为入口等待队列,由于进程会执行唤醒操作,因此可能有多个等待使用管程的队列,这样的队列称为紧急队列,它的优先级高于等待队列。
二、管程的特征
1.模块化。
管程是一个基本的软件模块,可以被单独编译。
2.抽象数据类型。
管程中封装了数据及对于数据的操作,这点有点像面向对象编程语言中的类。
3.信息隐藏。
管程外的进程或其他软件模块只能通过管程对外的接口来访问管程提供的操作,管程内部的实现细节对外界是透明的。
4.使用的互斥性。
任何一个时刻,管程只能由一个进程使用。进入管程时的互斥由编译器负责完成。
三、enter过程、leave过程、条件型变量c、wait(c) 、signal(c)
1.enter过程
一个进程进入管程前要提出申请,一般由管程提供一个外部过程--enter过程。如Monitor.enter()表示进程调用管程Monitor外部过程enter进入管程。
2.leave过程
当一个进程离开管程时,如果紧急队列不空,那么它就必须负责唤醒紧急队列中的一个进程,此时也由管程提供一个外部过程—leave过程,如Monitor.leave()表示进程调用管程Monitor外部过程leave离开管程。
3.条件型变量c
条件型变量c实际上是一个指针,它指向一个等待该条件的PCB队列。如notfull表示缓冲区不满,如果缓冲区已满,那么将要在缓冲区写入数据的进程就要等待notfull,即wait(notfull)。相应的,如果一个进程在缓冲区读数据,当它读完一个数据后,要执行signal(notempty),表示已经释放了一个缓冲区单元。
4.wait(c)
wait(c)表示为进入管程的进程分配某种类型的资源,如果此时这种资源可用,那么进程使用,否则进程被阻塞,进入紧急队列。
5.signal(c)
signal(c)表示进入管程的进程使用的某种资源要释放,此时进程会唤醒由于等待这种资源而进入紧急队列中的第一个进程。