java语言支持两种形式的线程:互斥与协作,实现这两种方式的机制就是监视器。java虚拟机通过对象锁来实现互斥,允许多个线程在同一个共享数据上独立而互不干扰的工作。协作则是通过Object类的wait()方法和notify方法来实现的,允许多个线程为同一目标而共同工作。互斥是帮助线程在访问共享数据时不被其它线程干扰,而协作帮助线程与其它线程共同工作。
记得以前高中的时候,夏天每次下了晚自习,回到宿舍的时候,大家都想去洗个澡。但是宿舍只有一个卫生间,所以小A总是第一个冲回宿舍,跑进卫生间就把门锁上了,然后在里面自由自在的冲凉。(其实并不清楚他是否在冲凉)其它人只能在外面等着了,只到小A打开门出来,第二个人才可以进去。这就是一个标准的互斥。
而协作就相当于我们在进行I/O操作的时候,需要一个”读线程“,从缓冲区中读取数据,而另一个”写线程“会向缓冲区中填充数据。”读线程“需要缓冲区处于一个”非空“的状态,这样它才可以从中读数据。如果”读线程“发现缓冲区是空的,它就必须等待。”写线程“就负责向缓冲区写数据,只有”写线程“完成一些数据的写入,”读线程“才能完成相应的读取操作。java虚拟机中把这种监视器称作”等待并唤醒“监视器。等待线程将自身挂起是因为监视器保护数据并不属于它想要继续执行正确操作的状态。同样,唤醒线程在它将监视器保护数据设置成等待线程想要的状态后执行唤醒命令。
为了实现监视器排他性的监视能力,Java虚拟机为每一个对象和类都关联一个锁(有时候被成为互斥体(mutex))。一个锁就像一种任何时候只允许一个线程”拥有“的特权。java语言提供了两种方式来标志监视区域:同步语句和同步方法。
同步语句:要建立一个同步语句,只需要在一个计算引用的表达式中加上synchronized关键字就可以了。代码示例如下:
package ticketsale; public class SaleTickets1 implements Runnable { private static int tickets = 100; private void sale(){ if(tickets > 0){ synchronized(this){ System.out.println( Thread.currentThread().getName() +"卖出了第"+tickets+"张票"); tickets -- ; try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } System.out.println("此处可以进行其它操作..."); } @Override public void run() { while(tickets > 0){ sale(); } } public static void main(String[] args) { SaleTickets1 ticket = new SaleTickets1(); for(int i=1; i<=10; i++){ Thread t = new Thread(ticket); t.setName("第"+i+"窗口:"); t.start(); } } }
如果没有获得对当前对象this的锁,在同步块中的语句是不会执行的。如果使用的不是this引用,而是用一个表达式获得对另一个对象的引用,在线程语句体之前需要获得那个对象的锁。
同步方法:只需要在方法修饰符前加上synchronized关键字。代码示例如下:
package ticketsale; public class SaleTicket implements Runnable { private static int tickets = 100; private synchronized void sale(){ if(tickets > 0){ System.out.println(Thread.currentThread().getName() +"卖出第:"+tickets+"张票"); tickets --; try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } @Override public void run() { while(tickets > 0){ sale(); } } public static void main(String[] args) { SaleTicket sale = new SaleTicket(); for(int i=0; i<10; i++){ Thread t = new Thread(sale); t.setName("第"+i+"个窗口"); t.start(); } } }
同步类方法(静态方法)和同步实例方法是相同的操作。不同之处在于同步语句的时候不使用this(类方法没有this),线程必须获得对应的Calss实例的锁。
上面的实例中就是我们常见的售票系统。其中所用的线程就是互斥。下面的实例则为协作式线程,也是一个常见的问题,就是生产者——消费者的问题。前面已经讲过协作式线程,下面直接上代码:
测试类:
package producersandconsumers; public class ProducersAndConsumers { public static void main(String[] args) { Storage storage = new Storage(); Thread consumer1 = new Thread(new Consumer(storage)); Thread consumer2 = new Thread(new Consumer(storage)); consumer1.setName("消费者1"); consumer2.setName("消费者2"); Thread producer1 = new Thread(new Producer(storage)); Thread producer2 = new Thread(new Producer(storage)); producer1.setName("生产者1"); producer2.setName("生产者2"); consumer1.start(); consumer2.start(); producer1.start(); producer2.start(); } }
产品类:
package producersandconsumers; /* * 产品类 */ public class Product { private int id; private String name; public Product(int id, String name) { super(); this.id = id; this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String toString(){ return "产品的id为:"+id+" 名称为:"+name; } }
仓库类:
package producersandconsumers; /** * 仓库类 * @author Administrator */ public class Storage { //设置仓库容量 private Product[] products = new Product[10]; private int top = 0; //生产者向仓库里放入产品 public synchronized void push(Product product){ while(top == products.length){ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //把产品放入仓库 products[top++] = product; System.out.println(Thread.currentThread().getName()+"生产了产品:"+product); notifyAll(); } //消费者把产品从仓库中取出来 public synchronized Product pop(){ while(top == 0){ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //从仓库中取出产品 -- top; Product p = new Product(products[top].getId(),products[top].getName()); products[top] = null; System.out.println(Thread.currentThread().getName()+"消费了产品:"+p); notifyAll(); return p; } }
消费者类
package producersandconsumers; public class Consumer implements Runnable { private Storage storage; public Consumer(Storage storage){ this.storage = storage; } @Override public void run() { for(int i=0; i<20; i++){ storage.pop(); } } }
生产者类:
package producersandconsumers; public class Producer implements Runnable { private Storage storage; public Producer(Storage storage){ this.storage = storage; } @Override public void run() { for(int i=0; i<20; i++){ Product product = new Product(100+i,"苹果"); storage.push(product); } } }
java SE5的java.util.concurrent类库中还定义了显示的Lock对象,可以显示的创建、锁定和释放。可以使程序的灵活性显得更加优雅。有机会大家自己可以去多了解。
版权声明:本文为博主原创文章,未经博主允许不得转载。