多线程之间通讯

1. 多线程之间如何实现通讯

什么是多线程之间通讯?

多线程之间通讯,其实就是多个线程在操作同一个资源,但是操作的动作不同

代码01 多线程发生线程安全问题(数据错乱):/thread03/src/com/mysoft/demo1/GoodsDemo01.java

package com.mysoft.demo1;

/**
 * 
 * @author lpz
 * 生产者与消费者同时进行发生线程安全问题(数据错乱)
 */

//创建实体类
class Goods{
	
	private  String type;
	private String name;
	
	public String getType() {
		return type;
	}
	public void setType(String type) {
		this.type = type;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
}


//生产者生产商品
class Produce extends Thread{
	
	public Goods goods;
	
	public Produce(Goods goods) {
		this.goods=goods;
	}
	
	@Override
	public void run() {
		int count=0;
		while(true) {
			if(count==0) {
				goods.setType("日用品");
				goods.setName("香皂");
			}
			if(count==1) {
				goods.setType("水果");
				goods.setName("苹果");
			}
			count=(count+1)%2;
		}
	}
}

//消费者消费商品
class Consumer extends Thread{
	public Goods goods;
	
	public Consumer(Goods goods) {
		this.goods=goods;
	}
	
	@Override
	public void run() {
		while(true) {
			System.out.println(goods.getType()+","+goods.getName());
		}
	}	
}



public class GoodsDemo01 {

	public static void main(String[] args) {
		//创建商品共享对象
		Goods goods=new Goods();
		//创建两个线程,共享goods
		Thread t1=new Thread(new Produce(goods));
		Thread t2=new Thread(new Consumer(goods));
		t1.start();
		t2.start();
	}
}

运行结果:
数据发生错乱
代码02 生产者与消费者案例:/thread03/src/com/mysoft/demo2/GoodsDemo02.java

同步解决了数据错乱问题,没有实现生产1个消费1个

package com.mysoft.demo2;

/**
 * 
 * @author lpz
 * 生产者与消费者:同步解决了数据错乱问题,没有实现生产1个消费1个
 */

//创建商品类
class Goods{
	
	private  String type;
	private String name;
	
	public String getType() {
		return type;
	}
	public void setType(String type) {
		this.type = type;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
}


//生产者生产商品
class Produce extends Thread{
	
	public Goods goods;
	
	public Produce(Goods goods) {
		this.goods=goods;
	}
	
	@Override
	public void run() {
		int count=0;
		while(true) {
			//同步解决数据错乱,同步锁必须是共享对象
			synchronized (goods) {
				if(count==0) {
					goods.setType("日用品");
					goods.setName("香皂");
				}
				if(count==1) {
					goods.setType("水果");
					goods.setName("苹果");
				}
				count=(count+1)%2;
			}			
		}
	}
}

//消费者消费商品
class Consumer extends Thread{
	public Goods goods;
	
	public Consumer(Goods goods) {
		this.goods=goods;
	}
	
	@Override
	public void run() {
		//同步解决数据错乱,同步锁必须是共享对象		
		while(true) {
			synchronized (goods) {
				System.out.println(goods.getType()+","+goods.getName());
			}
		}		
	}	
}



public class GoodsDemo02 {

