Account类:
package ThreadSafe;
public class Account {
private String account;
private double balance;
public Account() {
}
public Account(String account, double balance) {
this.account = account;
this.balance = balance;
}
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public void moneyOut(double money) {
double bal_before = this.getBalance();
double bal_after = bal_before - money;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.setBalance(bal_after);
}
}
线程类
package ThreadSafe;
import java.io.IOException;
public class AccountThread implements Runnable {
private Account act;
public AccountThread() {
}
public AccountThread(Account act) {
this.act = act;
}
public Account getAct() {
return act;
}
public void setAct(Account act) {
this.act = act;
}
@Override
public void run() {
this.act.moneyOut(5000);
System.out.println(Thread.currentThread().getName() + "取出成功,余额:" + this.act.getBalance());
}
}
调用方法:
public static void test28() {
Account act = new Account("act-1", 10000);
Thread t1 = new Thread(new AccountThread(act));
t1.setName("xxxxx");
Thread t2 = new Thread(new AccountThread(act));
t2.setName("yyyy");
t1.start();
t2.start();
}
解决方案:
public void moneyOut(double money) {
// 以下代码必须是线程同步的,不能并发;
// 一个线程把这里的代码全部执行完后,另一个线程才能进来
/**
* 线程同步机制的语法是
* synchronized(){
* 线程同步代码块
* }
* synchronized后面小括号中的是相当关键的,
* 这个数据必须是多线程共享的,才能达到多线程排队
* 假设有t1,t2,t3,t4,t5,5个线程,
* 想让t1,t2,t3,3个线程排队,t4,t5不需要排队,
* 小括号中一定要写一个对t1,t2,t3是共享的对象,这个对象对t4,t5是不共享的
* 这里的共享对象是账户对象,就是this
*/
synchronized (this) {
double bal_before = this.getBalance();
double bal_after = bal_before - money;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.setBalance(bal_after);
}
}
原理说明:
? ? ? ? 在JAVA语言中,任何一个对象都有一把锁,其实这个锁就是标记(只是叫做锁);
假设t1和t2线程并发,开始执行以上代码的时候,肯定会有一个先执行一个后执行,假设t1先执行了,遇到了synchronized,这个时候t1就会自动找到后面共享对象的对象锁,之后占有这把锁,然后执行同步代码块中的程序,直到同步代码块中的程序执行完毕,释放这把锁,期间一直占用这把锁;
假设t1已经占有这把锁,t2也遇到了synchronized关键字,也会去占有后面这个共享对象的对象锁,此时t1已经占有这把锁,t2只能在同步代码块外面等待,直到t1把同步代码块执行完毕,t1归还这把锁后,t2才能占有这把锁,之后t2才能执行同步代码块中的程序,这样就达到了线程排队执行;
其中这个共享对象是最关键的,必须是需要排队的线程对象所共享的;
例子中的共享对象也可以是account实例里面的任何一个元素,synchronized只要传入线程的共享对象,就能触发它的占用锁机制;
//这样写就不是线程安全了,因为会new多个对象,不是同一个
Object obj2 = new Object();
synchronized (obj2) {
....
}
//这样写是线程安全的,因为abc在常量池里面,只存在一个,是共享对象,但是会导致所有线程同步
synchronized ("abc") {
...
}
t1与t2同步,与t3不同步
synchronized (this) {
double bal_before = this.getBalance();
double bal_after = bal_before - money;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.setBalance(bal_after);
}
public static void test28() {
Account act = new Account("act-1", 10000);
Account act2 = new Account("act-2", 10000);
Thread t1 = new Thread(new AccountThread(act));
t1.setName("xxxxx");
Thread t2 = new Thread(new AccountThread(act));
t2.setName("yyyy");
//此时t1与t2同步,与t3不同步
Thread t3 = new Thread(new AccountThread(act2));
t2.setName("zzzzz");
t1.start();
t2.start();
t3.start();
}
//如果传入的是abc,那么t1,t2,t3是同步的
synchronized ("abc") {}
synchronized范围越大,效率越低;
下面这样写也可以,但是会降低效率;
public void run() {
synchronized (act) {
this.act.moneyOut(5000);
System.out.println(Thread.currentThread().getName() + "取出成功,余额:" + this.act.getBalance());
}
}
?实例方法上的synchronized,此时共享对象必须是this,不能是其他对象,缺乏灵活,并且synchronized出现在方法体上,表示整个方法需要同步,可能会无故扩大同步范围,导致程序的执行效率降低,所以这种方式不常用;
如果共享对象是this,并且整个方法需要同步,此时可以用synchronized修饰方法体,可以节简代码;
public synchronized void moneyOut(double money) {
...
}
?synchronized修饰在静态变量上,就表示占用类锁,虽然对象不同,但是对象都属于同一个类,类锁被占用了,其他对象都要等待锁的释放:
public class ClassTest {
public synchronized static void doSome() {
System.out.println("doSome begin");
try {
Thread.sleep(5 * 1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("doSome end");
}
public synchronized static void doOther() {
System.out.println("doOther begin");
System.out.println("doOther end");
}
}
public class ClassThreadTest implements Runnable {
ClassTest ct;
public ClassThreadTest(ClassTest ct) {
this.ct = ct;
}
@Override
public void run() {
// TODO Auto-generated method stub
if (Thread.currentThread().getName() == "t1") {
this.ct.doSome();
} else {
this.ct.doOther();
}
}
}
public static void test29() {
ClassTest ct = new ClassTest();
ClassTest ct2 = new ClassTest();
Thread t1 = new Thread(new ClassThreadTest(ct));
t1.setName("t1");
Thread t2 = new Thread(new ClassThreadTest(ct2));
t2.setName("t2");
t1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
t2.start();
}
以上的锁也称为排他锁;
|