精华内容
下载资源
问答
  • 一组14个的多选框,选中前13个第14个会没有选中第14个前13个会没有,js代码应该怎么写,我只想实现互斥而不想实现别的任何功能,前13个的class=a,14的class=b $()function selectAll(obj){ if(obj.value=='无...
  • java实现同步互斥问题

    2014-11-14 16:18:41
    在操作系统中 同步与互斥是一个重要问题,这里主要研究一下怎样用Java来实现操作系统中的一些同步互斥算法。 一,软件实现临界区域问题 在《操作系统概念(第七版)》中,7.2讨论了临界区域问题,下面给出算法和...

    在操作系统中 同步与互斥是一个重要问题,这里主要研究一下怎样用Java来实现操作系统中的一些同步互斥算法。

    一,软件实现临界区域问题

    在《操作系统概念(第七版)》中,7.2讨论了临界区域问题,下面给出算法和Java实现代码。

    1.1 算法2

    算法2的伪代码如下:

    1. do{  
    2.  flag[i]=true;  
    3.  while(flag[j]);  
    4.  临界区;  
    5.  flag[i]=false;  
    6.  剩余区;  
    7. }while(1)  

    Java实现代码如下:

    [java] view plaincopy
    1. package mutiple_thread;  
    2.   
    3. public class OS_SYN_A2{  
    4.     public static  int flag[]=new int [3];  
    5.     public static int cnt=0;  
    6.     public static void main(String args[]){  
    7.         class proo implements Runnable{  
    8.             public proo(){  
    9.                   
    10.             }  
    11.             @Override  
    12.             public void run() {  
    13.                 // TODO Auto-generated method stub  
    14.                 while(true){  
    15.                     flag[1]=1;  
    16.                     while(flag[2]==1){  
    17.                           
    18.                     }  
    19.                     if(cnt==5){  
    20.                         flag[1]=0;  
    21.                     }else{  
    22.                         cnt++;  
    23.                         System.out.println("pro ++! now id"+cnt);  
    24.                         flag[1]=0;  
    25.                     }  
    26.                 }  
    27.             }  
    28.               
    29.         }  
    30.   
    31.         class conn implements Runnable{  
    32.   
    33.             @Override  
    34.             public void run() {  
    35.                 // TODO Auto-generated method stub  
    36.                 while(true){  
    37.                     flag[2]=1;  
    38.                     while(flag[1]==1){  
    39.                           
    40.                     }  
    41.                     //临界区  
    42.                     if(cnt==0){  
    43.                         flag[2]=0;  
    44.                     }else{  
    45.                         cnt--;  
    46.                         System.out.println("con --! now id"+cnt);  
    47.                         //退出临界区  
    48.                         flag[2]=0;  
    49.                     }  
    50.                 }  
    51.             }  
    52.         }  
    53.         new Thread(new proo()).start();  
    54.         new Thread(new conn()).start();  
    55.     }  
    56.       
    57. }  


    这个算法的主要思路是通过设置flag来确定执行哪个线程,但是可能会造成饥饿,因此不行。

    1.2 算法3

    算法3通过共享两个变量 flag 和turn来实现同步。


     

    [java] view plaincopy
    1. package mutiple_thread;  
    2.   
    3. public class OS_SYN_A3{  
    4.     public static  int flag[]=new int [3];  
    5.     public static int turn=0;  
    6.     public static int cnt=0;  
    7.     public static void main(String args[]){  
    8.         class proo implements Runnable{  
    9.             public proo(){  
    10.                   
    11.             }  
    12.             @Override  
    13.             public void run() {  
    14.                 // TODO Auto-generated method stub  
    15.                 while(true){  
    16.                     flag[1]=1;  
    17.                     turn=2;  
    18.                     while(flag[2]==1&&turn==2){  
    19.                           
    20.                     }  
    21.                     if(cnt==5){  
    22.                         flag[1]=0;  
    23.                     }else{  
    24.                         cnt++;  
    25.                         System.out.println("pro ++! now id"+cnt);  
    26.                         flag[1]=0;  
    27.                     }  
    28.                 }  
    29.             }  
    30.               
    31.         }  
    32.   
    33.         class conn implements Runnable{  
    34.   
    35.             @Override  
    36.             public void run() {  
    37.                 // TODO Auto-generated method stub  
    38.                 while(true){  
    39.                     flag[2]=1;  
    40.                     turn=1;  
    41.                     while(flag[1]==1&&turn==1){  
    42.                           
    43.                     }  
    44.                     //临界区  
    45.                     if(cnt==0){  
    46.                         flag[2]=0;  
    47.                     }else{  
    48.                         cnt--;  
    49.                         System.out.println("con --! now id"+cnt);  
    50.                         //退出临界区  
    51.                         flag[2]=0;  
    52.                     }  
    53.                 }  
    54.             }  
    55.         }  
    56.         new Thread(new proo()).start();  
    57.         new Thread(new conn()).start();  
    58.     }  
    59.       
    60. }  

    这是一种正确的软件实现方法。

    2.经典同步问题的Java实现

    2.1 读者写者问题

    这里实现的读者优先的算法,使用了Java并发包的信号量来实现。

    实现的伪代码如下:

    读者进程:

    while(1){

     wait(mutex)

       count++;

       if(readercount==1){

       wait(writer); 

     }

    signal(mutex);

    do reading;

    wait(mutex);

    cnt--;

    if(cnt==0){

      signal(writer);

    }

    signal(mutex);

    }

    }

    算法通过共享writer和mutex两个信号量,来处理同步问题

    [java] view plaincopy
    1. package mutiple_thread;  
    2.   
    3. import java.util.concurrent.Semaphore;  
    4.   
    5. public class OS_Readerwriter{  
    6.     static Semaphore sem=new Semaphore(1);  
    7.     static Semaphore sem_wrt=new Semaphore(1);  
    8.     static int readercount=0;  
    9.     static String a="hahaha";  
    10.     public static void main(String args[]){  
    11.         class reader implements Runnable{  
    12.             public reader(){  
    13.                   
    14.             }  
    15.             @Override  
    16.             public void run() {  
    17.                 // TODO Auto-generated method stub  
    18.                 try {  
    19.                     sem.acquire();  
    20.                     readercount++;  
    21.                 } catch (InterruptedException e) {  
    22.                     // TODO Auto-generated catch block  
    23.                     e.printStackTrace();  
    24.                 }  
    25.                 if(readercount==1){  
    26.                     try {  
    27.                         sem_wrt.acquire();  
    28.                     } catch (InterruptedException e) {  
    29.                         // TODO Auto-generated catch block  
    30.                         e.printStackTrace();  
    31.                     }  
    32.                 }  
    33.                 sem.release();  
    34.                   
    35.                 System.out.println("Reading "+a);  
    36.                   
    37.                 try {  
    38.                     sem.acquire();  
    39.                 } catch (InterruptedException e) {  
    40.                     // TODO Auto-generated catch block  
    41.                     e.printStackTrace();  
    42.                 }  
    43.                 readercount--;  
    44.                 if(readercount==0){  
    45.                     sem_wrt.release();  
    46.                 }  
    47.                 sem.release();  
    48.             }  
    49.         }  
    50.           
    51.         class writer implements Runnable{  
    52.             public writer(){  
    53.                   
    54.             }  
    55.             @Override  
    56.             public void run() {  
    57.                 // TODO Auto-generated method stub  
    58.                 try {  
    59.                     sem_wrt.acquire();  
    60.                 } catch (InterruptedException e) {  
    61.                     // TODO Auto-generated catch block  
    62.                     e.printStackTrace();  
    63.                 }  
    64.                 a=a+"abc";  
    65.                 System.out.println("Writing "+a);  
    66.                 sem_wrt.release();  
    67.             }  
    68.               
    69.         }  
    70.         for(int i=1;i<=10;i++){  
    71.             new Thread(new writer()).start();  
    72.             new Thread(new reader()).start();  
    73.         }  
    74.           
    75.     }  
    76. }  


    2.2 哲学家问题

    算法思路:通过对每一只筷子设置信号量,来进行同步判断。

    Java实现代码如下:

    [java] view plaincopy
    1. package mutiple_thread;  
    2.   
    3. import java.util.concurrent.Semaphore;  
    4.   
    5. public class OS_Philosopher{  
    6.     static int chop[]=new int [7];  
    7.     static Semaphore []sem=new Semaphore[7];  
    8.       
    9.     public static void main(String args[]) throws InterruptedException{  
    10.         for(int i=0;i<=6;i++){  
    11.             sem[i]=new Semaphore(1);  
    12.         }  
    13.         Thread.sleep(1000);  
    14.         class philosopher implements Runnable{  
    15.             int i;  
    16.             public philosopher(int i){  
    17.                 this.i=i;  
    18.             }  
    19.   
    20.             @Override  
    21.             public void run() {  
    22.                 // TODO Auto-generated method stub  
    23.                 while(true){  
    24.                     try {  
    25.                         synchronized(this){  
    26.                             sem[i].acquire();  
    27.                             sem[(i+1)%5].acquire();  
    28.                         }  
    29.                           
    30.                     } catch (InterruptedException e) {  
    31.                         // TODO Auto-generated catch block  
    32.                         e.printStackTrace();  
    33.                     }  
    34.                     System.out.println("Phi"+i+" is Eating");  
    35.                     //sleep();  
    36.                     try {  
    37.                         Thread.sleep(1);  
    38.                     } catch (InterruptedException e) {  
    39.                         // TODO Auto-generated catch block  
    40.                         e.printStackTrace();  
    41.                     }  
    42.                     sem[i].release();  
    43.                     sem[(i+1)%5].release();  
    44.                     System.out.println("Phi"+i+" is Thinking");  
    45.                     //sleep();  
    46.                     try {  
    47.                         Thread.sleep(1);  
    48.                     } catch (InterruptedException e) {  
    49.                         // TODO Auto-generated catch block  
    50.                         e.printStackTrace();  
    51.                     }  
    52.                 }  
    53.             }  
    54.         }  
    55.         philosopher t1=new philosopher(1);  
    56.         philosopher t2=new philosopher(2);  
    57.         philosopher t3=new philosopher(3);  
    58.         philosopher t4=new philosopher(4);  
    59.         philosopher t5=new philosopher(5);  
    60.         new Thread(t1).start();  
    61.         new Thread(t2).start();  
    62.         new Thread(t3).start();  
    63.         new Thread(t4).start();  
    64.         new Thread(t5).start();  
    65.           
    66.     }  
    67. }  


    2.3 理发店问题:

    理发店理有一位理发师、一把理发椅和 5 把供等候理发的顾客坐的椅 子。如果没有顾客,理发师便在理发椅上睡觉。一个顾客到来时,它必须叫 醒理发师。如果理发师正在理发时又有顾客来到,则如果有空椅子可坐,就 坐下来等待,否则就离开。

    算法思路如下:采用信号量进行判断。初始值为1,即是有一个理发师在服务。

    实现代码如下:

    [java] view plaincopy
    1. package mutiple_thread;  
    2.   
    3. import java.util.concurrent.Semaphore;  
    4.   
    5. public class OS_Barber1{  
    6.     static int cnt = 0;  
    7.     static int MAX = 5;  
    8.     static int busy = 0;  
    9.     static Semaphore sem=new Semaphore(1);  
    10.     public static void main(String args[]) throws InterruptedException{  
    11.         OS_Barber1 bar=new OS_Barber1();  
    12.         for(int i=1;i<=20;i++){  
    13.             new Thread(new Bar1(bar,i)).start();  
    14.             Thread.sleep((int) (400 - Math.random() * 300));  
    15.         }  
    16.     }  
    17.     public synchronized boolean isFull() {  
    18.         if (cnt == MAX) {  
    19.             return true;  
    20.         }  
    21.         return false;  
    22.     }  
    23.   
    24.     public synchronized boolean isEmpty() {  
    25.         if (cnt == 0) {  
    26.             return true;  
    27.         }  
    28.         return false;  
    29.     }  
    30.   
    31.     public synchronized boolean isBusy() {  
    32.         if (busy == 1) {  
    33.             return true;  
    34.         }  
    35.         return false;  
    36.     }  
    37.       
    38.     public  void Gobar(int index) throws InterruptedException{  
    39.           
    40.           
    41.         System.out.println("Con"+index+" is coming");  
    42.         cnt++;  
    43.         //判断是否满  
    44.         if(isFull()){  
    45.             System.out.println("Is full,"+"Con"+index+" is leaving");  
    46.             cnt--;  
    47.         }else{  
    48.             if(busy==1){  
    49.                 System.out.println("Con"+index+" is waitting");  
    50.             }  
    51.             //sem.acquire();  
    52.             synchronized (this){  
    53.                 while(busy==1){  
    54.                     //若有人在理发,则等待  
    55.                     wait();  
    56.                 }  
    57.             }  
    58.               
    59.             if(cnt==1){  
    60.                 System.out.println("Con"+index+" is wake");  
    61.             }  
    62.             busy=1;  
    63.             System.out.println("Con"+index+" is Serving");  
    64.             Thread.sleep(1000);  
    65.             System.out.println("Con"+index+" is leaving");  
    66.             cnt--;  
    67.             //sem.release();  
    68.             synchronized (this){  
    69.                 busy=0;  
    70.                 //唤醒  
    71.                 notify();  
    72.             }  
    73.             if(cnt==0){  
    74.                 System.out.println("Bar is sleep");  
    75.             }  
    76.         }  
    77.     }  
    78. }  
    79. class Bar1 implements Runnable {  
    80.     OS_Barber1 ob;  
    81.     int index;  
    82.     public Bar1(OS_Barber1 ob,int i) {  
    83.         this.ob = ob;  
    84.         index=i;  
    85.     }  
    86.   
    87.     @Override  
    88.     public void run() {  
    89.         // TODO Auto-generated method stub  
    90.         try {  
    91.             ob.Gobar(index);  
    92.         } catch (InterruptedException e) {  
    93.             // TODO Auto-generated catch block  
    94.             e.printStackTrace();  
    95.         }  
    96.     }  
    97.   
    98. }  


    在算法中我使用了wait(),notify()来实现,也可以使用信号量来实现,注释部分就是使用信号量的实现。

    在实现过程中,我发现了一些问题,也就是下面要讨论的wait() 和notify() 和notifyAll()

    3 wait() ,notify() 和notifyAll()

    synchronized 方法控制对类成员变量的访问:每个类实例对应一把锁,每个 synchronized 方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。
    wait()/notify():调用任意对象的 wait() 方法导致线程阻塞,并且该对象上的锁被释放。而调用 任意对象的notify()方法则导致因调用该对象的 wait() 方法而阻塞的线程中随机选择的一个解除阻塞(但要等到获得锁后才真正可执行)。

    synchronized和wait()、notify()的关系

    1.有synchronized的地方不一定有wait,notify

    2.有wait,notify的地方必有synchronized.这是因为wait和notify不是属于线程类,而是每一个对象都具有的方法,而且,这两个方法都和对象锁有关,有锁的地方,必有synchronized。

    另外,请注意一点:如果要把notify和wait方法放在一起用的话,必须先调用notify后调用wait,因为如果调用完wait,该线程就已经不是current thread了。

    注:调用wait()方法前的判断最好用while,而不用if;while可以实现被wakeup后thread再次作条件判断;而if则只能判断一次;


       线程的四种状态

      1. 新状态:线程已被创建但尚未执行(start() 尚未被调用)。

      2. 可执行状态:线程可以执行,虽然不一定正在执行。CPU 时间随时可能被分配给该线程,从而使得它执行。

      3. 死亡状态:正常情况下 run() 返回使得线程死亡。调用 stop()或 destroy() 亦有同样效果,但是不被推荐,前者会产生异常,后者是强制终止,不会释放锁。

      4. 阻塞状态:线程不会被分配 CPU 时间,无法执行。


    首先,前面叙述的所有方法都隶属于 Thread 类,但是这一对 (wait()/notify()) 却直接隶属于 Object 类,也就是说,所有对象都拥有这一对方法。初看起来这十分不可思议,但是实际上却是很自然的,因为这一对方法阻塞时要释放占用的锁,而锁是任何对象都具有的,调用任意对象的 wait() 方法导致线程阻塞,并且该对象上的锁被释放。而调用 任意对象的notify()方法则导致因调用该对象的 wait() 方法而阻塞的线程中随机选择的一个解除阻塞(但要等到获得锁后才真正可执行)。

     其次,前面叙述的所有方法都可在任何位置调用,但是这一对方法却必须在 synchronized 方法或块中调用,理由也很简单,只有在synchronized 方法或块中当前线程才占有锁,才有锁可以释放。

     同样的道理,调用这一对方法的对象上的锁必须为当前线程所拥有,这样才有锁可以释放。因此,这一对方法调用必须放置在这样的synchronized 方法或块中,该方法或块的上锁对象就是调用这一对方法的对象。若不满足这一条件,则程序虽然仍能编译,但在运行时会出现IllegalMonitorStateException 异常。

     wait() 和 notify() 方法的上述特性决定了它们经常和synchronized 方法或块一起使用,将它们和操作系统的进程间通信机制作一个比较就会发现它们的相似性:synchronized方法或块提供了类似于操作系统原语的功能,它们的执行不会受到多线程机制的干扰,而这一对方法则相当于 block 和wakeup 原语(这一对方法均声明为 synchronized)。它们的结合使得我们可以实现操作系统上一系列精妙的进程间通信的算法(如信号量算法),并用于解决各种复杂的线程间通信问题。关于

    wait() 和 notify() 方法最后再说明两点:

      第一:调用 notify() 方法导致解除阻塞的线程是从因调用该对象的 wait() 方法而阻塞的线程中随机选取的,我们无法预料哪一个线程将会被选择,所以编程时要特别小心,避免因这种不确定性而产生问题。

      第二:除了 notify(),还有一个方法 notifyAll() 也可起到类似作用,唯一的区别在于,调用 notifyAll() 方法将把因调用该对象的wait() 方法而阻塞的所有线程一次性全部解除阻塞。当然,只有获得锁的那一个线程才能进入可执行状态。

    谈到阻塞,就不能不谈一谈死锁,略一分析就能发现,suspend() 方法和不指定超时期限的 wait() 方法的调用都可能产生死锁。遗憾的是,Java 并不在语言级别上支持死锁的避免,我们在编程中必须小心地避免死锁。

     以上我们对 Java 中实现线程阻塞的各种方法作了一番分析,我们重点分析了 wait() 和 notify()方法,因为它们的功能最强大,使用也最灵活,但是这也导致了它们的效率较低,较容易出错。实际使用中我们应该灵活使用各种方法,以便更好地达到我们的目的。

    展开全文
  • Java 线程的互斥机制

    2020-11-20 21:36:57
    怎样实现互斥 用锁机制,用synchronized关键字来标记必须原子执行的代码段synchronized(D) { S; } //D是临界资源;S是临界区; 例 1、/*模拟银行存取钱,假定张三的银行帐户中有余额1000元,对银行账户的处理需要...
    • 为什么要有互斥机制
      假设当多个线程对统一数据并发进行读写操作(即竞争),会导致数据的不确定性。
    • 怎样实现互斥
      用锁机制,用synchronized关键字来标记必须原子执行的代码段,让多个线程有一共享对象,该对象有互斥锁,只有拿到,才能执行临界区;并且,临界区要么不执行,若执行,必须一次执行完。synchronized(D) { S; } //D是临界资源;S是临界区;

    1. /*模拟银行存取钱,假定张三的银行帐户中有余额1000元,对银行账户的处理需要经历三次函数调用:
      显示余额、修改余额(存钱或取钱)、显示修改后的余额。
      这三次调用的输出信息分别是:
      XX帐户余额为……元\n
      向……帐户存入(或者取出)……元\n
      XX帐户余额为……元\n
      请构造3个线程来同时操纵上述帐户,并输出结果。如张三向其中存5000元,老婆取2000,儿子取4000
      */
    class Account{
    	private String name;
    	private int val;
    	public String getName() {
    		return name;
    	}
    	Account(String s,int x){
    		name = s;
    		val = x;
    	}
    	public int getVal() { //查
    		return val;
    	}
    	public void setVal(int x) { //改
    		val = x;
    	}
    }
    class Saver extends Thread{ //查-改-查
    	private Account a;
    	private String name;
    	private int cm;
    	Saver(Account x,String y,int z){
    		a = x;
    		name = y;
    		cm = z;
    	}
    	public void run() {
    		synchronized(a) {
    			System.out.println(a.getName()+"账户余额为"+a.getVal()+"元");		
    			System.out.println(name+"向"+a.getName()+"账户存入"+cm+"元");
    			int x = a.getVal()+cm;
    			a.setVal(x);
    			System.out.println(a.getName()+"账户余额为"+x+"元\n");
    		}
    	}
    }
    class Fetcher extends Thread{ //查-改-查
    	private Account a;
    	private String name;
    	private int qm;
    	Fetcher(Account x,String y,int z){
    		a = x;
    		name = y;
    		qm = z;
    	}
    	public void run() {
    		synchronized(a) {
    			System.out.println(a.getName()+"账户余额为"+a.getVal()+"元");		
    			System.out.println(name+"向"+a.getName()+"账户取出"+qm+"元");		
    			int x = a.getVal()-qm;
    			a.setVal(x);
    			System.out.println(a.getName()+"账户余额为"+x+"元\n");
    		}
    	}
    }
    class app{
    	public static void main(String [] args) {
    		Account a = new Account("张三",1000);
    		Saver s1 = new Saver(a,"张三",5000);
    		Fetcher f1 = new Fetcher(a,"老婆",2000);
    		Fetcher f2 = new Fetcher(a,"儿子",4000);
    		s1.start(); f1.start(); f2.start();
    	}
    }
    

    在这里插入图片描述
    此例中:
    临界区为查-改-查,此处必须原子执行,即一次执行完。
    注:
    不能在Account类中synchronized(a){ public int getVal() { return val; } }//查 synchronized(a){ public void setVal(int x) { val = x; } } //改,这样只能保证操作是原子执行,而需求是查-改-查三步原子执行。

    1. /*线程类T定义方式为:class T implements Runnable{ int data; …}
      线程体实现依次输出data的1~10倍数据。
      在main中,构造和启动线程的方式形如: T t=new T(2); t.start();
      构造data分别为2、3、5的线程,线程名分别为t2、t3、t5,要求:t2、t3、t5分3行输出,且输出结果不得交叉。
      */
    class T implements Runnable{
    	private int data;
    	Thread t;
    	public void start() { t.start();}
    	String flag;
    	T(String x,int y){
    		flag = x;
    		data = y;
    		t = new Thread(this);
    	}
    	public void run() {
    		synchronized(flag) { //注意这里共享资源,也可以直接填"a",但不安全!此处涉及到常量池的概念
    			System.out.print("开始");
    			for(int i=1;i<=10;i++)
    				System.out.print(data*i+" ");
    			System.out.println("结束");
    		}
    	}
    }
    class app{
    	public static void main(String [] args) {
    		String s = "a";
    		T t2 = new T(s,2);
    		T t3 = new T(s,3);
    		T t5 = new T(s,5);
    		t2.start(); t3.start(); t5.start();
    	}
    }
    

    在这里插入图片描述

    展开全文
  • Redis 怎样实现的分布式锁1) 分布式锁1.1) 分布式锁的常见实现方式:2)单机锁3)使用 Redis 实现分布式锁4)锁超时 如果在分布式环境下使用就会出现锁不生效的问题,需要使用分布式锁来解决。 1) 分布式锁 ...


    如果在分布式环境下使用就会出现锁不生效的问题,需要使用分布式锁来解决。

    1) 分布式锁

    分布式锁是控制分布式系统之间同步访问共享资源的一种方式,是为了解决分布式系统中,不同的系统或是同一个系统的不同主机共享同一个资源的问题,它通常会采用互斥来保证程序的一致性,这就是分布式锁的用途以及执行原理。分布式锁示意图如下图所示:
    在这里插入图片描述

    1.1) 分布式锁的常见实现方式:

    • 基于 MySQL 的悲观锁来实现分布式锁,这种方式使用的最少,因为这种实现方式的性能不好,且容易造成死锁;
    • 基于 Memcached 实现分布式锁,可使用 add 方法来实现,如果添加成功了则表示分布式锁创建成功;
    • 基于 Redis 实现分布式锁,这也是本课时要介绍的重点,可以使用 setnx 方法来实现;
    • 基于 ZooKeeper 实现分布式锁,利用 ZooKeeper 顺序临时节点来实现。

    由于 MySQL 的执行效率问题和死锁问题,所以这种实现方式会被我们先排除掉,而 Memcached 和 Redis 的实现方式比较类似,但因为 Redis 技术比较普及,所以会优先使用 Redis 来实现分布式锁,而 ZooKeeper 确实可以很好的实现分布式锁。但此技术在中小型公司的普及率不高,尤其是非 Java 技术栈的公司使用的较少,如果只是为了实现分布式锁而重新搭建一套 ZooKeeper 集群,显然实现成本和维护成本太高,所以综合以上因素,本文会采用 Redis 来实现分布式锁。

    之所以可以使用以上四种方式来实现分布式锁,是因为以上四种方式都属于程序调用的“外部系统”,而分布式的程序是需要共享“外部系统”的,这就是分布式锁得以实现的基本前提。

    2)单机锁

    程序中使用的锁叫单机锁,我们日常中所说的“锁”都泛指单机锁,其分类有很多,大体可分为以下几类:

    • 悲观锁,是数据对外界的修改采取保守策略,它认为线程很容易把数据修改掉,因此在整个数据被修改的过程中都会采取锁定状态,直到一个线程使用完,其他线程才可以继续使用,典型应用是 synchronized;
    • 乐观锁,和悲观锁的概念恰好相反,乐观锁认为一般情况下数据在修改时不会出现冲突,所以在数据访问之前不会加锁,只是在数据提交更改时,才会对数据进行检测,典型应用是 ReadWriteLock 读写锁;
    • 可重入锁,也叫递归锁,指的是同一个线程在外面的函数获取了锁之后,那么内层的函数也可以继续获得此锁,在 Java 语言中 ReentrantLock 和 synchronized 都是可重入锁;
    • 独占锁和共享锁,只能被单线程持有的锁叫做独占锁,可以被多线程持有的锁叫共享锁,独占锁指的是在任何时候最多只能有一个线程持有该锁,比如 ReentrantLock 就是独占锁;而 ReadWriteLock 读写锁允许同一时间内有多个线程进行读操作,它就属于共享锁。

    单机锁之所以不能应用在分布式系统中是因为:在分布式系统中,每次请求可能会被分配在不同的服务器上,而单机锁是在单台服务器上生效的。如果是多台服务器就会导致请求分发到不同的服务器,从而导致锁代码不能生效,因此会造成很多异常的问题,那么单机锁就不能应用在分布式系统中了。

    3)使用 Redis 实现分布式锁

    使用 Redis 实现分布式锁主要需要使用 setnx 方法,也就是 set if not exists(不存在则创建),具体的实现代码如下:

    127.0.0.1:6379> setnx lock true
    (integer) 1 #创建锁成功
    #逻辑业务处理...
    127.0.0.1:6379> del lock
    (integer) 1 #释放锁
    

    当执行 setnx 命令之后返回值为 1 的话,则表示创建锁成功,否则就是失败。释放锁使用 del 删除即可,当其他程序 setnx 失败时,则表示此锁正在使用中,这样就可以实现简单的分布式锁了。

    但是以上代码有一个问题,就是没有设置锁的超时时间,因此如果出现异常情况,会导致锁未被释放,而其他线程又在排队等待此锁就会导致程序不可用。有人可能会想到使用 expire 来设置键值的过期时间来解决这个问题,例如以下代码:

    127.0.0.1:6379> setnx lock true
    (integer) 1 #创建锁成功
    127.0.0.1:6379> expire lock 30 #设置锁的(过期)超时时间为 30s
    (integer) 1 
    #逻辑业务处理...
    127.0.0.1:6379> del lock
    (integer) 1 #释放锁
    

    但这样执行仍然会有问题,因为 setnx lock true 和 expire lock 30 命令是非原子的,也就是一个执行完另一个才能执行。但如果在 setnx 命令执行完之后,发生了异常情况,那么就会导致 expire 命令不会执行,因此依然没有解决死锁的问题。

    这个问题在 Redis 2.6.12 之前一直没有得到有效的处理,当时的解决方案是在客户端进行原子合并操作,于是就诞生了很多客户端类库来解决此原子问题,不过这样就增加了使用的成本。因为你不但要添加 Redis 的客户端,还要为了解决锁的超时问题,需额外的增加新的类库,这样就增加了使用成本,但这个问题在 Redis 2.6.12 版本中得到了有效的处理。
    在 Redis 2.6.12 中我们可以使用一条 set 命令来执行键值存储,并且可以判断键是否存在以及设置超时时间了,如下代码所示:

    127.0.0.1:6379> set lock true ex 30 nx
    OK #创建锁成功
    

    其中,ex 是用来设置超时时间的,而 nx 是 not exists 的意思,用来判断键是否存在。如果返回的结果为“OK”则表示创建锁成功,否则表示此锁有人在使用。

    4)锁超时

    使用 set 命令之后好像一切问题都解决了吗?其实并没有。例如,我们给锁设置了超时时间为 10s,但程序的执行需要使用 15s,那么在第 10s 时此锁因为超时就会被释放,这时候线程二在执行 set 命令时正常获取到了锁,于是在很短的时间内 2s 之后删除了此锁,这就造成了锁被误删的情况,如下图所示:
    在这里插入图片描述
    锁被误删的解决方案:是在使用 set 命令创建锁时,给 value 值设置一个归属标识。例如,在 value 中插入一个 UUID,每次在删除之前先要判断 UUID 是不是属于当前的线程,如果属于再删除,这样就避免了锁被误删的问题。

    注意:在锁的归属判断和删除的过程中,不能先判断锁再删除锁,如下代码所示:

    if(uuid.equals(uuid)){ // 判断是否是自己的锁
    	del(luck); // 删除锁
    }
    

    应该把判断和删除放到一个原子单元中去执行,因此需要借助 Lua 脚本来执行,在 Redis 中执行 Lua 脚本可以保证这批命令的原子性,它的实现代码如下:

    /**
     * 释放分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁的 key
     * @param flagId 锁归属标识
     * @return 是否释放成功
     */
    public static boolean unLock(Jedis jedis, String lockKey, String flagId) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(flagId));
        if ("1L".equals(result)) { // 判断执行结果
            return true;
        }
        return false;
    }
    

    其中,Collections.singletonList() 方法是将 String 转成 List,因为 jedis.eval() 最后两个参数要求必须是 List 类型。

    锁超时可以通过两种方案来解决:

    • 把执行耗时的方法从锁中剔除,减少锁中代码的执行时间,保证锁在超时之前,代码一定可以执行完;
    • 把锁的超时时间设置的长一些,正常情况下我们在使用完锁之后,会调用删除的方法手动删除锁,因此可以把超时时间设置的稍微长一些。
    展开全文
  • 1 引言 讲了第二个内核对象,互斥体。前面已经学过一个内核对象,线程。这节讲两个函数,...互斥体:实现跨进程的互斥的控制。多个进程抢同一个全局变量,怎样保证最终只有一个进程来用。 2 ForSingleObject(...

    1 引言

    讲了第二个内核对象,互斥体。前面已经学过一个内核对象,线程。这节讲两个函数,WaitForSingleObject()和WaitForMultipleObjects()。因此这两个函数是根据内核对象的状态来进行操作的。

    临界区:一个进程里面对线程进行互斥的控制。
    互斥体:实现跨进程的互斥的控制。多个进程抢同一个全局变量,怎样保证最终只有一个进程来用。

    2 ForSingleObject()

    DWORD WaitForSingleObject(
      HANDLE hHandle,        // handle to object
      DWORD dwMilliseconds   // time-out interval
    );
    

    功能说明:
    等待函数可使线程自愿进入等待状态,直到一个特定的内核对象变为已通知状态为止.

    hHandle:
    内核对象句柄,可以是进程也可以是线程.

    dwMilliseconds:
    等待时间,单位是毫秒 INFINITE(-1)一直等待

    返回值:
    WAIT_OBJECT_0(0) 等待对象变为已通知
    WAIT_TIMEOUT(0x102) 超时

    特别说明:
    1、内核对象中的每种对象都可以说是处于已通知或未通知的状态之中
    2、这种状态的切换是由Microsoft为每个对象建立的一套规则来决定的
    3、当线程正在运行的时候,线程内核对象处于未通知状态
    4、当线程终止运行的时候,它就变为已通知状态
    5、在内核中就是个BOOL值,运行时FALSE 结束TRUE

    代码演示

    DWORD WINAPI ThreadProc1(LPVOID lpParameter)    
    {     
       for(int i=0;i<5;i++) 
       {  
          printf("+++++++++\n");
          Sleep(1000);
       }  
       return 0;   
    }     
          
    int main(int argc, char* argv[])    
    {     
          
       //创建一个新的线程  
       HANDLE hThread1 = ::CreateThread(NULL, 0, ThreadProc1,   
          NULL, 0, NULL);
          
       DWORD dwCode = ::WaitForSingleObject(hThread1, INFINITE);   
          
       MessageBox(0,0,0,0); 
          
       return 0;   
    }     
    

    3 和WaitForMultipleObjects()

    DWORD WaitForMultipleObjects(
      DWORD nCount,             // number of handles in array
      CONST HANDLE *lpHandles,  // object-handle array
      BOOL bWaitAll,            // wait option
      DWORD dwMilliseconds      // time-out interval
    );
    

    功能说明:同时查看若干个内核对象的已通知状态

    nCount:要查看内核对象的数量

    lpHandles:内核对象数组

    bWaitAll:等到类型 TRUE 等到所有变为已通知 FALSE 只要有一个变为已通知

    dwMilliseconds:超时时间

    INFINITE一直等待

    返回值:
    bWaitAll为TRUE时,返回WAIT_OBJECT_0(0) 代码所以内核对象都变成已通知
    bWaitAll为FALSE时,返回最先变成已通知的内核对象在数组中的索引

    WAIT_TIMEOUT(0x102)

    代码演示:

    DWORD WINAPI ThreadProc1(LPVOID lpParameter)    
    {     
       for(int i=0;i<5;i++) 
       {  
          printf("+++++++++\n");
          Sleep(1000);
       }  
       return 0;   
    }     
          
    DWORD WINAPI ThreadProc2(LPVOID lpParameter)    
    {     
       for(int i=0;i<3;i++) 
       {  
          printf("---------\n");
          Sleep(1000);
       }  
          
       return 0;   
    }     
          
          
    int main(int argc, char* argv[])    
    {     
          
       HANDLE hArray[2]; 
          
       //创建一个新的线程  
       HANDLE hThread1 = ::CreateThread(NULL, 0, ThreadProc1,   
          NULL, 0, NULL);
          
       //创建一个新的线程  
       HANDLE hThread2 = ::CreateThread(NULL, 0, ThreadProc2,   
          NULL, 0, NULL);
          
       hArray[0] = hThread1;   
       hArray[1] = hThread2;   
          
       DWORD dwCode = ::WaitForMultipleObjects(2, hArray,FALSE,INFINITE);   
          
       MessageBox(0,0,0,0); 
          
       return 0;   
    }     

    4 跨进程的线程控制与互斥体

    进程一:
    HANDLE g_hMutex = CreateMutex(NULL,FALSE, "XYZ");

    进程二:
    HANDLE g_hMutex = OpenMutex(MUTEX_ALL_ACCESS,FALSE, "XYZ");
    WaitForSingleObject(g_hMutex,INFINITE);

    //逻辑代码
    ReleaseMutex(g_hMutex);

    进程三:
    HANDLE g_hMutex = OpenMutex(MUTEX_ALL_ACCESS,FALSE, "XYZ");
    WaitForSingleObject(g_hMutex,INFINITE);

    //逻辑代码
    ReleaseMutex(g_hMutex);

    互斥体与临界区的区别:
    1、临界区只能用于单个进程间的线程控制.
    2、互斥体可以设定等待超时,但临界区不能.
    3、线程意外终结时,Mutex可以避免无限等待.
    4、Mutex效率没有临界区高.

    5 练习项目

    做一个抢红包项目,要求如下

    第一步:在第一个文本框中输入一个值,比如1000         
    第二步:点击抢红包,同时创建3个线程,每个线程循环进行抢红包的操作,每次抢50    
    第三步:使用Mutex进行线程控制,当第一个文本框中的值<50时,强红包线程结束.       
    特别说明:        
    1、四个文本框中的值总和应该为1000  
    2、强红包线程每次延时50毫秒. 
    3、使用WaitForMultipleObjects监听所有线程,当线程全部结束后       
       调用CloseHandle关闭句柄. 

    转载于:https://www.cnblogs.com/flatcc/p/7859302.html

    展开全文
  • 在操作系统中 同步与互斥是一个重要问题,这里主要研究一下怎样用Java来实现操作系统中的一些同步互斥算法。 AD: &lt;script&gt;&lt;/script&gt;   在操作系统中同步与互斥是一...
  • Futex,Fast Userspace muTEXes,作为linux下的一种快速同步(互斥)机制,已经存在了很长一段时间了(since linux 2.5.7)。它有什么优势?又提供了怎样一些功能,本文就简单探讨一下。 futex诞生之前 在futex...
  • 在操作系统中同步与互斥是一个重要问题,这里主要研究一下怎样用Java来实现操作系统中的一些同步互斥算法。1、软件实现临界区域问题在《操作系统概念(第七版)》中,7.2讨论了临界区域问题,下面给出算法和Java实现...
  • 两条线程的互斥合作

    2009-04-16 21:43:00
    两条线程的互斥合作就是指,当一条线程在工作时另一条线程挂起。 问题的提出:我想做一个动太显示随机个数的Label。 实现:1)一开始用一条线程搞个... 另一个问题又来了:怎样实现这两条线程的强烈互斥合作?必需要
  • windows是个多用户多任务的操作...该函数对象在系统中只能存在一个实例且是互斥体,所以利用这种特性就很简单的实现了我们的要求。 unit Winapi.Windows中的函数原声明: // NT 4.0 bug workaround - NT 4.0 does
  • 每个协程之间是相对独立的,如何在协程之间共享数据,进而保证数据的一致性,其中的一个方案是加互斥锁。 在标准库sync包中提供了两个互斥类型:sync.Mutex 与 sync.RWMutex,本文的代码中使用的是 sync.Mutex 类型...
  • java线程安全被分为了几个等级类,但不管怎样总体来讲就是使线程之内和几个线程之间的事务操作具有原子性,数据具有正确性,一般来讲...互斥是实现同步的一种方式,临界区、互斥量、信号量都是实现互斥的主要方式。...
  • spinlock在上一篇文章有提到:http://www.cnblogs.com/charlesblc/p/6254437.html 通过锁数据总线来实现。 而看了这篇文章说明:mutex内部也用到了... 获取互斥锁。 实际上是先给count做自减操作,然后使用本...
  • java线程安全被分为了几个等级类,但不管怎样总体来讲就是使线程之内和几个线程之间的事务操作具有原子性,数据具有正确性,一般来讲...互斥是实现同步的一种方式,临界区、互斥量、信号量都是实现互斥的主要方式。...
  • 使用信号量实现同步

    2015-09-08 16:51:23
    在上一篇文章中已经用信号量来实现线程间的互斥,达到了互斥锁的效果,今天这篇文章将讲述怎样用信号量去实现同步。 信号量的互斥同步都是通过PV原语来操作的,我们可以通过注册两个信号量,让它们在互斥的问题上...
  • java线程安全的实现方法

    千次阅读 2019-01-21 18:01:32
    java线程安全被分为了几个等级类,但不管怎样总体来讲就是使线程之内和几个线程之间的事务操作具有原子性,数据具有正确性,一般来讲具有以下几种...互斥实现同步的一种方式,临界区、互斥量、信号量都是实现互...
  • 信号量实现同步

    2012-09-27 10:29:45
    在上一篇文章中已经用信号量来实现线程间的互斥,达到了互斥锁的效果,今天这篇文章将讲述怎样用信号量去实现同步。 信号量的互斥同步都是通过PV原语来操作的,我们可以通过注册两个信号量,让它们在互斥的问题上...
  • 同步是指在多线程并发访问共享数据时,保证共享数据在同一时刻只被一个线程使用(同一时刻,只有一个线程操作共享数据),而互斥实现同步的一个手段,临界区,互斥量和信号量都是主要的互斥实现方式。因此,互斥是...
  • 写线程之间需要互斥,读线程跟写线程需要互斥,读线程之间不用互斥。早些时候听张sir的课,讲述java5中增强并发的功能。用java.util.concurrent.locks中ReadWriteLock 可以轻松解决读写锁问题。我在思考如果没有...
  • 到底怎样实现一个分布式锁呢? 锁的本质就是互斥,保证任何时候能有一个客户端持有同一个锁,如果考虑使用redis来实现一个分布式锁,最简单的方案就是在实例里面创建一个键值,释放锁的时候,将键值删除。但是一个...
  • Redis实现分布式锁

    2017-10-23 13:14:54
    到底怎样实现一个分布式锁呢? 锁的本质就是互斥,保证任何时候能有一个客户端持有同一个锁,如果考虑使用redis来实现一个分布式锁,最简单的方案就是在实例里面创建一个键值,释放锁的时候,将键值删除。但是一个...
  • 在上一篇文章中已经用信号量来实现线程间的互斥,达到了互斥锁的效果,今天这篇文章将讲述怎样用信号量去实现同步。 信号量的互斥同步都是通过PV原语来操作的,我们可以通过注册两个信号量,让它们在互斥的问题上...
  • 到底怎样实现一个分布式锁呢?本文主要介绍了Redis实现分布式锁的方法示例,小编觉得挺不错的,现在分享给大家,也给大家做个参考,希望能帮助到大家。锁的本质就是互斥,保证任何时候能有一个客户端持有同一个锁,...

空空如也

空空如也

1 2 3 4 5 ... 7
收藏数 125
精华内容 50
关键字:

怎样实现互斥