Java基础教程——线程同步

线程同步

synchronized:同步的

例:取钱

不做线程同步的场合,假设骗子和户主同时取钱,可能出现这种情况:

  • 【骗子】取款2000:账户余额1000
  • 【户主】取款2000:账户余额1000
  • 结果是社会财富增加1000,银行不开心。

代码如下所示:

// 账户类
class Account {
	private int accountBalance = 2000;
	public void withdraw(String userName, int amount) {
		System.out.println(userName + "===in===");
		if (accountBalance >= amount) {
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			accountBalance -= amount;// 取钱
			System.out.println(userName + "取款" + amount + ",余额:" + accountBalance);
		} else {
			System.out.println(userName + "企图取款" + amount + ",但余额只有:" + accountBalance);
		}
		System.out.println(userName + "===out===");
	}
}
class MyThread extends Thread {
	@Override
	public void run() {
		取钱Demo.act.withdraw(getName(), 1000);
	}
}
public class 取钱Demo {
	static Account act = new Account();// 账户就一份
	public static void main(String[] args) {
		MyThread t1 = new MyThread();
		MyThread t2 = new MyThread();
		t1.setName("户主");
		t2.setName("骗子");
		t2.start();
		t1.start();
	}
}

想要银行开心,就需要对线程进行同步处理,避免出现重复取款的情况。


线程同步

方法一:锁对象

	public void withdraw(String userName, int amount) {
		synchronized (this) {
          ……
        }
	}

如果是静态方法,没有this,则是锁住【类名.class】

	public static void withdraw(String userName, int amount) {
		synchronized (Account.class) {
          ……
        }
	}

方法二:锁方法

当方法被调用时,调用线程必须获得当前对象的锁,否则将等待下去。
方法结束后,锁会被释放。

public synchronized void withdraw(String userName, int amount) {...}

方法三:ReentrantLock重入锁

ReentrantLock是java.util.concurrent.locks.Lock接口的一个实现类。(reentrant:[rɪ'entrənt]再进去)

一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大(可以中断、定时)。

API文档上建议的用法:

建议总是 立即实践,使用 lock 块来调用 try,在之前/之后的构造中,最典型的代码如下: 

 class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...

   public void m() { 
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }
 }

应用示例:

import java.util.concurrent.locks.ReentrantLock;
// 账户类
class Account {
	private int accountBalance = 2000;
	private ReentrantLock lock = new ReentrantLock();
	public void withdraw(String userName, int amount) {
		synchronized (Account.class) {
			lock.lock();
			try {
				System.out.println(userName + "===in===");
				if (accountBalance >= amount) {
					Thread.sleep(500);
					accountBalance -= amount;// 取钱
					System.out.println(userName + "取款" + amount + ",余额:" + accountBalance);
				} else {
					System.out.println(userName + "企图取款" + amount + ",但余额只有:" + accountBalance);
				}
				System.out.println(userName + "===out===");
			} catch (InterruptedException e) {
				e.printStackTrace();
			} finally {
				lock.unlock();
			}
		}
	}
}

练习:买票

(未做线程同步,请实现线程同步)

import java.util.Random;
public class 卖票 {
	public static void main(String[] args) {
		// 一个Runnable实例对象
		SellTicket st = new SellTicket();
		// 创建三个线程对象
		Thread t1 = new Thread(st, "窗口1");
		Thread t2 = new Thread(st, "窗口2");
		Thread t3 = new Thread(st, "窗口3");
		// 启动线程
		t1.start();
		t2.start();
		t3.start();
	}
}
class SellTicket implements Runnable {
	// 定义票数
	private int tickets = 100;
	private void sell() {
		if (tickets > 0) {
			// 模拟售票过程
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			String name = Thread.currentThread().getName();
			System.out.println(name + "正在出售第" + (tickets--) + "张票");
		}
	}
	@Override
	public void run() {
		while (tickets > 0) {
			sell();
			// 模拟空闲过程
			try {
				Thread.sleep(new Random().nextInt(11) * 100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}
posted @ 2019-07-13 08:48  虎老狮  阅读(210)  评论(0编辑  收藏  举报