1.Synchronized Vs ReentrantLock 的区别:在低并发量时期两者性能没有较大的区别,在高并发量的情况下,Sync的阻塞性的锁会严重的损害其性能
在Java1.5之前写并发程序的时候,Synchronized对多个线程共享共同的资源变量中为了保证其共享资源的线程安全的性能,采用Synchronized对共享资源表持同步机制,但是这种同步机制提供的锁是一种悲观锁,在任何的时候只有一个线程才能够进入临界区域,对其他的线程产生一种阻塞的状态,如果一步注意的话,一般有可能会产生死锁的状态(线程永远处在等待的状态,一种十分糟糕的情况)。将这种锁叫做是内置锁,缺点:内置锁在执行的过程中无法被中断。Synchronized 关键词锁住的只能是某个对象,或者是某个方法,不能锁住某个变量,LOck可以锁住中某个变量,Synchronized对某些线程不安全的方法,直接在这种方法外进行锁的封装。通过增加这个关键词,将不是很安全线程进行线程安全操作
下面模拟火车站买票的情况,总的票数AllTotal,分为三个窗口售票,采用Synchronized这个内置锁的关键字进行模拟操作。
Sync锁一般要么锁住方法或者锁住同步码块,在这里锁住代码块,最好采用锁住同步码块减少锁的力度,有利于系统操作
1 package ExecutorDemoPagage; 2 3 public class TranDemo implements Runnable{ 4 public static int AllTotal=20; 5 public static int i=1; 6 public static Object obj=new Object(); 7 public void run() 8 {//在售票的过程中,每个窗口可以同时访问主票数 9 while(true) 10 { 11 //System.out.println(Thread.currentThread().getName()+"窗口开始进行售票"); 12 //sync关键锁住必须要是一个对象,或者一个方法 13 synchronized(this){ 14 if(AllTotal>0)//将此放入锁之内,因为,必须要锁住这个代码 15 { 16 try { 17 Thread.sleep(200); 18 } catch (InterruptedException e) { 19 // TODO Auto-generated catch block 20 e.printStackTrace(); 21 } 22 System.out.println(Thread.currentThread().getName()+"窗口售票出第"+i+++"票"); 23 AllTotal=AllTotal-1; 24 25 } 26 else 27 { 28 break; 29 } 30 } 31 } 32 } 33 34 public static void main(String[] args) { 35 // TODO Auto-generated method stub 36 //采用5个线程进行模拟表示的是5个窗口,直到所有的票多卖完 37 TranDemo T=new TranDemo(); 38 Thread [] t= new Thread[5]; 39 for (int i=0;i<5;i++) 40 { 41 t[i]=new Thread(T);// 42 t[i].start(); 43 44 } 45 } 46 47 }
结果显示:
Java中ReentrantLock为我们重入锁的概念是一种乐观锁,采用CAS的原子性操作,能够实现非阻塞类结构的算法,同时能够与异常进行交互式操作。这种锁显示锁,必须要人工的进行解锁操作,但是synchronized自动的进行解锁。
总结:Lock有比synchronized更精确的线程语义和更好的性能。synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且必须在finally从句中释放。最重要的一点,每一次在使用锁的过程中必须保证这把锁是解锁状态,特别在while,for循环中,UNLock写入到循环中这样才能保证此线程在循环过程对共享资源进行操作,这也是为什么叫可重入锁的原因。
同样一个采用ReentrantLock:模拟火车票的购票情况:
1 package ExecutorDemoPagage; 2 3 import java.util.concurrent.locks.ReentrantLock; 4 5 public class TranDemo1 implements Runnable { 6 public static int AllTotal=20; 7 public static int i=0; 8 //多个线程采用同一把锁 9 public static ReentrantLock lock1=new ReentrantLock(); 10 11 public void run() 12 { 13 while(true) 14 { 15 try{ 16 lock1.lock(); 17 if (AllTotal>0) 18 { 19 System.out.println(Thread.currentThread().getName()+"窗口售票出第"+i+++"票"); 20 AllTotal=AllTotal--; 21 } 22 }finally{ 23 lock1.unlock(); 24 } 25 if(AllTotal<=0) 26 { 27 break; 28 } 29 } 30 } 31 32 public static void main(String[] args) { 33 // TODO Auto-generated method stub 34 //采用5个线程进行模拟表示的是5个窗口,直到所有的票多卖完 35 TranDemo T=new TranDemo(); 36 Thread [] t= new Thread[5]; 37 for (int i=0;i<5;i++) 38 { 39 t[i]=new Thread(T);// 40 t[i].start(); 41 42 } 43 } 44 }
1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
5)Lock可以提高多个线程进行读操作的效率。
在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。
2.Synchronized与ReentrantLock相比,如果在线程中发生了死锁的情况,对于Sync锁而言导致两个线程一直处于中断,对于ReentrantLock可以采用Lock.LockInterruptibly()对这种死锁的情况进行可中断的处理,抛出异常,让人理解,同时可以采用ReentrantLock中可实现的方法来避免中断。
下面通过模拟死锁的对其可中断的情况进行处理:
1 package JavaLockPackage; 2 3 import java.util.concurrent.ExecutorService; 4 import java.util.concurrent.Executors; 5 import java.util.concurrent.locks.ReentrantLock; 6 7 //在这个过程中模拟死锁的过程 8 public class ReentrantLockInt implements Runnable{ 9 //开辟连个锁,在开始的时候锁住1线程,其中锁住2线程,在2线程过程中锁住1线程 10 public int lock; 11 public static ReentrantLock lock1=new ReentrantLock(); 12 public static ReentrantLock lock2=new ReentrantLock(); 13 public ReentrantLockInt(int L) 14 { 15 this.lock=L; 16 } 17 public void run() 18 {try{ 19 if(lock==1) 20 { lock1.lockInterruptibly(); 21 //开始锁定线程1 22 try { 23 Thread.sleep(100); 24 } catch (InterruptedException e) { 25 // TODO Auto-generated catch block 26 e.printStackTrace(); 27 } 28 lock2.lockInterruptibly();//只能够用这个方法,普通的lock无用的, 29 //因为lock2在线程2中没有解锁,所以在此发生死锁的情况 30 } 31 else 32 { 33 lock2.lockInterruptibly(); 34 try { 35 Thread.sleep(100); 36 } catch (InterruptedException e) { 37 // TODO Auto-generated catch block 38 e.printStackTrace(); 39 } 40 lock1.lockInterruptibly();//只能够用这个方法,普通的lock无用的 41 } 42 }catch(InterruptedException e) 43 { 44 e.printStackTrace(); 45 } 46 finally{ 47 if(lock1.isHeldByCurrentThread())//或者采用lock1.getHoldCount(1)判断是否得到锁 48 { 49 lock1.unlock(); 50 } 51 if(lock2.isHeldByCurrentThread()) 52 { 53 lock2.unlock(); 54 } 55 System.out.println(Thread.currentThread().getId()+"线程退出"); 56 } 57 } 58 public static void main(String[] args) throws InterruptedException { 59 // TODO Auto-generated method stub 60 Thread t1=new Thread(new ReentrantLockInt(1)); 61 Thread t2=new Thread(new ReentrantLockInt(2)); 62 t1.start();t2.start(); 63 Thread.sleep(1000); 64 DeadlockChecker.check();//这里必须要有一个死锁检查程序,不然使用中断的无法进行结束。 65 } 66 67 }
1 package JavaLockPackage; 2 3 import java.lang.management.ManagementFactory; 4 import java.lang.management.ThreadInfo; 5 import java.lang.management.ThreadMXBean; 6 7 //此程序最主要对死锁过程中的锁进行检查 8 public class DeadLockCheck { 9 private final static ThreadMXBean mbean=ManagementFactory.getThreadMXBean(); 10 //此功能是对CPU的运行时间和竞争征用进行监视,评估某个线程运行的时间 11 final static Runnable deadLockChecker=new Runnable(){ 12 public void run() 13 { while(true) 14 {//通过ThreadMXBean找到所有死锁中断程序,与输入程序的线程ThreadInfo进行比较,Id相同则进行中断 15 long [] deadlockedThreadIds=mbean.findDeadlockedThreads(); 16 if(deadlockedThreadIds!=null){ 17 ThreadInfo [] threadInfos=mbean.getThreadInfo(deadlockedThreadIds); 18 for(Thread t:Thread.getAllStackTraces().keySet()) 19 { 20 for(int i=0;i<threadInfos.length;i++) 21 { 22 if(t.getId()==threadInfos[i].getThreadId()) 23 { 24 t.interrupt(); 25 } 26 } 27 } 28 } 29 try{ 30 Thread.sleep(500); 31 }catch(InterruptedException e) 32 { 33 e.printStackTrace(); 34 } 35 } 36 37 } 38 }; 39 public static void check() 40 { 41 Thread t=new Thread(deadLockChecker);//传替一种方法 42 t.setDaemon(true); 43 t.start(); 44 } 45 }
通过jps,jstack来查看系统中虚拟机的运行情况:
第二种避免死锁的情况而已进行可现实,使用lock.tryLock()方法,这样,不需要直接写其中断的方法
1 package JavaLockPackage; 2 3 import java.util.concurrent.TimeUnit; 4 import java.util.concurrent.locks.ReentrantLock; 5 6 public class TimeLock implements Runnable { 7 public static ReentrantLock lock=new ReentrantLock(); 8 public void run() 9 { 10 try{ 11 if(lock.tryLock(5,TimeUnit.SECONDS ))//在规定的时间中竞争同一把锁 12 { 13 System.out.println(Thread.currentThread().getName()+"Get Successed Lock"); 14 Thread.sleep(5000); 15 } 16 else 17 { 18 System.out.println(Thread.currentThread().getName()+"Get Failed Lock"); 19 } 20 } catch(InterruptedException e) 21 { 22 e.printStackTrace(); 23 } 24 25 finally 26 { 27 if(lock.isHeldByCurrentThread()) 28 lock.unlock(); 29 } 30 } 31 public static void main(String[] args) { 32 // TODO Auto-generated method stub 33 TimeLock L1=new TimeLock(); 34 Thread p1=new Thread(L1); 35 Thread p2=new Thread(L1); 36 //俩个线程竞争同一把锁 37 p1.start();p2.start(); 38 39 } 40 41 }
结果显示:第一种结果:
第二种结果:
3.练习题:
问题的描述
启动3个线程打印递增的数字, 线程1先打印1,2,3,4,5, 然后是线程2打印6,7,8,9,10, 然后是线程3打印11,12,13,14,15. 接着再由线程1打印16,17,18,19,20....以此类推, 直到打印到75. 程序的输出结果应该为:
线程1: 1
线程1: 2
线程1: 3
线程1: 4
线程1: 5
线程2: 6
线程2: 7
线程2: 8
线程2: 9
线程2: 10
...
线程3: 71
线程3: 72
线程3: 73
线程3: 74
线程3: 75
第一种方法,采用Synchronized,notify(),wait()
1 package ThreadTest; 2 //用这种方法不在main函数中将pn写成全局变量这样导致id,id1,id2值发生修改 3 public class NUmbelPrint implements Runnable{ 4 static int state=1; 5 static int n=1; 6 private int id=0,id1=0,id2=0; 7 static NUmbelPrint pn=new NUmbelPrint(); 8 public static void main(String[] args) { 9 // TODO Auto-generated method stub 10 pn.id=1;new Thread(pn).start();try { 11 Thread.sleep(20); 12 } catch (InterruptedException e) { 13 // TODO Auto-generated catch block 14 e.printStackTrace(); 15 } 16 pn.id=0;pn.id1=2;new Thread(pn).start(); 17 try{ 18 Thread.sleep(20); 19 } catch (InterruptedException e) { 20 // TODO Auto-generated catch block 21 e.printStackTrace(); 22 } 23 pn.id1=0;pn.id=0;pn.id2=3;new Thread(pn).start(); 24 try{ 25 Thread.sleep(20); 26 } catch (InterruptedException e) { 27 // TODO Auto-generated catch block 28 e.printStackTrace(); 29 } 30 31 } 32 public void run() 33 { if(id==1) 34 work(id); 35 if(id1==2) 36 work1(id1); 37 if(id2==3) 38 work2(id2); 39 } 40 public void work(int temp){ 41 for(int i=0;i<5;i++) 42 { 43 synchronized (pn){ 44 while(state !=temp) 45 { 46 try { 47 this.wait(); 48 } catch (InterruptedException e) { 49 // TODO Auto-generated catch block 50 e.printStackTrace(); 51 } 52 } 53 for(int j=0;j<5;j++) 54 { 55 System.out.println(Thread.currentThread().getName()+"线程 "+n++); 56 } 57 state=2; 58 pn.notifyAll(); 59 if(i==4) 60 System.out.println("线程1"+state); 61 } 62 } 63 } 64 public void work1(int temp){ 65 for(int i=0;i<=5;i++){ 66 synchronized (pn){ 67 while(state !=temp) 68 { 69 try { 70 this.wait(); 71 } catch (InterruptedException e) { 72 // TODO Auto-generated catch block 73 e.printStackTrace(); 74 } 75 } 76 for(int j=0;j<5;j++) 77 { 78 System.out.println(Thread.currentThread().getName()+"线程 "+n++); 79 } 80 state=3; 81 pn.notifyAll(); 82 } 83 if(i==4) 84 System.out.println("线程2"+state); 85 } 86 } 87 public void work2(int temp){ 88 for(int i=0;i<5;i++){//第三个线程运行了4次 89 synchronized (pn){ 90 while(state !=temp) 91 { 92 try { 93 this.wait(); 94 } catch (InterruptedException e) { 95 // TODO Auto-generated catch block 96 e.printStackTrace(); 97 } 98 } 99 for(int j=0;j<5;j++) 100 { 101 System.out.println(Thread.currentThread().getName()+"线程 "+n++); 102 } 103 state=1; 104 pn.notifyAll(); 105 } 106 107 System.out.println("线程3--"+state+"---"+i); 108 } 109 } 110 } 111 //为什么最后一次中会0 1 0,因为线程1结束后就没有了,在整个程序中只有俩个线程,所以1 2 3修改成1 2,现在只有两个线程 112 //在这里竞争,所以最后才会出现0 1 0,线程发生变化
1 public class NumberPrintDemo { 2 // n为即将打印的数字,这里采用的匿名类部类的方法: 3 private static int n = 1; 4 // state=1表示将由线程1打印数字, state=2表示将由线程2打印数字, state=3表示将由线程3打印数字 5 private static int state = 1; 6 7 public static void main(String[] args) { 8 final NumberPrintDemo pn = new NumberPrintDemo(); 9 new Thread(new Runnable() { 10 public void run() { 11 // 3个线程打印75个数字, 单个线程每次打印5个连续数字, 因此每个线程只需执行5次打印任务. 3*5*5=75 12 for (int i = 0; i < 5; i++) { 13 // 3个线程都使用pn对象做锁, 以保证每个交替期间只有一个线程在打印 14 synchronized (pn) { 15 // 如果state!=1, 说明此时尚未轮到线程1打印, 线程1将调用pn的wait()方法, 直到下次被唤醒 16 while (state != 1) 17 try { 18 pn.wait(); 19 } catch (InterruptedException e) { 20 e.printStackTrace(); 21 } 22 // 当state=1时, 轮到线程1打印5次数字 23 for (int j = 0; j < 5; j++) { 24 // 打印一次后n自增 25 System.out.println(Thread.currentThread().getName() 26 + ": " + n++); 27 } 28 System.out.println(); 29 // 线程1打印完成后, 将state赋值为2, 表示接下来将轮到线程2打印 30 state = 2; 31 // notifyAll()方法唤醒在pn上wait的线程2和线程3, 同时线程1将退出同步代码块, 释放pn锁. 32 // 因此3个线程将再次竞争pn锁 33 // 假如线程1或线程3竞争到资源, 由于state不为1或3, 线程1或线程3将很快再次wait, 释放出刚到手的pn锁. 34 // 只有线程2可以通过state判定, 所以线程2一定是执行下次打印任务的线程. 35 // 对于线程2来说, 获得锁的道路也许是曲折的, 但前途一定是光明的. 36 pn.notifyAll(); 37 } 38 } 39 } 40 }, "线程1").start(); 41 42 new Thread(new Runnable() { 43 public void run() { 44 for (int i = 0; i < 5; i++) { 45 synchronized (pn) { 46 while (state != 2) 47 try { 48 pn.wait(); 49 } catch (InterruptedException e) { 50 e.printStackTrace(); 51 } 52 for (int j = 0; j < 5; j++) { 53 System.out.println(Thread.currentThread().getName() 54 + ": " + n++); 55 } 56 System.out.println(); 57 state = 3; 58 pn.notifyAll(); 59 } 60 } 61 } 62 }, "线程2").start(); 63 64 new Thread(new Runnable() { 65 public void run() { 66 for (int i = 0; i < 5; i++) { 67 synchronized (pn) { 68 while (state != 3) 69 try { 70 pn.wait(); 71 } catch (InterruptedException e) { 72 e.printStackTrace(); 73 } 74 for (int j = 0; j < 5; j++) { 75 System.out.println(Thread.currentThread().getName() 76 + ": " + n++); 77 } 78 System.out.println(); 79 state = 1; 80 pn.notifyAll(); 81 } 82 } 83 } 84 }, "线程3").start(); 85 } 86 }
第二种:ReentrantLock,await(),signal()
1 package JavaLockPackage; 2 3 import java.util.concurrent.locks.Condition; 4 import java.util.concurrent.locks.ReentrantLock; 5 6 //三个线程轮换打印12345 678910,。。。。。有序的打印直到75 7 public class LockAndConditionExam { 8 private static int n=1; 9 private static int state=1; 10 11 private static ReentrantLock lock1=new ReentrantLock(); 12 //获取其中的三个分支线程 13 private static Condition c1=lock1.newCondition(); 14 private static Condition c2=lock1.newCondition(); 15 private static Condition c3=lock1.newCondition(); 16 //的是一个实例,必须要加上static关键字,不然会成为class类, 17 private static class CreateThread1 implements Runnable{ 18 public void run() 19 { 20 for(int i=0;i<5;i++) 21 { try{ 22 lock1.lock(); 23 while(state!=1) 24 { 25 try{ 26 c1.await(); 27 28 } 29 catch(InterruptedException e) 30 { 31 e.printStackTrace(); 32 } 33 } 34 for(int j=0;j<5;j++) 35 { 36 System.out.println(Thread.currentThread().getName()+"-线程--"+n++); 37 } 38 System.out.println(); 39 state=2; 40 c2.signal(); 41 42 } 43 finally{ 44 lock1.unlock(); 45 } 46 47 } 48 } 49 } 50 51 private static class CreateThread2 implements Runnable{ 52 public void run() 53 { 54 for(int i=0;i<5;i++) 55 { try{ 56 lock1.lock(); 57 while(state!=2) 58 { 59 try{ 60 c2.await(); 61 62 } 63 catch(InterruptedException e) 64 { 65 e.printStackTrace(); 66 } 67 } 68 for(int j=0;j<5;j++) 69 { 70 System.out.println(Thread.currentThread().getName()+"-线程--"+n++); 71 } 72 System.out.println(); 73 state=3; 74 c3.signal(); 75 76 } 77 finally{ 78 lock1.unlock(); 79 } 80 81 } 82 } 83 } 84 85 private static class CreateThread3 implements Runnable{ 86 public void run() 87 { 88 for(int i=0;i<5;i++) 89 { try{ 90 lock1.lock(); 91 while(state!=3) 92 { 93 try{ 94 c3.await(); 95 96 } 97 catch(InterruptedException e) 98 { 99 e.printStackTrace(); 100 } 101 } 102 for(int j=0;j<5;j++) 103 { 104 System.out.println(Thread.currentThread().getName()+"-线程--"+n++); 105 } 106 System.out.println(); 107 state=1; 108 c1.signal(); 109 110 } 111 finally{ 112 lock1.unlock(); 113 } 114 115 } 116 } 117 } 118 public static void main(String[] args) { 119 // TODO Auto-generated method stub 120 121 //对run方法进行重载 122 Thread T1=new Thread (){ 123 124 public void run() 125 { 126 for(int i=0;i<5;i++) 127 { try{ 128 lock1.lock(); 129 while(state!=1) 130 { 131 try{ 132 c1.await(); 133 134 } 135 catch(InterruptedException e) 136 { 137 e.printStackTrace(); 138 } 139 } 140 for(int j=0;j<5;j++) 141 { 142 System.out.println(Thread.currentThread().getName()+"-线程--"+n++); 143 } 144 System.out.println(); 145 state=2; 146 c2.signal(); 147 148 } 149 finally{ 150 lock1.unlock(); 151 } 152 153 } 154 } 155 }; 156 T1.start(); 157 158 Thread T2=new Thread(new CreateThread2()); 159 T2.start();//方法是进行new Runnable 实例 160 161 Thread T3=new Thread(new CreateThread3()); 162 T3.start(); 163 164 } 165 166 }