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()函数继续进行,输出上一轮设置后的数据

实例2:

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的使用方法

Java探索之旅(18)——多线程(2)的更多相关文章

  1. Java探索之旅(17)——多线程(1)

    1.多线程  1.1线程 线程是程序运行的基本执行单元.指的是一段相对独立的代码,执行指定的计算或操作.多操作系统执行一个程序时会在系统中建立一个进程,而在这个进程中,必须至少建立一个线程(这个线程被 ...

  2. Java探索之旅(1)——概述与控制台输入

    使用的课本: Java语言程序设计(基础篇)----西电 李娜(译) 原著: Introduction to Java Progrmming(Eighth Edition) -----Y.Daniel ...

  3. Java探索之旅(16)——异常处理

    1.异常与异常处理 在<java编程思想>中这样定义 异常:阻止当前方法或作用域继续执行的问题.虽然java中有异常处理机制,但是要明确一点,决不应该用"正常"的态度来 ...

  4. Java探索之旅(15)——包装类和字符类

    1.包装类 ❶出于对性能的考虑,并不把基本数据类型作为对象使用,因为适用对象需要额外的系统花销.但是某些Java方法,需要对象作为参数,例如数组线性表ArrayList.add(Object).Jav ...

  5. Java探索之旅(14)——文本I/O与读写

    1文件类File    ❶封装文件或路径的属性.不包括创建和读写文件操作.File实例并不会实际创建文件.不论文件存在与否,可以创建任意文件名的实例.两种实例创建方式如下:               ...

  6. Java探索之旅(13)——字符串类String

    1.初始化 String类是Java预定义类,非基本类型而是引用类型. public class StudyString { public static void main(String[] args ...

  7. Java探索之旅(12)——equals方法及其覆盖

    1.Object中的equals方法 java中的的基本数据类型:byte,short,char,int,long,float,double,boolean.==比较的是值. ❶作用:对于复合类型来说 ...

  8. Java探索之旅(11)——抽象类与接口

    1.Java数据类型       ❶不可变类,是指当创建了这个类的实例后,就不允许修改它的属性值. 它包括:         Primitive变量:boolean,byte, char, doubl ...

  9. Java探索之旅(10)——数组线性表ArrayList和字符串生成器StringBuffer/StringBuilder

    1.数组线性表ArrayList 数组一旦定义则不可改变大小.ArrayList可以不限定个数的存储对象.添加,插入,删除,查找比较数组更加容易.可以直接使用引用类型变量名输出,相当于toString ...

随机推荐

  1. RLearning第1弹:初识R语言

    R作为一种统计分析软件,是集统计分析与图形显示于一体的.体积小.开源.很强的互动性.自从学了R本人就很少再用matlab了... 一.R语言由函数和赋值构成. R使用<-(最好养成使用习惯),而 ...

  2. Python赋值原理:Python无变量,万物皆对象

    有几个和以前的常见语言,比如c语言不同 改变变量数据不覆盖原来的 name = '苍老师' print(id(name)) name = '志玲' print(id(name)) 运行结果 73955 ...

  3. mysql语句优化技巧

    1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引.2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引 ...

  4. C#仿QQ设置界面导航

    效果预览,选择左边标签,右边内容会自动滚动到适当位置 public class AnchorPanel { List<PanelMenu> lst = new List<PanelM ...

  5. systemverilog FAQ(zz)

    1. What is clocking block? Ans: Clocking block can be declared using the keywords clocking and endcl ...

  6. 20165101 学习基础和C语言基础调查

    学习基础和C语言基础调查 技能学习心得 看了15级学长学姐丰富的技能之后,我感到很惭愧.我的课外技能可以说是很糟糕.唱歌的话,小时候还可以用假声唱一下,变声之后就是高音上不去,低音下不来.体育更是差劲 ...

  7. 转的es6 =>函数

    原文地址 箭头函数=>无疑是ES6中最受关注的一个新特性了,通过它可以简写 function 函数表达式,你也可以在各种提及箭头函数的地方看到这样的观点--"=> 就是一个新的 ...

  8. Ansible 实战之部署Web架构

    WEB架构(ubuntu 16.04): Proxy -- WebServer(Nginx+PHP+Django) -- Nosql -- MariaDB 一. 定义Inventory [proxy] ...

  9. Spark-Spark setMaster & WordCount Demo

    Spark setMaster源码 /** * The master URL to connect to, such as "local" to run locally with ...

  10. Hadoop- MR的shuffle过程

    step1 input InputFormat读取数据,将数据转换成<key ,value>对,设置FileInputFormat,默认是文本格式(TextInputFormat) ste ...