Java线程同步是服务器技术的基础问题。大型网络服务器/应用服务器/数据库等均包含复杂的多线程实现。
这段代码的执行结果会像是下面这样:
,thread2:100
,thread2:100
,thread1:90
,thread1:100
,thread2:100
,thread2:90
,thread1:80
,thread1:90
,thread2:100
,thread2:90
,thread1:80
,thread1:90
原因前面已经说过了,因为无法保证两个函数能被互斥的访问。有一句名言,计算机领域的问题都可以通过引入一个中间层次得以解决,也就是说,为了解决这个问题,java1.5以后引入了新的包concurrent,这个包的内部去声明synchronized就行了。因此上面的代码会变成这样。这保证了没有Deposite或者WithDraw被同时执行造成潜在的数据冲突。多个线程每次只能执行一个Deposite或者WithDraw操作。
(1) 最常见的多线程同步的问题是和Singleton设计模式相关的。为了保证多线程在访问Singleton实例的时候没有多线程冲突,需要对代码进行保护。
- public class my {
- public static void main(String[] args) {
- my.Singleton.GetInstance();
- }
- static class Singleton{
- private static volatile Singleton inst=null;
- public static Singleton GetInstance(){
- if(inst==null){
- synchronized(Singleton.class){//JVM memory barrier
- inst=new Singleton();
- }
- }
- return inst;
- }
- Singleton(){System.out.println("ctor");}
- }
- }
public class my {
public static void main(String[] args) {
my.Singleton.GetInstance();
}
static class Singleton{
private static volatile Singleton inst=null;
public static Singleton GetInstance(){
if(inst==null){
synchronized(Singleton.class){//JVM memory barrier
inst=new Singleton();
}
}
return inst;
}
Singleton(){System.out.println("ctor");}
}
}
(2) Singleton的例子很简单,因为多线程保护集中在同一个函数当中。如果这种保护要跨越多个函数调用,那么"关键段"或者"synchronized"代码就失效了,如下所示。例如,对于银行ATM而言,"存款"和"取款"者两个函数调用是不能同时进行的,必须存在一个能管理全局的资源锁。
- public class ATM{
- int m_nAmount;
- ATM(int m){
- m_nAmount=m;
- }
- class ClientThread1 extends Thread{
- public void run(){
- try{
- while(true){
- Deposit(10);
- Thread.sleep(1000);
- System.out.println(",thread1:"+m_nAmount);
- }
- }catch(InterruptedException e){
- }
- }
- synchronized void Deposit(int amount){
- m_nAmount+=amount;
- }
- }
- class ClientThread2 extends Thread{
- public void run(){
- try{
- while(true){
- WithDraw(10);
- Thread.sleep(1000);
- System.out.println(",thread2:"+m_nAmount);
- }
- }catch(InterruptedException e){
- }
- }
- synchronized void WithDraw(int amount){
- m_nAmount-=amount;
- }
- }
- public static void main(String[] args) {
- ATM re=new ATM(100);
- ATM.ClientThread1 th1= re.new ClientThread1();
- ATM.ClientThread2 th2= re.new ClientThread2();
- ATM.ClientThread1 th3= re.new ClientThread1();
- ATM.ClientThread2 th4= re.new ClientThread2();
- th1.start();
- th2.start();
- th3.start();
- th4.start();
- }
- }
public class ATM{
int m_nAmount;
ATM(int m){
m_nAmount=m;
}
class ClientThread1 extends Thread{
public void run(){
try{
while(true){
Deposit(10);
Thread.sleep(1000);
System.out.println(",thread1:"+m_nAmount);
}
}catch(InterruptedException e){
}
}
synchronized void Deposit(int amount){
m_nAmount+=amount;
}
}
class ClientThread2 extends Thread{
public void run(){
try{
while(true){
WithDraw(10);
Thread.sleep(1000);
System.out.println(",thread2:"+m_nAmount);
}
}catch(InterruptedException e){
}
}
synchronized void WithDraw(int amount){
m_nAmount-=amount;
}
}
public static void main(String[] args) {
ATM re=new ATM(100);
ATM.ClientThread1 th1= re.new ClientThread1();
ATM.ClientThread2 th2= re.new ClientThread2();
ATM.ClientThread1 th3= re.new ClientThread1();
ATM.ClientThread2 th4= re.new ClientThread2();
th1.start();
th2.start();
th3.start();
th4.start();
}
}
这段代码的执行结果会像是下面这样:
,thread2:100
,thread2:100
,thread1:90
,thread1:100
,thread2:100
,thread2:90
,thread1:80
,thread1:90
,thread2:100
,thread2:90
,thread1:80
,thread1:90
原因前面已经说过了,因为无法保证两个函数能被互斥的访问。有一句名言,计算机领域的问题都可以通过引入一个中间层次得以解决,也就是说,为了解决这个问题,java1.5以后引入了新的包concurrent,这个包的内部去声明synchronized就行了。因此上面的代码会变成这样。这保证了没有Deposite或者WithDraw被同时执行造成潜在的数据冲突。多个线程每次只能执行一个Deposite或者WithDraw操作。
- import java.util.concurrent.locks.*;
- public class ATM {
- int m_nAmount=0;
- Lock m_lock=new ReentrantLock();
- ATM(int m){
- m_nAmount=m;
- }
- class ClientThread1 extends Thread{
- public void run(){
- try{
- while(true){
- m_lock.lock();
- Save(10);
- System.out.println(",thread1:"+m_nAmount);
- m_lock.unlock();
- Thread.sleep(1000);
- }
- }catch(InterruptedException e){}
- }
- synchronized void Save(int amount){
- m_nAmount+=amount;
- }
- }
- class ClientThread2 extends Thread{
- public void run(){
- try{
- while(true){
- m_lock.lock();
- WithDraw(10);
- System.out.println(",thread2:"+m_nAmount);
- m_lock.unlock();
- Thread.sleep(1000);
- }
- }catch(InterruptedException e){}
- }
- synchronized void WithDraw(int amount){
- m_nAmount-=amount;
- if(m_nAmount<0){
- System.out.println("failed withdraw");
- }
- }
- }
- public static void main(String[] args) {
- ATM re=new ATM(15);
- ATM.ClientThread1 th1= re.new ClientThread1();
- ATM.ClientThread2 th2= re.new ClientThread2();
- ATM.ClientThread1 th3= re.new ClientThread1();
- ATM.ClientThread2 th4= re.new ClientThread2();
- th1.start();
- th2.start();
- th3.start();
- th4.start();
- }
- }
import java.util.concurrent.locks.*;
public class ATM {
int m_nAmount=0;
Lock m_lock=new ReentrantLock();
ATM(int m){
m_nAmount=m;
}
class ClientThread1 extends Thread{
public void run(){
try{
while(true){
m_lock.lock();
Save(10);
System.out.println(",thread1:"+m_nAmount);
m_lock.unlock();
Thread.sleep(1000);
}
}catch(InterruptedException e){}
}
synchronized void Save(int amount){
m_nAmount+=amount;
}
}
class ClientThread2 extends Thread{
public void run(){
try{
while(true){
m_lock.lock();
WithDraw(10);
System.out.println(",thread2:"+m_nAmount);
m_lock.unlock();
Thread.sleep(1000);
}
}catch(InterruptedException e){}
}
synchronized void WithDraw(int amount){
m_nAmount-=amount;
if(m_nAmount<0){
System.out.println("failed withdraw");
}
}
}
public static void main(String[] args) {
ATM re=new ATM(15);
ATM.ClientThread1 th1= re.new ClientThread1();
ATM.ClientThread2 th2= re.new ClientThread2();
ATM.ClientThread1 th3= re.new ClientThread1();
ATM.ClientThread2 th4= re.new ClientThread2();
th1.start();
th2.start();
th3.start();
th4.start();
}
}
(3) 线程同步的问题,常见的是和synchronized关键字有关的。经典的问题之一是ATM取款机问题。因为windows的线程切换时间在10ms左右,因此下面这个程序就演示了100个线程同时存钱/取钱,最终的结果是不确定的。
- public class my{
- public static void main(String[] args){
- ATM[] pArr=new ATM[100];
- for(int i=0;i<pArr.length;++i){
- pArr[i]=new ATM();
- pArr[i].start();
- }
- try{
- Thread.sleep(300);
- }catch(InterruptedException e){
- e.printStackTrace();
- }
- System.out.println("Final amount="+ATM.m_acc.m_amount);
- }
- static class Account{
- int m_amount;
- String m_name;
- Account(int m,String name){
- m_amount=m;
- m_name =name;
- }
- void Deposit(int m){
- try{
- int a=m_amount;
- a+=m;
- Thread.sleep(10);
- m_amount=a;
- }catch(InterruptedException e){
- e.printStackTrace();
- }
- }
- void WithDraw(int m){
- try{
- int a=m_amount;
- a-=m;
- Thread.sleep(10);
- m_amount=a;
- }catch(InterruptedException e){
- e.printStackTrace();
- }
- }
- }
- static class ATM extends Thread{
- static Account m_acc=new my.Account(10,"self");
- public void run(){
- m_acc.Deposit(1);
- m_acc.WithDraw(1);
- }
- }
- }
public class my{
public static void main(String[] args){
ATM[] pArr=new ATM[100];
for(int i=0;i<pArr.length;++i){
pArr[i]=new ATM();
pArr[i].start();
}
try{
Thread.sleep(300);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("Final amount="+ATM.m_acc.m_amount);
}
static class Account{
int m_amount;
String m_name;
Account(int m,String name){
m_amount=m;
m_name =name;
}
void Deposit(int m){
try{
int a=m_amount;
a+=m;
Thread.sleep(10);
m_amount=a;
}catch(InterruptedException e){
e.printStackTrace();
}
}
void WithDraw(int m){
try{
int a=m_amount;
a-=m;
Thread.sleep(10);
m_amount=a;
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
static class ATM extends Thread{
static Account m_acc=new my.Account(10,"self");
public void run(){
m_acc.Deposit(1);
m_acc.WithDraw(1);
}
}
}
要克服这个问题,就要为Deposit和WithDraw函数加上synchronized锁,但是这还不够,因为这只能放置多个同时存,或者多个同时取,不能方式同时存取造成的冲突。因此同步的对象应该是this指针。注意主函数里面要让所有的子线程调用join函数,这样主线程等待所有子线程完成才打印最终结果。
- //改进后的程序:
- public class my{
- public static void main(String[] args){
- ATM[] pArr=new ATM[100];
- for(int i=0;i<pArr.length;++i){
- pArr[i]=new ATM();
- pArr[i].start();
- }
- try{
- for(int i=0;i<pArr.length;++i){
- pArr[i].join();
- }
- }catch(InterruptedException e){
- e.printStackTrace();
- }
- System.out.println("Final amount="+ATM.m_acc.m_amount);
- }
- static class Account{
- int m_amount;
- String m_name;
- Account(int m,String name){
- m_amount=m;
- m_name =name;
- }
- void Deposit(int m){
- try{
- synchronized(this){
- int a=m_amount;
- a+=m;
- Thread.sleep(10);
- m_amount=a;
- }
- }catch(InterruptedException e){
- e.printStackTrace();
- }
- }
- synchronized void WithDraw(int m){
- try{
- synchronized(this){
- int a=m_amount;
- a-=m;
- Thread.sleep(10);
- m_amount=a;
- }
- }catch(InterruptedException e){
- e.printStackTrace();
- }
- }
- }
- static class ATM extends Thread{
- static Account m_acc=new my.Account(10,"self");
- public void run(){
- m_acc.Deposit(1);
- m_acc.WithDraw(1);
- }
- }
- }
//改进后的程序:
public class my{
public static void main(String[] args){
ATM[] pArr=new ATM[100];
for(int i=0;i<pArr.length;++i){
pArr[i]=new ATM();
pArr[i].start();
}
try{
for(int i=0;i<pArr.length;++i){
pArr[i].join();
}
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("Final amount="+ATM.m_acc.m_amount);
}
static class Account{
int m_amount;
String m_name;
Account(int m,String name){
m_amount=m;
m_name =name;
}
void Deposit(int m){
try{
synchronized(this){
int a=m_amount;
a+=m;
Thread.sleep(10);
m_amount=a;
}
}catch(InterruptedException e){
e.printStackTrace();
}
}
synchronized void WithDraw(int m){
try{
synchronized(this){
int a=m_amount;
a-=m;
Thread.sleep(10);
m_amount=a;
}
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
static class ATM extends Thread{
static Account m_acc=new my.Account(10,"self");
public void run(){
m_acc.Deposit(1);
m_acc.WithDraw(1);
}
}
}
相比较而言,C++在C++11标准之前,线程同步依赖于操作系统的调用,非常麻烦,像posix系统就必须借助于Pthread线程库:
- #include <iostream>
- #include <pthread.h>
- #include <unistd.h>
- using namespace std;
- pthread_cond_t cond =PTHREAD_COND_INITIALIZER;
- pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
- void * start_routine(void* pvArg)
- {
- char* pch=(char*)pvArg;
- pthread_mutex_lock(&mutex);
- pthread_cond_wait(&cond,&mutex);
- pthread_mutex_unlock(&mutex);
- cout << pch <<endl;
- cout.widen("jjjj");
- return NULL;
- }
- int main()
- {
- pthread_t thread;
- pthread_create(&thread,NULL,start_routine,(void*)"kkk");
- sleep(5);
- pthread_mutex_lock(&mutex);
- pthread_cond_signal(&cond);
- pthread_mutex_unlock(&mutex);
- pthread_cond_destroy(&cond);
- pthread_mutex_destroy(&mutex);
- pthread_join(thread,NULL);
- return 0;
- }
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;
pthread_cond_t cond =PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
void * start_routine(void* pvArg)
{
char* pch=(char*)pvArg;
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond,&mutex);
pthread_mutex_unlock(&mutex);
cout << pch <<endl;
cout.widen("jjjj");
return NULL;
}
int main()
{
pthread_t thread;
pthread_create(&thread,NULL,start_routine,(void*)"kkk");
sleep(5);
pthread_mutex_lock(&mutex);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
pthread_cond_destroy(&cond);
pthread_mutex_destroy(&mutex);
pthread_join(thread,NULL);
return 0;
}
而且Pthread里面的mutex和conditional还不是正交的,而windows下的mutex和event是正交的。C++11从boost里面引入了atom/thread库,情况终于有所改观。