Java 多线程同步与锁

时间:2021-06-25 13:03:51

线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏。例如一个银行账户同时被两个线程操作,一个取100块,一个存钱100块。如果取钱线程和存钱线程同时发生,就会出现异常。因此多线程同步就是要解决这个问题,如下面的代码

package com.example;
/**
* Created by Owen Chan on 16/3/14.
* Copyright © 2016 Owen Chan. All rights reserved.
*/

public class BankAccount {
private int amount = 0;
//deposit money
public void depositMoney(int money){
amount += money;
System.out.println(System.currentTimeMillis() + "deposit money : " + money);
}
//withdraw money
public void withdrawMoney(int money){
if(amount - money < 0){
System.out.println("account has no enough money");
return;
}
amount -= money;
System.out.println(System.currentTimeMillis() +"withdraw money : " + money);
}
//enquiries
public void enquiriesAccount(){
System.out.println("account last: "+amount);
}
}
package com.example;

public class MyClass {
public static void main(String args[]) {
final BankAccount bank = new BankAccount();
Thread tadd = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
bank.depositMoney(10);
bank.enquiriesAccount();
System.out.println("\n");
}
}
});

Thread tsub = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
bank.withdrawMoney(10);
bank.enquiriesAccount();
System.out.println("\n");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
});
tsub.start();
tadd.start();
}
}

输出结果:

account has no enough money

account last: 0

1457947900621deposit money : 10

1457947900621withdraw money : 10

account last: 0

account last: 0

从结果发现,这样的输出值明显是不合理的。原因是两个线程不加控制的访问BankAccount对象并修改其数据所致。

同步和锁定

Java中每个对象都有一个内置锁,当程序运行到非静态的synchronized同步方法上时,自动获得与正在执行代码类的当前实例有关的锁。如果一个线程获得该锁,就没有其他线程可以获得锁,直到第一个线程释放锁。

同步后的代码

package com.example;

/**
* Created by Owen Chan on 16/3/14.
* Copyright © 2016 Owen Chan. All rights reserved.
*/

public class BankAccount {

private int amount = 0; //账户余额

//deposit money
public synchronized void depositMoney(int money){
amount += money;
System.out.println(System.currentTimeMillis() + "deposit money : " + money);
}

//withdraw money
public synchronized void withdrawMoney(int money){
if(amount - money < 0){
System.out.println("account has no enough money");
return;
}
amount -= money;
System.out.println(System.currentTimeMillis() +"withdraw money : " + money);
}

//enquiries
public void enquiriesAccount(){
System.out.println("account last: "+amount);
}

}

输出结果如下:

account has no enough money
account last: 0
1457949577270deposit money : 10
account last: 10
1457949577270withdraw money : 10
account last: 0

静态方法同步

要同步静态方法,需要一个用于整个类对象的锁,这个对象是就是这个类(XXX.class)。
例如:

public static synchronized int setAccount(String name){
Xxx.name = name;
}
等价于
public static int setAccount(String name){
synchronized(Xxx.class){
Xxx.name = name;
}
}

线程死锁

当两个线程被阻塞,每个线程在等待另一个线程时就发生死锁。

public class DeadlockRisk { 
private static class Resource {
public int value;
}
private Resource resourceA = new Resource();
private Resource resourceB = new Resource();

public int read() {
synchronized (resourceA) {
synchronized (resourceB) {
return resourceB.value + resourceA.value;
}
}
}

public void write(int a, int b) {
synchronized (resourceB) {
synchronized (resourceA) {
resourceA.value = a;
resourceB.value = b;
}
}
}
}

假设read()方法由一个线程启动,write()方法由另外一个线程启动。读线程将拥有resourceA锁,写线程将拥有resourceB锁,两者都坚持等待的话就出现死锁。

线程同步总结

1、线程同步的目的是防止多个线程访问一个数据对象时,对数据的破坏
2、线程同步方法是可以通过锁来实现,每个对象都有切仅有一个锁,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他同步方法。
3、对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。
4、当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。
5、死锁是线程间相互等待锁锁造成的。