多线程引发的问题1:
银行类:
//银行类
public class Bank {
double money = 9000;
// 取款
public void drawMoney(String name, double num) {
if (num <= money) {
System.out.println(name + "取了" + num + "元还剩" + (money - num) + "元");
money -= num;
} else {
System.out.println(name + "取钱,钱不够...");
}
}
}
取款机:
//取款机
public class ATMRunnable implements Runnable {
private Bank bank;//从哪个银行取款
private String name;//取款人的名字
public ATMRunnable(Bank bank, String name) {
this.bank = bank;
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
bank.drawMoney(name, 1000);//为了更好的测试观察,循环取款3次
}
}
}
测试类:
//测试类
public final class Test {
public static void main(String[] args) {
Bank bank = new Bank();
Thread t1 = new Thread(new ATMRunnable(bank, "1张三"));
Thread t2 = new Thread(new ATMRunnable(bank, "2李四"));
Thread t3 = new Thread(new ATMRunnable(bank, "3王五"));
t1.start();
t2.start();
t3.start();
}
}
运行,观察输出的结果!
24.1 多线程访问临界资源时的数据安全问题
多线程的同步机制对资源进行加锁,使得在同一个时间,只有一个线程可以进行操作,同步用以解决多个线程同时访问同一块资源时可能出现的问题。
同步机制可以使用synchronized关键字实现。
24.1.1 synchronized关键字
synchronized(同步的)。
理解:什么是同步?什么是异步?
问,以下哪种情况是同步?哪种情况是异步?:
张三和李四在工地的空地上搬砖,两个人同时搬一堆砖(难免会抢到同一块砖)。(异步:各不相干,一块内存同一时刻可以有多个线程访问)
张三和李四在工地的一个小房间里搬砖出来(小房间门的大小只能容许一个人进出),两个人同时搬一堆砖(同一时间只有一个人在搬)。(同步:有先后顺序,一块内存同一时刻只有一个线程访问)
答案是?
synchronized关键字的作用域有2种:
作用与方法:synchronized (static) void aMethod(){};
作用于方法中的某个区块中: synchronized(this){/区块/};
被synchronized修饰的代码同一时刻只能被一个线程所访问。
24.1.2 同步方法
当synchronized关键字修饰一个方法的时候,该方法叫做同步方法。
当synchronized方法执行完或发生异常时,会自动释放锁。
通过在方法声明中加入 synchronized关键字来声明 synchronized 方法。如:
public synchronized void accessVal(int newVal){
//允许访问控制的代码
}
- synchronized 方法控制对类成员变量的访问:每个类实例对应一把锁,每个 synchronized 方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例(对象),其所有声明为 synchronized 的成员函数中至多只有一个处于可执行状态(因为至多只有一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明为synchronized)。
注意:synchronized 方法的缺陷:若将一个大的方法声明为synchronized 将会大大影响效率,典型地,若将线程类的方法 run()声明为 synchronized ,由于在线程的整个生命期内它一直在运行,因此将导致它对本类任何 synchronized 方法的调用都永远不会成功。当然我们可以通过将访问类成员变量的代码放到专门的方法中,将其声明为 synchronized ,并在主方法中调用来解决这一问题,但是 Java 为我们提供了更好的解决办法,那就是 synchronized 块。
24.1.3 同步'成员方法内部'代码块(锁对象)
通过 synchronized关键字来声明 synchronized 块(成员方法内部)。语法如下:
synchronized(syncObject)
{
//允许访问控制的代码
}
当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的除synchronized(this)同步代码块以外的部分。
以上规则对其它对象锁同样适用(即,可以把 this 换成其他对象)。
24.1.4 同步静态方法
在 Java 中,不光是类实例,每一个类也对应一把锁,这样我们也可将类的静态成员函数声明为 synchronized ,以控制其对类的静态成员变量的访问。
将synchronized作用于static 函数,示例代码如下:
public class Foo
{
public synchronized static void methodAAA(){ // 同步的static 函数
//….
}
public static void methodBBB() {
synchronized(Foo.class){// class (类名称字面常量)
}
}
}
代码中的methodBBB()方法是把class作为锁的情况,它和同步的static函数产生的效果是一样的,取得的锁很特别,是当前调用这个方法的对象所属的类(Class,而不再是由这个Class产生的某个具体对象了)。
24.1.5 死锁
死锁:由多线程带来的性能改善是以可靠性为代价的,主要是因为有可能产生线程死锁。死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不能正常运行。简单的说就是:线程死锁时,第一个线程等待第二个线程释放资源,而同时第二个线程又在等待第一个线程释放资源。这里举一个通俗的例子:如在人行道上两个人迎面相遇,为了给对方让道,两人同时向一侧迈出一步,双方无法通过,又同时向另一侧迈出一步,这样还是无法通过。假设这种情况一直持续下去,这样就会发生死锁现象。
代码示例:
线程1:
public class DeadLock1 implements Runnable {
@Override
public void run() {
synchronized (MainIn.o1) {
System.out.println("线程1----获得 o1 锁");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (MainIn.o2) {
System.out.println("线程1----获得 o2 锁");
}
}
}
}
线程2:
public class DeadLock2 implements Runnable {
@Override
public void run() {
synchronized (MainIn.o2) {
System.out.println("线程2----获得 o2 锁");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (MainIn.o1) {
System.out.println("线程2----获得 o1 锁");
}
}
}
}
测试类:
public class MainIn {
public static Object o1 = new Object(), o2 = new Object();
public static void main(String[] args) {
DeadLock1 deadLock1 = new DeadLock1();
DeadLock2 deadLock2 = new DeadLock2();
Thread t1 = new Thread(deadLock1);
Thread t2 = new Thread(deadLock2);
t1.start();
t2.start();
}
}