现在的绝大多数应用程序都是多线程的程序,而当有两个或两个以上的线程需要对同一数据进行存取时,就会出现条件竞争,也即
是这几个线程中都会有一段修改该数据状态的代码。但是如果这些线程的运行顺序推行不当的话是很容易造成死锁现象的。所以在JAVA中为
了防止出现这种现象的出现就产生了锁和条件机制用来对实现各个线程之间互斥的去访问这段临界区的代码,唉!由于我也是才刚学多线程
并发编程,也不可能有多深多深的理解,所以就先只写一下它们的用法了。
------------------YYC
其实从JAVA SE 5.0以后就有两种机制来防止代码块受并发访问的干扰,一种是通过lock机制,另一种是通过synchronized来实现,
下面分别来实现。
该部分程序的功能主要是实现通过多个线程 去访问yy和cc这两个变量,然后在每个线程中都分别不断的从其中一个大于0 的变量中减去10,然
后在给另外的一个变量加上10,如果说此处不用锁的机制去实现线程的修改yy和cc时,是恒容易发生死锁错误的,而且在运行一段时间后yy和cc
的总和也将不再是100.
方法一:
public int yy = 0; public int cc =100;
public class YYThread extends Thread{ private Condition condition ; //定义条件对象 private Lock YYlock = new ReentrantLock(); //定义锁对象, //其中ReentrantLock()方法是用来构造一个用来保护临界区的可以重入锁,此处也可以调用ReentrantLock(boolean fair)来构造一个公平锁 @Override public void run() { // TODO Auto-generated method stub super.run(); YYlock.lock();//当线程运行到这个地方时给下面的代码片加上互斥锁 condition = YYlock.newCondition(); //初始化条件对象 Random random = new Random(); int a = random.nextInt(); int b = a%2; if(b==0) { if(yy<10) {try { condition.await();//当发现该线程的条件不足时,自动阻塞当前线程,并释放处理机等资源和解锁临界资源 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); }} yy-=10; cc+=10; Log.d("yy "+yy, "cc "+cc); condition.signalAll();//当资源状态改变时,调用该方法解除所有因这一条件而等待的所有线程,当这些线程从等待集里移除时。它们就 //再次成为可运行的,相当于是处于就绪状态,调度器就可再次激活他们。 } else { if(cc<10) {try { condition.await(); //当发现该线程的条件不足时,自动阻塞当前线程,并释放处理机等资源和解锁临界资源 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); }} yy+=10; cc-=10; Log.d("yy "+yy, "cc "+cc); condition.signalAll();//激活其他线程 } try { Thread.sleep(500); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace();} finally{ YYlock.unlock(); //解锁对该部分代码的访问控制,注意此解锁操作必须要放在finally字句里面,因为如果临界区的代码抛出异常的话,锁必须要被 //释放,否则其他所有线程都将永远阻塞 } } }
调用部分
//实现方式一: for(int i =0 ;i<80;i++) { YYThread yt = new YYThread(); yt.start(); }
成功实现锁和条件的机制锁主要是原来实现保护临界区的代码片,实现互斥的相互访问
条件主要是用来管理进入临界区的线程condition.await();进入条件的等待集, 该线程被阻塞,
并自动放弃处理机资源和解锁临界资源,但它与互斥阻塞的主要区别是只要
当condition.signalAll();激活该该线程时,该线程将立马成为可运行的,并接受调度器的调度。
YYlock.lock();互斥阻塞,当获得临界资源时才可能被执行
方法二:
定义该方法
public int yy = 0; public int cc =100; private synchronized void fun()//用关键字synchronized声明为该方法只能互斥访问 { Random random = new Random(); int a = random.nextInt(); int b = a%2; if(b==0) { while(yy<10) {try { wait();//条件阻塞 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); }} yy-=10; cc+=10; notifyAll();//激活阻塞队列的线程 Log.d("yy "+yy, "cc "+cc); } else { while(cc<10) {try { wait();//用while循环检测条件,但也会遵循“让权等待”的原则。 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); }} cc-=10; yy+=10; notifyAll();//激活阻塞队列的线程 Log.d("yy "+yy, "cc "+cc); } }
定义线程
public class YYThread extends Thread{ @Override public void run() { // TODO Auto-generated method stub super.run(); fun(); } }
开启线程
for(int i =0 ;i<80;i++) { YYThread yt = new YYThread(); yt.start(); }
其实方法一和方法二的效果都是一样的,但是我们可以看到使用synchronized关键字来编写代码要简洁的多,但是如果你要理解这一关键字的机制,你就必须了解
其实每一个对象都是有一个内部锁的,并且该锁有一个内部条件,由锁来管理那些试图进入synchronized方法的线程,由条件来管理那些调用wait的线程。