	public static void main(String[] args) {
		//创建商品共享对象
		Goods goods=new Goods();
		//创建两个线程,共享goods
		Thread t1=new Thread(new Produce(goods));
		Thread t2=new Thread(new Consumer(goods));
		t1.start();
		t2.start();
	}
}

2. wait()、notify()、notifyAll()区别

wait()、notify()、notifyAll()详解:
 1、wait()、notify/notifyAll() 方法是Object的本地final方法,无法被重写。
 2.wait()使当前线程阻塞,前提是 必须先获得锁,一般配合synchronized 关键字使用,即,一般在
  synchronized 同步代码块里使用 wait()、notify/notifyAll() 方法。
 3、由于 wait()、notify/notifyAll() 在synchronized 代码块执行,说明当前线程一定是获取了锁的。
  当线程执行wait()方法时候,会释放当前的锁,然后让出CPU,进入等待状态。
  只有当 notify/notifyAll() 被执行时候,才会唤醒一个或多个正处于等待状态的线程,然后继续往
  下执行,直到执行完synchronized 代码块的代码或是中途遇到wait() ,再次释放锁。
  也就是说,notify/notifyAll() 的执行只是唤醒沉睡的线程,而不会立即释放锁,锁的释放要看代码
  块的具体执行情况。所以在编程中,尽量在使用了notify/notifyAll() 后立即退出临界区,以唤醒其
  他线程让其获得锁
 4、wait() 需要被try catch包围,以便发生异常中断也可以使wait等待的线程唤醒。
 5、notify 和wait 的顺序不能错,如果A线程先执行notify方法,B线程在执行wait方法,那么B线程是
   无法被唤醒的。
 6、notify 和 notifyAll的区别
  notify方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程等待一个对
  象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。
  notifyAll 会唤醒所有等待(对象的)线程,尽管哪一个线程将会第一个处理取决于操作系统的实现。
  如果当前情况下有多个线程需要被唤醒,推荐使用notifyAll 方法。比如在生产者-消费者里面的使
  用,每次都需要唤醒所有的消费者或是生产者,以判断程序是否可以继续往下执行。
 7、在多线程中要测试某个条件的变化,使用if 还是while?
  要注意,notify唤醒沉睡的线程后,线程会接着上次的执行继续往下执行。所以在进行条件判断时
  候,可以先把 wait 语句忽略不计来进行考虑;显然,要确保程序一定要执行,并且要保证程序直
  到满足一定的条件再执行,要使用while进行等待,直到满足条件才继续往下执行。如下代码:

代码03 多线程之间的通讯:/thread03/src/com/mysoft/demo3/GoodsDemo03.java

要求:生产者线程生产一个,消费者线程立即消费。生产者没有任何生产,消费者不能消费。消费者没有消费完,生产不能再继续生产。

package com.mysoft.demo3;

/**
 * 
 * @author lpz
 * 多线程之间通讯
 */

//创建商品类
class Goods{
	
	public String type;
	public String name;
	
	//线程通讯标志
	public boolean flag=true;
}


//生产者生产商品
class Produce extends Thread{
	
	public Goods goods;
	
	public Produce(Goods goods) {
		this.goods=goods;
	}
	
