线程锁机制是为了线程间通信的互斥问题,这里我们先说说什么是互斥和同步。
互斥: 线程间的间接制约关系。线程A和线程B的执行都需要资源C,当线程A持有资源C的时候,线程B需要等到线程A执行完毕、释放资源C以后才能执行,这就是互斥关系。
同步:线程间的直接制约关系。线程A依赖于线程B,只有等B执行了某操作以后,线程A才能执行。
java的锁机制是通过synchronized关键字来实现的,它包括synchronized方法和synchronized块两种方式。
通过在方法声明中加入synchronized关键字来声明synchronized方法,语法如下。
public synchronized void process(){}
当多个线程访问同一个synchronized方法的时候,必须获取调用该方法的类实例的锁才能执行,否则所属线程被阻塞。方法一旦执行,就独占该锁,直到该方法返回才把锁释放。此后,其它被阻塞的线程才能获得锁,并执行。
这种机制,在同一时刻,对于同一个类实例,有且仅有一个声明为synchronized的成员方法被执行,不管这个类有多少synchronized方法。这带来了一个缺陷,若将一个运行时间较长的方法声明为synchronized,并且被调用,那么本类中其它的synchronized方法就不能运行。
Java提供了synchronized块来规避这个问题。
通过 synchronized 关键字来声明synchronized 块,语法如下:
synchronized(syncObject) { //允许访问控制的代码 }
synchronized 块必须获得syncObject对象的锁才能执行。由于可以针对任意代码块设置不同的上锁对象,因此 synchronized 块具有更高的灵活性。
下面给出一个代码示例,可以通过注释掉synchronized 来观察效果。
过年的时候大家都玩发红包,这里我们就以红包来做个示例,一共3个类,分别是
1.红包类(GiftMoney),包含了钱包数组,和钱包之间互相转账发红包的方法、获得所有钱包货币总额的方法。去掉synchronized后,可以通过观察控制台中输出的货币总额来体会synchronized的作用。
2.红包任务类(MoneyTransferTask),实现Runnable接口,调用发红包的方法。
3.测试类(Main),创建多个线程,执行发红包。
GiftMoney.java
/** * 红包系统, 过年了,大家都互相送红包(GiftMoney)。 * * 所有红包中的货币总量是不变的。 * * * 红包不会凭空创造或者消息,只会从一个人发给另一个人。 */ public class GiftMoney { // 钱包 private final double[] wallets; private final Object lockObj = new Object(); /** * * @param n 钱包的数量(一个钱包就是代表一个人) * @param initialMoney 每个钱包中里初始的钱数 */ public GiftMoney(int n, double initialMoney) { wallets = new double[n]; for (int i = 0; i < wallets.length; i++) { wallets[i] = initialMoney; } } /** * 发红包,钱从一个钱包转到另一个钱包 * * @param from 发红包的人 * @param to 收红包的人 * @param amount 红包钱数 */ public void transfer(int from, int to, double amount) { synchronized (lockObj) { while (wallets[from] < amount) { try { // 条件不满足,当前任务被放入Wait Set,释放CPU资源 lockObj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.print(Thread.currentThread().getName()); wallets[from] -= amount; System.out.printf("\t %d给%d发了%10.2f的红包。", from, to, amount); wallets[to] += amount; System.out.printf("货币总量:\t %10.2f%n", getTotalMoney()); // 唤醒lockObj上的所有等待线程 lockObj.notifyAll(); } } /** * 返回货币总量 */ public double getTotalMoney() { double sum = 0; for (int i = 0; i < wallets.length; i++) { sum += wallets[i]; } return sum; } /** * 获得钱包的总数 */ public int getWalletsLength() { return wallets.length; } }
MoneyTransferTask.java
public class MoneyTransferTask implements Runnable { // 红包系统 private GiftMoney gm; // 红包来源的钱包index private int fromWallet; // 每次可以发的红包金额最大值 private double maxAmount; public MoneyTransferTask(GiftMoney gm, int from, double max) { this.gm = gm; this.fromWallet = from; this.maxAmount = max; } public void run() { try { while (true) { //随机获取到收红包的toWallet, 随机产生红包中的金额amount int toWallet = (int) (gm.getWalletsLength() * Math.random()); double amount = maxAmount * Math.random(); //发红包 gm.transfer(fromWallet, toWallet, amount); Thread.sleep(3000); } } catch (InterruptedException e) { e.printStackTrace(); } } }
Main.java
public class Main{ // 一共100个钱包 public static final int WALLET_AMOUNT = 100; // 每个钱包中的初始金额是1000元 public static final double INITIAL_AMOUNT = 1000; public static void main(String[] args) { GiftMoney gm = new GiftMoney(WALLET_AMOUNT, INITIAL_AMOUNT); for (int i = 0; i < WALLET_AMOUNT; i++) { MoneyTransferTask mtt = new MoneyTransferTask(gm, i, INITIAL_AMOUNT); Thread t = new Thread(mtt, "TransferThread_" + i); t.start(); } } }