Java探索之旅(18)——多线程(2)
1 线程协调
目的对各线程进行控制,保证各自执行的任务有条不紊且有序并行计算。尤其是在共享资源或者数据情况下。
1.1 易变volatile
cache技术虽然提高了访问数据的效率,但是有可能导致主存储器和cache中的值在某个瞬间的值不同。在多线程中,某个线程访问的可能是cache的值而非主存储器。
volatile保证线程直接访问主存储器,保证数据的一致性。volatile只能用于基本数据类型或者数组(boolean,byte, char, double ,float, integer, long, short),且不协调线程先后次序。
1.2 同步synchronized
定义某个程序块或者整个方法。协调多线程多某个方法或者程序块的访问。利用Monitor-lock技术,保证lock关关时,仅有一个线程使用某个对象。线程访问完毕,lock打开等待线程调度器分配给下一个线程。
【同步代码块】
synchronized(同步对象){ //需要同步的代码 }
class hello implements Runnable { public void run() { for(int i=0;i<10;++i){ synchronized (this) {//需要同步的对象 if(count>0){ try{ Thread.sleep(1000); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println(count--); } } } } public static void main(String[] args) { hello he=new hello(); Thread h1=new Thread(he); Thread h2=new Thread(he); Thread h3=new Thread(he); h1.start(); h2.start(); h3.start(); } private int count=5; }
【同步方法】
synchronized 返回类型 方法名(参数列表){
// 其他代码
}
class hello implements Runnable { public void run() { for (int i = 0; i < 10; ++i) { sale(); } } public synchronized void sale() { if (count > 0) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(count--); } } public static void main(String[] args) { hello he = new hello(); Thread h1 = new Thread(he); Thread h2 = new Thread(he); Thread h3 = new Thread(he); h1.start(); h2.start(); h3.start(); } private int count = 5; }
1.3等待wait()与通知notify()及notifyAll()
wait()、notify()、notifyAll()是三个定义在Object类里的方法,用来控制线程的状态。
❶如果对象调用了wait方法就会使持有该对象的线程把该对象的控制权交出去,然后处于等待状态。可设定参数控制等待时长。如果没有指定参数,默认一直等待直到被通知。 wait() wait(long) wait(long,int)
❷如果对象调用了notify方法就会随机通知某个正在等待这个对象的控制权的线程可以继续运行。
❸如果对象调用了notifyAll方法就会通知所有等待这个对象控制权的线程继续运行
❹直接使用默认为当前对象。
1.4实例
实例1:使用2个进程交替输出“B<===>100 A<===>20”:
/*** 测试类**/ class test { public static void main(String[] args) { Info info = new Info(); Producer pro = new Producer(info); Consumer con = new Consumer(info); new Thread(pro).start(); new Thread(con).start(); } } class Info { private String name = "A"; private int age = 20; private boolean flag=true; public String getName(){return name;} public int getAge(){return age;} public synchronized void set(String name, int age){ if(!flag)//一旦下面的代码执行过一次,则必须交出对象的使用权限 try{wait();} catch (Exception e) { e.printStackTrace();} this.name=name; this.age=age; flag=false; notifyAll(); } public synchronized void get(){ if(flag)//一旦下面的代码执行过一次,则必须交出对象的使用权限 try{wait();} catch (Exception e) { e.printStackTrace();} System.out.println(this.getName()+"<===>"+this.getAge()); flag=true; notifyAll(); } } /** * 生产者 * */ class Producer implements Runnable { private Info info = null; Producer(Info info) { this.info = info; } public void run() { boolean flag = false; for (int i = 0; i < 25; ++i) if (flag) { this.info.set("A", 20); flag = false;} else { this.info.set("B", 100); flag = true; } } } /*** 消费者类***/ class Consumer implements Runnable { private Info info = null; public Consumer(Info info) { this.info = info; } public void run() { for (int i = 0; i < 25; ++i) { this.info.get(); } } }
分析: ❶当输出成功时。使用notifyAll()使得等待的set()处于可执行态,调整flag。当下一轮输出循环时,flag保证输出函数的wait被激发,则等待的set()函数继续运行,重新设置数据。 ❷当设置成功时。使用notifyAll()使等待的get()处于可执行态,调整flag。当下一轮设置循环时,flag保证设置函数的set()被激发,则等待的get()函数继续进行,输出上一轮设置后的数据
public class NotifyTest { private String flag="true"; class NotifyThread extends Thread { public NotifyThread(String name) { super(name); } public void run() { try { System.out.println("notifyAll() begain"); sleep(1000);} catch (InterruptedException e) { e.printStackTrace(); } synchronized (flag) {//对象flag的控制权,控制块 flag.notifyAll();//flag能够被其它进程控制 System.out.println("notifyAll() end"); } } } class WaitThread extends Thread { public WaitThread(String name) { super(name); } public void run() { synchronized (flag) { System.out.println(getName() + " begin waiting!"); long waitTime = System.currentTimeMillis(); try {flag.wait();} //放弃对象flag的控制权 catch (InterruptedException e) { e.printStackTrace(); } waitTime = System.currentTimeMillis() - waitTime; System.out.println(Thread.currentThread().getName() + " end waiting!,wait time :" + waitTime); } } } public static void main(String[] args) { System.out.println("Main Thread Run!"); NotifyTest test = new NotifyTest(); NotifyThread notifyThread = test.new NotifyThread("notify01"); WaitThread waitThread01 = test.new WaitThread("waiter01"); WaitThread waitThread02 = test.new WaitThread("waiter02"); notifyThread.start(); waitThread01.start(); waitThread02.start(); } }
输出:
Main Thread Run! notifyAll() begain waiter02 begin waiting! waiter01 begin waiting! notifyAll() end waiter01 end waiting!,wait time :1000 <pre style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-bottom: 0px; font-family: Helvetica, Tahoma, Arial, sans-serif; line-height: 25.2000007629395px;">waiter02 end waiting!,wait time :1000
注意: ❶任何一个时刻,对象的控制权(monitor)只能被一个线程拥有。 ❷无论是执行对象的wait、notify还是notifyAll方法,必须保证当前运行的线程取得了该对象的控制权(monitor) ❸如果在没有控制权的线程里执行对象的以上三种方法,就会报java.lang.IllegalMonitorStateException异常。 ❹JVM基于多线程,默认情况下不能保证运行时线程的时序。 ❺进程的释放和控制都是与对象相关。
2.综合实例
2.1使用多个进程排序获得多维数组的最大值
public class FindMax { public static void main(String[] args){ final int row=100,col=200; long startTime=0, endTime=0; double[][] matrix=randomMatrix.getMatrix(row, col); maxThread[] eachThread= new maxThread[row]; double max=Double.MIN_VALUE; startTime=System.currentTimeMillis(); for(int i=0;i<row;i++){ eachThread[i]=new maxThread(matrix[i]); eachThread[i].start(); } try{ for(int i=0;i<row;i++) {eachThread[i].join();//阻塞主进程直到当前进程运行完毕 max=Math.max(max, eachThread[i].getMax());} endTime=System.currentTimeMillis();} catch(InterruptedException e){ System.out.println(e);} System.out.println("span="+(endTime-startTime)+"\nMax="+max); } } /*****************生成随机数组************************/ class randomMatrix { public static double[][] getMatrix(int row,int col) { double[][] matrix=new double[row][col]; for(int i=0;i<row;i++) for(int j=0;j<col;j++) matrix[i][j]=Math.random()*100; return matrix; } } /*****************排序进程************************/ class maxThread extends Thread { private double max=Double.MIN_VALUE; private double[] data; maxThread(double[] array) { data=array; } public double getMax(){ return max;} public void run() { for(int i=0;i<data.length;i++) max=Math.max(max, data[i]); } }
时间间隔为0。可见并行计算的效率极高。
2.2 生产-消费模拟
设定场景 1.多个生产者。当生产的数量大于特定数值时不再生产。 2.多个消费者。当商店的物品不足一定数量时,不能消费。 3.生产和消费都需要一定的时间。 4.生产和消费进程都在同一个商店中
商品类:
import java.text.NumberFormat; public final class Product { private int ID; private double price; public Product(int num) { ID=num; price= Math.random()*20+5;} public String toString(){ String amount=NumberFormat.getCurrencyInstance().format(price); return "ID: "+ID+"; Price"+amount; } }生产厂家:
public class producer extends Thread{ private static volatile int productNumber; private Shop shop; public producer(Shop shop1){ this.shop=shop1; } public void run() { try{ productNumber++; Product product=new Product(productNumber); Thread.sleep((int)Math.random()*10000);//生产时间跨度 shop.producting(product);//生产完毕,加入商店商品目录 System.out.println(product+"product by " +this.getName());} catch(InterruptedException e){ Thread.currentThread().interrupt(); System.out.println(e); } } }消费者:
public class consumer extends Thread{ private Shop shop; public consumer(Shop shop1){ this.shop=shop1; } public void run(){ Product product; try{ Thread.sleep((int) Math.random()*1000);//消费选择时间 product=shop.consuming();//选择完毕,从商店货架移除 System.out.println(product+"is consuming by "+ this.getName()); } catch(InterruptedException e){ Thread.currentThread().interrupt(); } } }
商店:
package multithreadProduct; import java.util.*; public class Shop { private volatile LinkedList<Product> productList=new LinkedList<Product>();// 产品队列 public synchronized void producting(Product product)//线程协调方法 { while(productList.size()>10){ try{ System.out.println("too much product, waiting consumers to buy......"+ productList.size()); System.out.println("Producer: "+Thread.currentThread().getName()+" is waiting...."); wait(100); } catch(InterruptedException e) {System.out.println(e);} } notifyAll();//通知其他线程有机会进入 productList.addLast(product); System.out.println("Product success! There are "+productList.size()+" products is avaliable"); } public synchronized Product consuming()//线程协调方法 { while(productList.size()<1){ try{ System.out.println("Product is not enough, only "+productList.size()+" is available"); System.out.println("Consumer "+Thread.currentThread().getName()+" is waiting......"); wait(); } catch(InterruptedException e){ System.out.println(e); } } Product product=productList.removeFirst(); System.out.println("Consumeing Success! "+Thread.currentThread().getName()+" get what s/he want"); return product; } public synchronized int getSize()//线程协调方法 { return productList.size(); } }<span style="font-size: 18px;"><strong> </strong></span>
</pre><pre>测试:
public class MultiThreadShop { public static void main(String[] args){ final int numberOFproducer=100;//100个生产厂家 final int numberOFconsumer=56;//56个消费者 Thread[] producers=new producer[numberOFproducer]; Thread[] consumers=new consumer[numberOFconsumer]; Shop shop=new Shop(); for(int i=0;i<producers.length;i++){ producers[i]=new producer(shop); producers[i].start(); } for(int i=0;i<consumers.length;i++){ consumers[i]=new consumer(shop); consumers[i].start();} } }
分析: ❶任何时候,只有一个进程可以进行产品的生产和消费操作。 ❷产品数超过15。任何一个生产进程必须等待100毫秒,即等待消费。 ❸产品数小于0。则无限等待。 ❹进程协调的目标对象是shop,即任何时候对象shop的控制权只能被一个线程掌控。
3.其它知识点
1.private static和public static的比较,区别在于修改的范围不同。但作用域是否全局,与具体线程无关。 2.不同类型数组间赋值,抛出ArrayStoreException异常。 3.有效利用多线程的关键是理解程序是并发执行而不是串行执行的。例如:程序中有两个子系统需要并发执行,这时候就需要利用多线程编程。 4.通过对多线程的使用,可以编写出非常高效的程序。不过请注意,如果你创建太多的线程,程序执行的效率实际上是降低了,而不是提升了。 5.请记住,上下文的切换开销也很重要,如果你创建了太多的线程,CPU花费在上下文的切换的时间将多于执行程序的时间!
参考
1.private static和public static的比较:多线程间
2. Java 多线程编程
3.最简实例说明wait、notify、notifyAll的使用方法