	@Override
	public void run() {
		int count=0;
		while(true) {
			//同步解决数据错乱,同步锁必须是共享对象
			synchronized (goods) {
				//flag为false,停止生产,等待消费者消费
				if(!goods.flag) {
					try {
						goods.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				if(count==0) {
					goods.type="日用品";
					goods.name="香皂";
				}
				if(count==1) {
					goods.type="水果";
					goods.name="苹果";
				}
				count=(count+1)%2;
				goods.flag=false;
				goods.notify();
			}			
		}
	}
}

//消费者消费商品
class Consumer extends Thread{
	public Goods goods;
	
	public Consumer(Goods goods) {
		this.goods=goods;
	}
	
	@Override
	public void run() {
		//同步解决数据错乱,同步锁必须是共享对象
		
		while(true) {
			synchronized (goods) {
				//flag为true时候,停止消费,等待生产者生产
				if(goods.flag) {
					try {
						goods.wait();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				System.out.println(goods.type+","+goods.name);
				goods.flag=true;
				goods.notify();
			}
		}		
	}	
}



public class GoodsDemo03 {

	public static void main(String[] args) {
		//创建商品共享对象
		Goods goods=new Goods();
		//创建两个线程,共享goods
		Thread t1=new Thread(new Produce(goods));
		Thread t2=new Thread(new Consumer(goods));
		t1.start();
		t2.start();
	}
}

3. Lock锁(实现多线程之间通讯)

3.1 Lock锁的写法
Lock lock  = new ReentrantLock();
lock.lock();
try{
//可能会出现线程安全的操作
}finally{
//一定在finally中释放锁
//也不能把获取锁在try中进行,因为有可能在获取锁的时候抛出异常
  lock.ublock();
}

3.2 Lock 接口与 synchronized 关键字的区别

1、Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现,synchronized是在JVM层面上实现的,不但可以通过一些监控工具synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定。但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将 unLock()放到finally{} 中;
2、synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
3、Lock可以让等待锁的线程响应中断,线程可以中断去干别的事务,而synchronized却不行,使
用synchronized时,等待的线程会一直等待下去,不能够响应中断
4、通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
5、Lock可以提高多个线程进行读操作的效率。

3.3 Condition用法
//创建condition
Condition condition = lock.newCondition();
//当前线程等待
condition.await();  //类似synchronized中的wait()
//唤醒某一个使用lock锁正在等待的线程
condition. Signal()  //类似synchronized中的notify()

代码04 lock实现多线程之间的通讯:/thread03/src/com/mysoft/demo4/GoodsDemo04.java

package com.mysoft.demo4;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author lpz
 * lock实现多线程之间的通讯
 */

//商品类
class Goods{
	public String type;
	public String name;
	//线程通讯标志
	public boolean flag=false;
	//锁对象
	Lock lock=new ReentrantLock();
}

//生产者
class Produce extends Thread{
	Goods goods;
	Condition condition;
	
	public Produce(Goods goods,Condition condition) {
		this.goods=goods;
		this.condition=condition;
	}
	
	@Override
	public void run() {
		int count=0;
		while(true) {
			//上锁lock
			goods.lock.lock();
			try {
				if(!goods.flag) {
					try {
						//当前线程等待,并且释放锁
						condition.await();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				
				if(count==0) {
					goods.type="日用品";
					goods.name="香皂";
				}
				if(count==1) {
					goods.type="水果";
					goods.name="苹果";
				}
				//奇数偶数之间相互转换
				count=(count+1)%2;
				goods.flag=false;
				//唤醒某一个使用lock锁正在等待的线程
				condition.signal();
			} finally {
				goods.lock.unlock();
			}			
		}
	}
}

class Consumer extends Thread{
	Goods goods;
	Condition condition;
	public Consumer(Goods goods,Condition condition) {
		this.goods=goods;
		this.condition=condition;
	}
	
	@Override
	public void run() {
		while (true) {
			//上锁lock
			goods.lock.lock();
			try {
				if(goods.flag) {
					try {
						//当前线程等待,并且释放锁
						condition.await();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}				
				System.out.println(goods.type+","+goods.name);
				goods.flag=true;
				condition.signal();
			} finally {
				//唤醒某一个使用lock锁正在等待的线程
				goods.lock.unlock();
			}			
		}
	}
}


public class GoodsDemo04 {

	public static void main(String[] args) {
		//实现同步和通讯,必须保证lock锁和Condition对象都是唯一的
		Goods goods=new Goods();
		Condition condition=goods.lock.newCondition();
		Thread t1=new Thread(new Produce(goods,condition));
		Thread t2=new Thread(new Consumer(goods,condition));
		t1.start();
		t2.start();
	}
}

4. 怎么停止线程

停止线程的3种方法

1、使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
2、使用stop方法强行终止线程(这个方法不推荐使用,因为stop和suspend、resume一样,也可能发生不可预料的结果)。
3、使用interrupt方法中断线程

代码05 使用退出标志终止线程:/thread03/src/com/mysoft/demo5/StopThreadDemo01.java

注意:
1、如果主线程修改flag的值后,立即休眠1000ms,那么jmm将不能保证主线程与子线程之间的可见性。
2、使用volatile修饰共享变量flag,可以保证主线程和子线程之间的可见性

package com.mysoft.demo5;

/**
 * @author lpz
 * 停止线程的方法:
 * 	  1、如果主线程修改flag的值后,立即休眠1000ms,那么jmm将不能保证主线程与子线程之间的可见性。
 *       子线程无法获取最新的flag的值,而无法停掉
 *    2、使用volatile修饰共享变量flag,可以保证主线程和子线程之间的可见性
 */

class Thread01 extends Thread{
	
	private volatile boolean flag=true;
	
	//volatile保证可见性
	//private volatile boolean flag=true; 
	
	@Override
	public void run() {
		synchronized (this) {
			System.out.println("子线程开始执行.....");
			while(flag) {
				
			}
			System.out.println("子线程执行结束.....");
		}		
	}
	
	public void stopThread() {
		this.flag=false;
	}
}


public class StopThreadDemo01 {
	public static void main(String[] args) throws InterruptedException {
		Thread01 thread=new Thread01();
		Thread t1=new Thread(thread);
		t1.start();
		for (int i = 0; i < 10; i++) {
			//主线程修改flag后立即休眠,会影响jvm的可见性。所以子线程没有停掉
			Thread.sleep(1000);
			System.out.println("主线程"+i);
			if(i==5) {
				//方法1.调用stopThread方法停止线程
				thread.stopThread();
			}			
		}
	}
}

代码06 Thread类的stop()方法终止线程:/thread03/src/com/mysoft/demo5/StopThreadDemo02.java

package com.mysoft.demo5;

/**
 * @author lpz
 * 停止线程的方法:使用Thread类的stop()方法,不介意使用
 */

class Thread02 extends Thread{
	
	@Override
	public void run() {
		synchronized (this) {
			System.out.println("子线程开始执行.....");
			while(true) {
				
			}
			//System.out.println("子线程执行结束.....");
		}		
	}
}


public class StopThreadDemo02 {
	@SuppressWarnings("deprecation")
	public static void main(String[] args) throws InterruptedException {
		Thread02 thread=new Thread02();
		Thread t1=new Thread(thread);
		t1.start();
		for (int i = 0; i < 10; i++) {
			//主线程修改flag后立即休眠,会影响jvm的可见性。所以子线程没有停掉
			Thread.sleep(1000);
			System.out.println("主线程"+i);
			if(i==5) {
				//方法2 使用Thread类的stop()方法。
				t1.stop();
			}			
		}
	}
}

代码06 Thread类interrupt()方法终止程:/thread03/src/com/mysoft/demo5/StopThreadDemo03.java

Thread类的interrupt()方法作用:如果调用该方法的线程处于等于等待状态,则该线程抛出异常

package com.mysoft.demo5;

/**
 * @author lpz
 * 停止线程的方法:Thread类的interrupt()方法
 */

class Thread03 extends Thread{
	
	private volatile boolean flag=true;
	
	@Override
	public void run() {
		synchronized (this) {
			System.out.println("子线程开始执行.....");
			while(flag) {
				try {
					wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
					stopThread();
				}				
			}
			System.out.println("子线程执行结束.....");
		}		
	}
	
	public void stopThread() {
		this.flag=false;
	}
}


public class StopThreadDemo03 {
	public static void main(String[] args){
		Thread03 thread=new Thread03();
		Thread t1=new Thread(thread);
		t1.start();
		for (int i = 0; i < 10; i++) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("主线程"+i);
			if(i==5) {
				//方法3 使用Thread类的interrupt()方法。
				//如果当前线程正在等待,则直接抛出异常
				t1.interrupt();
			}			
		}
	}
}

5. 生产者消费者模型

5.1 多个生产者多个消费者(生产者生产一种商品)

代码06 多个生产者多个消费者,生产者生产一种商品

package 生产者消费者模型;

import java.util.ArrayList;
import java.util.List;

//货物类
class Goods {
    private String goods;
    private int count;
    public Goods(String goods) {
        this.goods = goods;
    }

    /**
     * 生产者生产货物的方法
     */
    public synchronized void produce(){
        //不断判断执行条件
        /**
         * 因为有多个线程 假设现在已有商品,生产者线程均在等待
         * 消费者线程唤醒生产者线程后,假设此时生产者线程1生产产品
         * 此时count=1,若用if判断会造成count数量一直增加
         */
        while(count > 0){
            System.out.println("该商品还有库存....");
            //生产者等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果没有库存 商品数量+1
        this.count++;
        System.out.println(Thread.currentThread().getName()+"生产产品"+toString());
        //唤醒所有消费者
        notifyAll();
    }

    /**
     * 消费者消费商品
     */
    public synchronized void consumer(){
        //不断判断执行条件
         /**因为有多个线程 假设消费者线程先执行、由于不止一个消费者
          * 此时可能有多个消费者在等待,如果此时一个生产者生产了商品
          * 消费者线程均被唤醒、如果此时线程2消费产品 此时若用if判断
          * 其余等待线程也会count-- 造成错误 所以需要用while判断
         */
        while(count == 0){
            System.out.println("仓库已空...");
            //消费者等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //消费商品 数量-1
        this.count--;
        System.out.println(Thread.currentThread().getName()+"消费产品"+toString());
        //唤醒所有生产者
        notifyAll();
    }

    @Override
    public String toString() {
        return "Goods{" +
                "goods='" + goods + '\'' +
                ", count=" + count +
                '}';
    }
}
//生产者线程
class Producer implements Runnable{
    //生产的货物
    private Goods goods;

    public Producer(Goods goods) {
        this.goods = goods;
    }

    @Override
    public void run() {
        while(true){
            this.goods.produce();
        }
    }
}
//消费者线程
class Consumer implements Runnable{
    //消费的货物
    private Goods goods;

    public Consumer(Goods goods) {
        this.goods = goods;
    }

    @Override
    public void run() {
        while(true){
            this.goods.consumer();
        }
    }
}
public class Test{
    public static void main(String[] args) {

       Goods goods = new Goods("特百惠杯子");
       List<Thread> list = new ArrayList<>();

       for(int i=0;i<10;i++){
           Thread thread = new Thread(new Producer(goods),"生产者"+i);
           list.add(thread);
       }

        for(int i=0;i<5;i++){
            Thread thread = new Thread(new Consumer(goods),"消费者"+i);
            list.add(thread);
        }

       for(Thread thread:list){
           //消费者、生产者谁先启动不一定
           thread.start();
       }
    }
}
5.2 多个生产者多个消费者(生产者生产商品数量不固定)

代码07 多个生产者多个消费者(生产者生产商品数量不固定)

package 生产者消费者模型;

import java.util.ArrayList;
import java.util.List;

//货物类
class Goods {
    private String goods;
    private int count;
    public Goods(String goods) {
        this.goods = goods;
    }

    public int getCount() {
        return count;
    }

    /**
     * 生产者生产货物的方法
     */
    public synchronized void produce(){
        //如果没有库存 商品数量+1
        if(this.count<10){
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.count++;
            System.out.println(Thread.currentThread().getName()+"生产产品"+toString());
        }
        //唤醒所有消费者
        notifyAll();
    }

    /**
     * 消费者消费商品
     */
    public synchronized void consumer(){
        //不断判断执行条件
         /**因为有多个线程 假设消费者线程先执行、由于不止一个消费者
          * 此时可能有多个消费者在等待,如果此时一个生产者生产了商品
          * 消费者线程均被唤醒、如果此时线程2消费产品 此时若用if判断
          * 其余等待线程也会count-- 造成错误 所以需要用while判断
         */
        while(count == 0){
            System.out.println("仓库已空...");
            //消费者等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //消费商品 数量-1
        this.count--;
        System.out.println(Thread.currentThread().getName()+"消费产品"+toString());
        //唤醒所有生产者
        notifyAll();
    }

    @Override
    public String toString() {
        return "Goods{" +
                "goods='" + goods + '\'' +
                ", count=" + count +
                '}';
    }
}
//生产者线程
class Producer implements Runnable{
    //生产的货物
    private Goods goods;

    public Producer(Goods goods) {
        this.goods = goods;
    }

    @Override
    public void run() {
        //最大库存为10
        while(true){
            this.goods.produce();
        }
    }
}
//消费者线程
class Consumer implements Runnable{
    //消费的货物
    private Goods goods;

    public Consumer(Goods goods) {
        this.goods = goods;
    }

    @Override
    public void run() {
        while(true){
            this.goods.consumer();
        }
    }
}
public class Test{
    public static void main(String[] args) {

       Goods goods = new Goods("特百惠杯子");
       List<Thread> list = new ArrayList<>();
       
        for(int i=0;i<10;i++){
            Thread thread = new Thread(new Producer(goods),"生产者"+i);
            list.add(thread);
        }
        for(int i=0;i<5;i++){
            Thread thread = new Thread(new Consumer(goods),"消费者"+i);
            list.add(thread);
        }

       for(Thread thread:list){
           //消费者、生产者谁先启动不一定
           thread.start();
       }
    }
}

6. ThreadLocal

1、ThreadLocal是什么?
  为共享变量在每个线程中创建一个副本,每个线程可以访问自己内部的副本变量。
2、ThreadLocal的接口方法

void set(Object value)      设置当前线程的线程局部变量的值。
public Object get()       该方法返回当前线程所对应的线程局部变量。
public void remove()      将当前线程局部变量的值删除
protected Object initialValue()  返回该线程局部变量的初始值。在线程第1次调用get()或
                set(Object)时才执行,并且仅执行1次

代码08 ThreadLocal解决多个线程之间线程安全问题

package com.txkt.demo;

public class ThreadLocalDemo2 {

    //多个线程使用的同一个threadLocal
    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
        @Override
        //初始化value
        protected Integer initialValue() {
            return 0;
        }
    };

    public static void main(String[] args) {
        //创建一个线程数组
        Thread[] threads=new Thread[5];
        for (int i = 0; i <threads.length; i++) {
            //实例化线程对象
            threads[i]=new Thread(()->{
                //线程执行体
                //获取当前线程threadLocal实例对应的int型value
                int num = threadLocal.get().intValue();
                num+=5;
                //修改value值
                threadLocal.set(num);
                System.out.println(Thread.currentThread().getName()+":"+num);
            },"thread-"+(i+1));
        }
        for (int i = 0; i <threads.length; i++) {
            //启动线程
            threads[i].start();
        }
    }
}

总结如下:

1、对于某一ThreadLocal来讲,他的索引值i是确定的,在不同线程之间访问时访问的是不同的table
  数组的同一位置即都为table[i],只不过这个不同线程之间的table是独立的。
2、对于同一线程的不同ThreadLocal来讲,这些ThreadLocal实例共享一个table数组,然后每个
  ThreadLocal实例在table中的索引i是不同的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值