最常见的程序员面试题(5)Java/C++线程同步
来源:互联网 发布:炉石传说盒子数据在哪 编辑:程序博客网 时间:2024/06/05 21:08
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 barrierinst=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库,情况终于有所改观。
- 最常见的程序员面试题(5)Java/C++线程同步
- 常见的java线程面试题
- Java常见面试题:线程的生命周期
- java最常见面试题
- java线程常见面试题
- java线程常见面试题
- 最常见的程序员面试题(10)字符串的处理
- 最常见的程序员面试题(1)单链表反向
- 最常见的程序员面试题(2)二叉树逐层打印
- 最常见的程序员面试题(3)置换算法
- 最常见的程序员面试题(7)二叉树转链表
- 面试题:最常见最有可能考到的C语言面试题汇总
- JAVA WEB面试中最常见的面试题解答
- java中关于线程的常见面试题
- C/C++程序员应聘常见面试题
- C/C++程序员应聘常见面试题
- C/C++程序员应聘常见面试题
- C/C++程序员应聘常见面试题
- openstack实际应用中问题总结
- 最常见的程序员面试题(2)二叉树逐层打印
- The Quiz(http://dmitrysoshnikov.com/ecmascript/the-quiz/#q9)
- 最常见的程序员面试题(3)置换算法
- 最常见的程序员面试题(4)写一个能工作的Singleton
- 最常见的程序员面试题(5)Java/C++线程同步
- 以前进行的程序安装创建了挂起的文件操作(SqlServer2000或SqlServer 2000 SP4补丁安装) .
- [poj]1002.487-3279
- top命令参数解释
- 最常见的程序员面试题(6)计算最常用的URL地址
- JS关闭窗口不提示
- AdaBoost中利用Haar特征进行人脸识别算法分析与总结2——级联分类器与检测过程 .
- 最常见的程序员面试题(7)二叉树转链表
- RMAN备份filesperset用法