一. Java多线程安全问题,模拟银行存款
/**
*
*银行账户对象,存取款操作
*
*/
public class Account {
String name;
float amount;
public Account(String name, float amount) {
this.name = name;
this.amount = amount;
}
// 存款
public void deposit(float amt) {
float tmp = amount;
tmp += amt;
try {
Thread.sleep(100);//模拟其它处理所需要的时间,比如刷新数据库等
} catch (InterruptedException e) {
// ignore
}
amount = tmp;
}
// 取款
public void withdraw(float amt) {
float tmp = amount;
tmp -= amt;
try {
Thread.sleep(100);//模拟其它处理所需要的时间,比如刷新数据库等
} catch (InterruptedException e) {
// ignore
}
amount = tmp;
}
public float getBalance() {
return amount;
}
}
/**
*
* 多线程模拟银行存取款,如果没有采取同步,就会出现多线程安全问题,
* 则最终的银行账户会出现金额不一致的情况。
*
*/
public class AccountTest {
private static int NUM_OF_THREAD = 1000;
static Thread[] threads = new Thread[NUM_OF_THREAD];
public static void main(String[] args){
final Account acc = new Account("John", 1000.0f);
for (int i = 0; i< NUM_OF_THREAD; i++) {
threads[i] = new Thread(new Runnable() {
public void run() {
acc.deposit(100.0f);
acc.withdraw(100.0f);
}
});
threads[i].start();
}
for (int i=0; i<NUM_OF_THREAD; i++){
try {
threads[i].join(); //等待所有线程运行结束
} catch (InterruptedException e) {
// ignore
}
}
System.out.println("Finally, John's balance is:" + acc.getBalance());
}
}
二、Java多线程之synchronized
/**
*
* synchronized中同步的对象必须是多线程中访问的同一个对象,否则达不到同步效果,
* 也可用synchronized(this)代表当前对象。
* 如果为synchronized(name)则达不到同步效果。
*
*/
public class Foo extends Thread {
private int val;
// 通过对公共对象静态变量加锁
private static Object lock = new Object();
public Foo(int v) {
val = v;
}
// 交叉输出(1,3)
public synchronized void printVal1(int v) {
while(true){
System.out.println(v);
}
}
// 类同步,实现printVal是断面,输出只能是1或者只能是3而不能是两者同时出现
public void printVal2(int v)
{
synchronized(Foo.class) {
while(true){
System.out.println(v);
}
}
}
// 同步的一般原理是应该尽量减小同步的粒度以到达更好的性能,比类同步(printVal2)效果更忧
public void printVal3(int v)
{
synchronized(lock) {
while(true){
System.out.println(v);
}
}
}
public void run() {
// printVal1(val);
// printVal2(val);
printVal3(val);
}
}
public class FooTest {
public static void main(String args[]){
Foo f1 = new Foo(1);
f1.start();
Foo f2 = new Foo(3);
f2.start();
}
}
三. Java多线程之ThreadLocal
事例一:
/**
*
*在Java的多线程编程中,为保证多个线程对共享变量的安全访问,通常会使用synchronized来保证同一时刻
*只有一个线程对共享变量进行操作。但在有些情况下,synchronized不能保证多线程对共享变量的正确读写。
*对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”
*的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,
*因此可以同时访问而互不影响。
*/
public class QuerySvc {
private String sql;
private static ThreadLocal<String> sqlHolder = new ThreadLocal<String>();
public QuerySvc() {
}
/**
* 从运行结果可以看出sql变量中值不能保证在execute中值和set设置的值一样, 在web应用中就表现
* 为一个用户查询的结果不是自己的查询条件返回的结果,而是另一个用户查询条件的结果;
* 而ThreadLocal中的值总是和set中设置的值一样,这样通过使用ThreadLocal获得了线程安全性。
*/
public void execute() {
System.out.println("Thread " + Thread.currentThread().getId()
+ " Sql is " + sql);
System.out.println("Thread " + Thread.currentThread().getId()
+ " Thread Local variable Sql is " + sqlHolder.get());
}
public String getSql() {
return sql;
}
public void setSql(String sql) {
this.sql = sql;
sqlHolder.set(sql);
}
private static class Work extends Thread{
private QuerySvc querySvc;
private String sql;
public Work(QuerySvc querySvc,String sql){
this.querySvc = querySvc;
this.sql = sql;
}
public void run() {
querySvc.setSql(sql);
querySvc.execute();
}
}
public static void main(String args[]){
QuerySvc querySrc = new QuerySvc();
for(int k=0;k<10;k++){
String sql = "Select * from table where id =" + k;
new Work(querySrc,sql).start();
}
}
}
事例二:
/**
* 当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
* 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
* 如果要保存多个线程变量,可定义多个ThreadLocal变量,
* 或者将多个变量封装到一个对象中,将整个对象保存到ThreadLocal变量。
*
*/
public class SequenceNumber {
private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){
public Integer initialValue(){
return 0;
}
};
public Integer getNextNum(){
seqNum.set(seqNum.get()+1);
return seqNum.get();
}
// 测试方法
public static void main(String args[]){
SequenceNumber sn = new SequenceNumber();
// 3个线程共享sn,各自产生序列号
TestClient t1 = new TestClient(sn);
TestClient t2 = new TestClient(sn);
TestClient t3 = new TestClient(sn);
t1.start();
t2.start();
t3.start();
}
// 内部静态线程类
private static class TestClient extends Thread{
private SequenceNumber sn;
public TestClient(SequenceNumber sn) {
this. sn = sn;
}
public void run()
{
//每个线程打出3个序列值
for (int i = 0; i < 3; i++) {
System.out.println("thread["+Thread.currentThread().getName()+
"] sn["+sn.getNextNum()+"]");
}
}
}
}