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. QT5的QDesktopSerivices不同

    QT4使用QDesktopServices::storageLocation(QDesktopServices::xxxx)来获取一些系统目录, 现在则要改成QStandardPaths::writa ...

  2. STM32 ~ J-LINK V8 修复

    1.1    安装固件烧录软件 ♦请ATMEL官方网址下载AT91-ISP下载软件. 软件下载地址:http://www.atmel.com/dyn/products/tools_card.asp?t ...

  3. overflow-y:auto 回到顶部

    overflow-y     内容溢出元素框时发生的事情. overflow-y:auto        内容溢出元素框时自动出现滚动条,滑动滚动条显示溢出的内容. 滚动条回到顶部 var conta ...

  4. Windows命令行(DOS命令)教程

    一.命令行简介 命令行就是在Windows操作系统中打开DOS窗口,以字符串的形式执行Windows管理程序. 在这里,先解释什么是DOS? DOS——Disk Operation System 磁盘 ...

  5. 每天一个Linux命令(35)wc命令

          Linux系统中的wc(Word Count)命令的功能为统计指定文件中的字节数.字数.行数,并将统计结果显示输出.       (1)用法:     用法:  wc [选项] [文件]. ...

  6. 【leetcode刷题笔记】Combination Sum II

    Given a collection of candidate numbers (C) and a target number (T), find all unique combinations in ...

  7. css 行内元素 块元素 替换元素 非替换元素 以及这些元素的width height margin padding 特性

    一.各种元素的width height margin padding 特性(具体css元素的分来参看二) 1.块级元素 width. height. margin的四个方向. padding的四个方向 ...

  8. UI控件概述

    常见UI控件 UIKit框架提供了非常多功能强大又易用的UI控件,以便于开发者打造出各式各样的App 以下列举一些在开发中常见的UI控件(稍后补上图片示例) 1.UILabel– 文本标签:作用是显示 ...

  9. Shiro-Permissions 对权限的深入理解

    单个权限 query单个资源多个权限 user:query user:add 多值 user:query,add单个资源所有权限 user:query,add,update,delete user:* ...

  10. Java_异常_05_ OutOfMemoryError: Java heap space

    一.异常现象: 二.异常原因 JAVA的堆栈设置太小 注: 出现此异常之后,会引发其他的问题. 三.异常解决 手动设置Heap size: 修改 TOMCAT_HOME/bin/catalina.sh ...