• 多线程同步


    多线程同步

    回顾

    1 进程:正在运行的程序,操作系统通过进程Id区分不同进程。
    
    2 线程:进程中的一条执行路径。一个进程中可以包含多个线程,至少有一个。
    
    3 区别:
    	a.一个程序运行后至少有一个进程
    	b.一个进程可以包含多个线程,但是至少需要有一个线程,否则这个进程是没有意义的
    	c.进程间不能共享资源,但线程之间可以
    	d.系统创建进程需要为该进程重新分配系统资源,而创建线程则容易的多,因此使用线程实现多任务并发比多进程的效率高
    4 多线程的创建
    	(1)继承Thread ,重写run方法
    	(2)实现Runnable接口
    	(3)实现Callable<V>接口  有返回值,可以抛出异常,FutureTask<T>
    5 线程的方法
    	线程对象.setName(); 继承Thread类构造方法
    	休眠Thread.sleep();
    	线程对象.priority();
    	线程对象.join();//加入合并
    	线程对象.setDaemone(true);//后台线程  垃圾回收器
    	Thread.yield();//让步
    	线程对象.interrupt();//打断  打断所有能抛出InterruptException的线程
    	
    6 声明周期
    五个状态
    	新生--->就绪---->运行---->阻塞----->死亡
    

    今天任务

    1.多线程访问临界资源
    	1.1 多线程访问临界资源时的数据安全问题
    	1.2 解决临界资源问题
    	1.3 锁
    	1.4 同步代码块
    	1.5 同步方法
    	1.6 ReentrantLock类
    2.死锁
    3.多线程在单例设计模式中的应用
    4.线程的通信(难)
    	4.1 原理
    	4.2 实现
    

    教学目标

    1.了解多线程中临界资源问题产生的原因
    2.掌握解决临界资源问题的方案
    3.掌握锁的概念
    4.掌握同步代码块和同步方法
    5.了解ReentrantLock类的使用
    6.掌握多线程在单例中的应用
    7.了解死锁以及生产者与消费者设计模式
    

    第一节 多线程访问临界资源

    1.1 多线程访问临界资源时的数据安全问题
    	临界资源 :多个线程同时访问的资源。
    	产生原因:有多个线程在同时访问一个资源,如果一个线程在取值的过程中,时间片又被其他线程抢走了,临界资源问题就产生了
    	
    	演示:卖票案例
    	
    
    1.2 解决临界资源问题
    	解决方案:一个线程在访问临界资源的时候,如果给这个资源“上一把锁”,这个时候如果其他线程也要访问这个资源,就得在“锁”外面等待。
    
    1.3 锁
    锁:任意的对象都可以被当做锁来使用
    
    1.4 同步代码块

    同步:Synchronized:有等待

    异步:Asynchronized:没有等待,各执行各的

    语法:
    	synchronized(锁) {
    		//需要访问临界资源的代码段
    	}
    
    	说明:
    	a.程序走到代码段中,就用锁来锁住了临界资源,这个时候,其他线程不能执行代码段中的代码,只能在锁外边等待
    	b.执行完代码段中的这段代码,会自动解锁。然后剩下的其他线程开始争抢cpu时间片
    	c.一定要保证不同的线程看到的是同一把锁,否则同步代码块没有意义
    

    ​ 2.2.1 同步代码块使用

    public class Ticket implements Runnable{
         // 需求:100张
    	// 临界资源
         private int ticket = 100;
          @Override
          public void run() {
            while (true) {
              //上锁
            synchronized(this){
                if (ticket < 1) {
                  break;
                }
                System.out.println("售票员" + Thread.currentThread().getName() + "售出第"+ticket+"张票");
               ticket--;
           	  }
            }
          }
    }
    
    1.5 同步方法

    ​ 2.3.1 同步非静态方法

    public class Ticket implements Runnable{
         // 需求:100张
    	// 临界资源
          private int ticket = 100;
          @Override
          public void run() {
            while (true) {
    		   if(!sale()){
                    break;
                }
            }
          }
      	 public synchronized boolean sale(){//锁是this
               if (ticket < 1) {
                   return false;
                }
                System.out.println("售票员" + Thread.currentThread().getName() + "售出第"+ticket+"张票");
               ticket--;
           		return true;
         }
    }
    

    ​ 2.3.2 同步静态方法

    public class Ticket implements Runnable{
         // 需求:100张
    	// 临界资源
          private static int ticket = 100;
          @Override
          public void run() {
            while (true) {
    		   if(!sale()){
                    break;
                }
            }
          }
      	 public synchronized static boolean sale(){ //锁是 类.class
               if (ticket < 1) {
                   return false;
                }
                System.out.println("售票员" + Thread.currentThread().getName() + "售出第"+ticket+"张票");
               ticket--;
           	   return true;
         }
    }
    
    1.6 ReentrantLock类(可重入锁)jdk1.5
    	从jdk1.5之后加入新的接口 Lock,ReentrantLock是Lock接口的实现类。
    	通过显式定义同步锁对象来实现同步,同步锁提供了比synchronized代码块更广泛的锁定操
    	注意:最好将 unlock的操作放到finally块中
    	通过使用ReentrantLock这个类来进行锁的操作,它实现了Lock接口,使用ReentrantLock可以显式地加锁、释放锁
    

    ​ 案例一:模拟售票

    import java.util.concurrent.locks.ReentrantLock;
    public class ReentrantLockDemo01 {
    	public static void main(String[] args) {
             Ticket res=new Ticket();
    		Thread t0 = new Thread(res, "喜羊羊");
    		Thread t1 = new Thread(res, "沸羊羊");
    		Thread t2 = new Thread(res, "灰太狼");
    		Thread t3 = new Thread(res, "小灰灰");
    
    		t0.start();
    		t1.start();
    		t2.start();
    		t3.start();
    	}
    }
    
    //票类
    public class Ticket implements Runnable{
         // 需求:100张
    	// 临界资源
         private int ticket = 100;
    
    	// 定义一个ReentrantLock类的对象
         ReentrantLock lock = new ReentrantLock();
          @Override
          public void run() {
            while (true) {
              //上锁
              lock.lock();
              if (count < 1) {
                break;
              }
              System.out.println("售票员" + Thread.currentThread().getName() + "售出第"+ticket+"张票");
      		 ticket--;
              //解锁
              // unlock()
              lock.unlock();
    
              //注意:lock()和unlock()都是成对出现的
            }
          }
    }
    

    ​ 案例二:模拟银行卡存取钱

    package com.qf.day20_4;
    
    public class BankCard {
    	private double money;
    
    	public double getMoney() {
    		return money;
    	}
    
    	public void setMoney(double money) {
    		this.money = money;
    	}
    	
    }
    package com.qf.day20_4;
    /**
     * 存取类
     * @author wgy
     *
     */
    public class AddMoney implements Runnable{
    
    	private BankCard card;
    	
    	
    	public AddMoney(BankCard card) {
    		this.card = card;
    	}
    
    
    	@Override
    	public void run() {
    		for(int i=0;i<10;i++) {
    			synchronized (card) {
    				card.setMoney(card.getMoney()+1000);//钱加上
    				System.out.println(Thread.currentThread().getName()+"存了1000,余额是:"+card.getMoney());
    			}
    			
    		}
    	}
    
    }
    package com.qf.day20_4;
    
    public class SubMoney implements Runnable{
    	private BankCard card;
    	
    	
    	public SubMoney(BankCard card) {
    		
    		this.card = card;
    	}
    
    
    	@Override
    	public void run() {
    	
    		for(int i=0;i<10;i++) {
    			synchronized (card) {
    				if(card.getMoney()>=1000) {
    					card.setMoney(card.getMoney()-1000);
    					System.out.println(Thread.currentThread().getName()+"取了1000,余额是:"+card.getMoney());
    				}else {
    					System.out.println("余额不足");
    					i--;
    				}
    			}
    			
    		}
    	}
    }
    
    
    public class Test {
    	public static void main(String[] args) {
    		//1创建卡
    		BankCard card=new BankCard();
    		//2创建存钱和取钱功能
    		AddMoney add=new AddMoney(card);
    		SubMoney sub=new SubMoney(card);
    		//3创建线程对象
    		Thread zhengshuai=new Thread(add,"帅帅");
    		Thread benwei=new Thread(sub,"本伟");
    		//4启动
    		zhengshuai.start();
    		benwei.start();
    	}
    }
    

    第二节 死锁

    ​ 每个人都拥有其他人需要的资源,同时又等待其他人拥有的资源,并且每个人在获得所有需要的资源之前都不会放弃已经拥有的资源。

    ​ 当多个线程完成功能需要同时获取多个共享资源的时候可能会导致死锁。

    死锁的条件:

    ​ 1两个以上的线程

    ​ 2至少两个锁以上

    ​ 3同步中嵌套同步

    /*
    *锁
    */
    public class Lock {
    	public static Object locka=new Object();//第一个锁
    	public static Object lockb=new Object();//第二个锁
    }	
    
    /*
    *男孩
    */
    public class Boy extends Thread{
    	@Override
    	public void run() {
    		while (true) {
    			synchronized (Lock.locka) {
    				System.out.println("男孩拿着locka");
    				synchronized (Lock.lockb) {
    					System.out.println("男孩拿到lockb");
    					System.out.println("男孩可以吃了....");
    				}
    			} 
    		}
    	}
    }
    
    
    
    /*
     * 女孩
     */
    public class Girl extends Thread{
    	@Override
    	public void run() {
    		while (true) {
    			synchronized (Lock.lockb) {
    				System.out.println("女孩拿着lockb");
    				synchronized (Lock.locka) {
    					System.out.println("女孩拿到了locka");
    					System.out.println("女孩可以吃了...");
    				}
    			} 
    		}
    	}
    }
    
    
    public static void main(String[] args) {
    		Boy shaqiang=new Boy();
    		Girl xiaofeng=new Girl();
    		shaqiang.start();
    		xiaofeng.start();
    	}
    

    第三节 多线程在单例中的应用

    单例的实现方式:懒汉式和饿汉式
    	其中,懒汉式是线程不安全的,当有多条线程同时访问单例对象时,则会出现多线程临界资源问题
    单例实现步骤:
    	1 私有化构造方法
    	2 在类中创建对象
    	3 通过公开的方法返回这个对象
    
    3.1 多线程访问单例-饿汉式
    package com.qf.day20_7;
    /**
     * 单例模式
     * @author wgy
     *
     */
    public class SingleTon {
    	//1私有化构造方法
    	private SingleTon() {
    		
    	}
    	//2创建对象
    	private static SingleTon instance=new SingleTon();
    	//3公开的方法返回这个对象
    	public static SingleTon getInstance() {
    		return instance;
    	}
    	
    }
    
    package com.qf.day20_7;
    
    public class SingleTonThread extends Thread{
    	@Override
    	public void run() {
    		SingleTon singleTon=SingleTon.getInstance();
    		System.out.println(singleTon.hashCode());
    	}
    }	
    
    
    package com.qf.day20_7;
    
    public class Test {
    	public static void main(String[] args) {
    //		SingleTonThread s1=new SingleTonThread();
    //		SingleTonThread s2=new SingleTonThread();
    //		SingleTonThread s3=new SingleTonThread();
    //		s1.start();
    //		s2.start();
    //		s3.start();
    		
    		Runnable r=new Runnable() {
    			
    			@Override
    			public void run() {
    				SingleTon singleTon=SingleTon.getInstance();
    				System.out.println(singleTon.hashCode());
    			}
    		};
    		
    		new Thread(r).start();
    		new Thread(r).start();
    		new Thread(r).start();
    		
    		
    		
    	}
    }
    
    3.2 多线程访问单例-懒汉式
    /**
     * 单例
     * @author wgy
     *
     */
    public class SingleTon {
    	private SingleTon(){
    		 //禁止反射破解
            synchronized (SingleTon.class) {
                if (instance != null) {
                    throw new RuntimeException("不能使用反射创建对象");
                }
            }
    	}
    	private static volatile SingleTon instance; //volatile:不稳定的,易挥发的
    	
    	public static SingleTon getInstance() {
    		if(singleTon==null) {//为了提高效率
    			synchronized (SingleTon.class) {//判断锁的过程比较耗性能,为了提高效率
    				if (singleTon == null) {
    					singleTon = new SingleTon();
    				}
    			}
    		}
    		return singleTon;
    	}
    	
    }
    
    /**
     * 线程类
     * @author wgy
     *
     */
    public class SingleTonThread extends Thread{
    	@Override
    	public void run() {
    		SingleTon singleTon=SingleTon.getInstance();
    		System.out.println(singleTon.hashCode());
    	}
    }
    
    
    package com.qf.day20_6;
    
    public class Test {
    	public static void main(String[] args) {
    		//线程对象
    		SingleTonThread s1=new SingleTonThread();
    		SingleTonThread s2=new SingleTonThread();
    		SingleTonThread s3=new SingleTonThread();
    		//启动线程
    		s1.start();
    		s2.start();
    		s3.start();
    		
    	}
    }
    

    补充单例其他的写法

    静态内部类写法
    /*
     * wgy 2019/8/10 16:18
     * 佛祖保佑,永无BUG!
     * 静态内部类写法
     * (1)节省空间
     * (2)不会线程安全问题
     */
    public class SingleTon2 {
        private SingleTon2(){
    
        }
    
        static class Holder{
            private static final SingleTon2 INSTACNE=new SingleTon2();
        }
    
        public static SingleTon2 getInstance(){
            return Holder.INSTACNE;
        }
        
    }
    
    使用枚举
    /*
     * wgy 2019/8/10 16:22
     * 佛祖保佑,永无BUG!
     * 枚举写法
     * (1)没有线程安全问题
     * (2)反射破解问题
     *
     */
    public enum  SingleTon3 {
        INSTANCE;
        public static SingleTon3 getInstance(){
            return INSTANCE;
        }
    }
    
    

    第四节 线程的通信【生产者与消费者设计模式】

    4.1 线程通信

    需求:你和你朋友公用一张银行卡,你向卡中存钱,你朋友取钱,保证你存一笔,然后取一笔,再存一笔,再取一笔。

    实现功能:使用线程通信。

    在jdk1.5之前有三个方法实现线程通信:

    wait(): 等待,线程执行这个方法进入等待队列(和锁有关,一个锁对应一个等待队列), 需要被唤醒

    notify(): 通知唤醒,从等待队列中随机唤醒一个线程

    notifyAll():全部唤醒,把等待队列中所有的线程都唤醒

    代码实现

    package com.qf.day20_8;
    
    public class BankCard {
    	private double money;
    	
    	private boolean flag;// 标记 true  表示有钱, false没钱
    
    	public double getMoney() {
    		return money;
    	}
    
    	public void setMoney(double money) {
    		this.money = money;
    	}
    	/***
    	 * 存取
    	 */
    	public synchronized void save() { //this
    		
    		while(flag) {//true 有钱
    			try {
    				this.wait();//等待,释放了cpu,释放了锁 ,调用wait的对象是锁
    			} catch (InterruptedException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}//等待
    		}
    		
    		
    		money=money+1000;
    		System.out.println(Thread.currentThread().getName()+"存了1000,余额是:"+money);
    		
    		
    		flag=true;//修改标记
    		this.notifyAll();//唤醒取钱线程取取钱
    		
    	}
    	/**
    	 * 取
    	 */
    	public synchronized void qu() {//this
    		
    		while(flag==false) {
    			try {
    				this.wait();//等待  释放cpu和锁
    			} catch (InterruptedException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    		}
    		
    		money=money-1000;
    		System.out.println(Thread.currentThread().getName()+"取了1000,余额是:"+money);
    		//修改标记
    		flag=false;
    		//唤醒
    		this.notifyAll();
    		
    	}
    	
    }
    
    package com.qf.day20_8;
    /**
     * 存取类
     * @author wgy
     *
     */
    public class AddMoney implements Runnable{
    
    	private BankCard card;
    	
    	
    	public AddMoney(BankCard card) {
    		this.card = card;
    	}
    
    
    	@Override
    	public void run() {
    		for(int i=0;i<10;i++) {
    			card.save();
    		}
    	}
    	
    }
    
    package com.qf.day20_8;
    
    public class SubMoney implements Runnable{
    	private BankCard card;
    	
    	
    	public SubMoney(BankCard card) {
    		
    		this.card = card;
    	}
    
    
    	@Override
    	public void run() {
    	
    		for(int i=0;i<10;i++) {
    			card.qu();
    		}
    	}
    	
    	
    }
    
    package com.qf.day20_8;
    
    public class Test {
    	public static void main(String[] args) {
    		//1创建卡
    		BankCard card=new BankCard();
    		//2创建存钱和取钱功能
    		AddMoney add=new AddMoney(card);
    		SubMoney sub=new SubMoney(card);
    		//3创建线程对象
    		Thread zhengshuai=new Thread(add,"帅帅");
    		Thread dalang=new Thread(add, "大郎");
    		Thread benwei=new Thread(sub,"本伟");
    		Thread xiaolian=new Thread(sub,"金莲");
    		
    		
    		//4启动
    		zhengshuai.start();
    		benwei.start();
    		dalang.start();
    		xiaolian.start();
    		
    	}
    }
    
    4.2 生产者与消费者设计模式原理
    它描述的是有一块缓冲区作为仓库,生产者可以将产品放入仓库,消费者可以从仓库中取走产品,解决生产者/消费者问题,我们需要采用某种机制保护生产者和消费者之间的同步
    
    	同步问题核心在于:如何保证同一资源被多个线程并发访问时的完整性,常用的方法就是加锁,保证资源在任意时刻只被一个线程访问
    

    画图分析:

    生产者与消费者设计模式

    4.3 实现

    采用wait()、notify()和notifyAll()方法

    wait():当缓冲区已满或空时,生产者/消费者线程停止自己的执行,放弃锁,使自己处于等待状态,让其他线程执行
    
    		·是Object的方法
    		·调用方式:对象.wait();
    		·表示释放 对象 这个锁标记,然后在锁外边等待(对比sleep(),sleep是抱着锁休眠的)
    		·等待,必须放到同步代码段中执行	
    
    notify():当生产者/消费者向缓冲区放入/取出一个产品时,向其他等待的线程发出可执行的通知,同时放弃锁,使自己处于等待状态
    
    		·是Object的方法
    		·调用方式:对象.notify();
    		·表示唤醒 对象 所标记外边在等待的一个线程
    
    notifyAll():全部唤醒
    
    		·是Object的方法
    		·调用方式:对象.notifyAll()
    		·表示唤醒  对象 所标记外边等待的所有线程
    
    /*
    *面包
    */
    public class Bread {
    	private int id;
    	private String productName;
    	public Bread() {
    		// TODO Auto-generated constructor stub
    	}
    	
    	public Bread(int id, String productName) {
    		super();
    		this.id = id;
    		this.productName = productName;
    	}
    
    	public int getId() {
    		return id;
    	}
    	public void setId(int id) {
    		this.id = id;
    	}
    	public String getProductName() {
    		return productName;
    	}
    	public void setProductName(String productName) {
    		this.productName = productName;
    	}
    
    	@Override
    	public String toString() {
    		return "Bread [id=" + id + ", productName=" + productName + "]";
    	}
    	
    	
    }
    
    /*
     * 面包容器
     */
    public class BreadCon {
    	private Bread con;
    	private boolean flag; //
    	
    	
    	//放入面包
    	public synchronized void input(Bread b){
    		while(flag==true){
    			try {
    				this.wait();
    			} catch (InterruptedException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    		}
    		
    		
    		this.con=b;
    		System.out.println(Thread.currentThread().getName()+"生产了"+b.getId());
    		flag=true;
    		this.notifyAll();
    	}
    	
    	//吃面包
    	public synchronized void output(){
    		while(flag==false){
    			try {
    				this.wait();
    			} catch (InterruptedException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    		}
    		
    		
    		
    		
    		Bread b=con;
    		con=null;
    		System.out.println(Thread.currentThread().getName()+"消费了"+b.getId()+" 生产者:"+b.getProductName());
    		flag=false;
    		this.notifyAll();
    	}
    }
    
    /*
     * 生产面包类
     */
    public class Product implements Runnable{
    	private BreadCon con;
    	public Product(BreadCon con) {
    		this.con=con;
    	}
    	@Override
    	public void run() {
    		for(int i=1;i<=30;i++){
    			Bread b=new Bread(i, Thread.currentThread().getName());
    			this.con.input(b);
    		}
    	}
    }
    
    /*
     * 消费面包
     */
    public class Consume implements Runnable{
    	private BreadCon con;
    	public Consume(BreadCon con) {
    		this.con=con;
    	}
    	@Override
    	public void run() {
    		for(int i=1;i<=30;i++){
    			con.output();
    		}
    	}
    }
    
    public static void main(String[] args) {
    		//1创建容器
    		BreadCon con=new BreadCon();
    		//2生产
    		Product product=new Product(con);
    		//3消费
    		Consume consume=new Consume(con);
    		//4线程对象
    		Thread shaqiang=new Thread(product, "莎强");
    		Thread xiaocang=new Thread(consume,"小苍");
    		//5启动
    		shaqiang.start();
    		xiaocang.start();
    	}
    

    扩展:使用Jdk1.5 Lock优化生产者和消费者

    package com.qf.day20_11;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    
    
    /**
     * 面包容器
     * @author wgy
     *
     */
    public class BreadCon {
    	private Bread con;
    	private boolean flag;
    	private Lock lock=new ReentrantLock();
    	
    	Condition proCondition=lock.newCondition();
    	Condition conCondition=lock.newCondition();
    	
    	/**
    	 * 放入面包
    	 */
    	public  void input(Bread b) {
    		lock.lock();
    		try {
    			while (flag) {
    				try {
    					proCondition.await();
    				} catch (Exception e) {
    					// TODO: handle exception
    				}
    
    			}
    			con = b;
    			System.out.println(Thread.currentThread().getName() + "生产了" + b.getId() + "面包");
    			flag = true;
    			conCondition.signal();
    		} finally {
    			lock.unlock();
    		} 
    		
    		
    	}
    	/**
    	 * 消费面包
    	 */
    	public  void output() {
    		lock.lock();
    		try {
    			while (!flag) {
    				try {
    					conCondition.await();
    				} catch (Exception e) {
    					// TODO: handle exception
    				}
    			}
    			Bread b = con;
    			System.out
    					.println(Thread.currentThread().getName() + "消费了" + b.getId() + "面包, 生产者名字:" + b.getProductName());
    			con = null;
    			flag = false;
    			proCondition.signal();
    		} finally {
    			lock.unlock();
    		}
    	}
    	
    }	
    

    扩展知识1:读写锁

    ReadWriteLock接口:可以实现多个读线程同时读取数据,写线程需要互斥执行。

    读|写 、写|写 需要互斥

    读|读 不需要互斥

    package com.qf.day13;
    
    import java.util.concurrent.locks.ReadWriteLock;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    public class ReadWriteDemo {
    	
    	private int number=0;
    	private ReadWriteLock lock=new ReentrantReadWriteLock();
    	
    	public void read() {
    		lock.readLock().lock();
    		try {
    			System.out.println(Thread.currentThread().getName()+"读取了:"+number);
    		} finally {
    			lock.readLock().unlock();
    		}
    	}
    	
    	public void write(int number) {
    		lock.writeLock().lock();
    		try {
    			System.out.println(Thread.currentThread().getName()+"写入了"+number);
    			this.number=number;
    			
    		} finally {
    			lock.writeLock().unlock();
    		}
    	}
    }
    

    测试类:

    package com.qf.day13;
    
    import java.util.Random;
    
    public class Test {
    	public static void main(String[] args) {
    		ReadWriteDemo rw=new ReadWriteDemo();
    		new Thread(new Runnable() {
    			@Override
    			public void run() {
    				try {
    					Thread.sleep(10);
    				} catch (InterruptedException e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				}
    				rw.write(new Random().nextInt(100));
    			}
    		}).start();
    		
    		Runnable r=new Runnable() {
    			@Override
    			public void run() {
    				rw.read();		
    			}
    		};
    		for(int i=0;i<100;i++) {
    			new Thread(r).start();
    		}
    	}
    }
    

    扩展知识2:线程池

    为什么需要线程池:

    ​ 例如有非常的多的任务需要多线程来完成,且每个线程执行时间不会太长,这样会频繁的创建和销毁线程。频繁创建和销毁线程会比较耗性能。如果有了线程池就不要创建更多的线程来完成任务,因为线程可以重用。

    ​ 线程池用维护者一个队列,队列中保存着处于等待(空闲)状态的线程。不用每次都创建新的线程。

    和线程池相关的接口和类存在java.util.concurrent并发包中。

    接口:

    1 Executor:线程池的核心接口,负责线程的创建使用和调度的根接口

    2 ExecutorService: Executor的子接口,线程池的主要接口, 提供基本功能。

    3 ScheduledExecutorService: ExecutorService的子接口,负责线程调度的子接口。

    实现类:

    1 ThreadPoolExecutor:ExecutorService的实现类,负责线程池的创建使用。

    2 ScheduledThreadPoolExecutor:继承 ThreadPoolExecutor,并实现 ScheduledExecutorService接口,既有线程池的功能,又具有线程调度功能。

    3 Executors:线程池的工具类,负责线程池的创建。

    ​ newFixedThreadPool();创建固定大小的线程池。

    ​ newCachedThreadPool();创建缓存线程池,线程池大小没有限制。根据需求自动调整线程数量。

    ​ newSingleThreadExecutor();创建单个线程的线程池,只有一个线程。

    ​ newScheduledThreadPool();创建固定大小的线程池,可以延迟或定时执行任务。

    案例一:使用线程池实现卖票

    package com.qf.day13;
    
    public class Ticket implements Runnable{
    	private int ticket=100;
    
    	@Override
    	public void run() {
    		while(true) {
    			if(ticket<=0) {
    				break;
    			}
    			System.out.println(Thread.currentThread().getName()+"卖第"+ticket+"张票");
    			ticket--;
    		}
    	}
    	
    }
    
    package com.qf.day13;
    
    import java.util.Random;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class Test {
    	public static void main(String[] args) {
    		Ticket ticket=new Ticket();
    		ExecutorService threadPool = Executors.newFixedThreadPool(4);
    		for(int i=0;i<4;i++) {
    			threadPool.submit(ticket);
    		}
    		threadPool.shutdown();
    		System.out.println("主线程执行完毕........");
    		
    	}
    }
    

    案例二:线程池计算1-100的和,要求采用Callable接口

    package com.qf.day13;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Random;
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Future;
    
    public class Test {
    	public static void main(String[] args) throws Exception {
    		ExecutorService threadPool = Executors.newFixedThreadPool(4);
    		List<Future<Integer>> list=new ArrayList<>();
    		for (int i = 0; i < 10; i++) {
    			Future<Integer> future = threadPool.submit(new Callable<Integer>() {
    
    				@Override
    				public Integer call() throws Exception {
    					int sum = 0;
    					for (int i = 0; i <= 100; i++) {
    						Thread.sleep(10);
    						sum += i;
    					}
    					System.out.println(Thread.currentThread().getName() + "计算完毕");
    					return sum;
    				}
    			});
    			list.add(future);
    		}
    
    		threadPool.shutdown();
    		System.out.println("主线程结束了。。。。");
    		for (Future<Integer> fu : list) {
    			int s = fu.get();
    			System.out.println(s);
    		}
    		
    
    	}
    }
    

    案例三:延迟执行任务

    package com.qf.day13;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Random;
    import java.util.concurrent.Callable;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Future;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.TimeUnit;
    
    public class Test2 {
    	public static void main(String[] args) throws Exception{
    		ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(5);
    		List<Future<Integer>> list=new ArrayList<>();
    		for(int i=0;i<10;i++) {
    			Future<Integer> future=threadPool.schedule(new Callable<Integer>() {
    	
    				@Override
    				public Integer call() throws Exception {
    					int ran=new Random().nextInt(100);
    					System.out.println(Thread.currentThread().getName()+"...."+ran);
    					return ran;
    				}
    			},3,TimeUnit.SECONDS);
    			list.add(future);
    		}
    		threadPool.shutdown();
    		System.out.println("主线程结束了...........");
    		for (Future<Integer> future2 : list) {
    			int n=future2.get();
    			System.out.println(n);
    		}
    		
    	}
    }
    

    扩展知识3:定时器Timer

    总结

    1 多线程访问临界资源
    	数据安全问题?
    2 解决安全问题
    	同步 + 锁
    3 同步代码块
    	synchronized(metux){
          
    	}
    	锁:引用类型 ,唯一的
    4 同步方法	
    	方法的返回值前面 synchronized 
    	非静态方法 锁:this
    	静态方法 锁:类名.class
    	
    5 可重入锁 ReentrantLock
    	Lock lock=new ReeentrantLock();
    	lock.lock()
    	try{
          
    	}finally{
          lock.unlock();
    	}
    6 死锁 
       1多个锁 ,多个线程
       2同步中嵌套同步
    7 单例模式优化
    	懒汉式写法
    8 线程通信
    	Object中三个方法 
    	wait(); 等待
    	notifiy(); 唤醒
    	notifyAll();唤醒所有的
    
    	jdk1.5 Lock通信
    	Conditon代替监视器方法
    	
    

    课前默写

    1.分别使用继承Thread类和实现Runnable接口的方式创建线程
    
    2.设计程序,演示join方法的使用
    

    作业

    1.春运到了,某个火车站四个售票员出售某个车次最后100张车票的情形。
    分析:
      	火车票:  临界资源
     	 四个售票员:四个线程实例  , 出售车票:卖一张,车票就少一张
    
    2.使用线程安全的懒汉式写法。
    
    3.完善昨天的作业,模拟多个人通过一个山洞的模拟。这个山洞每次只能通过一个人,每个人通过山洞的时间为5秒,随机生成10个人,同时准备过此山洞,显示一下每次通过山洞人的姓名
    

    面试题

    1.多线程临界资源问题是如何产生的,该怎么解决
    	是因为多个线程访问同一个资源,出现安全问题? 
    	使用同步代码块  同步方法  ReentrantLock
    2.简述生产者与消费者设计模式的实现原理
    	1 同步
    	2 线程间通信, wait(), notify()  notifyAll();
    3.sleep()和wait()的区别?
      1>sleep休眠,自动唤醒,wait()等待一般需要别的线程唤醒,也可以设置等待时间,需要获的锁之后才能运行。
      2>sleep休眠只释放了cpu,没有释放锁, wait()cpu和锁都释放。
    4.三个线程交替输出A B C ,输出20遍。  
    5.volitale关键字的作用?
    	多个线程共享同一个变量,保证一个线程堆变量的修改,别的线程能理解看见。只能保证可见性和避免重排序,不能保证互斥。
    6.i++操作是原子操作吗?
    	i++;
    	两个步骤? 执行三个操作  1 取i值    2计算  i+1  3 赋值i
    	int temp=i;
    	i=i+1;
    	
    	分析如下代码,指出问题原因?如何解决
    	package com.qf.day13;
    
    public class Test3 {
    	public static void main(String[] args) {
    		Atomic atomic=new Atomic();
    		for(int i=0;i<10;i++) {
    			new Thread(atomic).start();
    		}
    	}
    }
    
    class Atomic implements Runnable{
    	int num=0;
    	@Override
    	public void run() {
    		try {
    			Thread.sleep(200);
    		} catch (InterruptedException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    		System.out.println(num++);
    	}
    }
    	
    
  • 相关阅读:
    Linux CentOS7 下设置tomcat 开机自启动
    MYSQL批量导入数据报:[Err] 2006
    HTML页面仿WORD样式
    /usr/bin/ld: cannot find -lxxx 问题
    Linux 重命名
    Linux mail
    cenos 7 mysql
    linux 解压与压缩
    python 字符串替换
    cpu相关信息(进程、线程、核...)
  • 原文地址:https://www.cnblogs.com/Zzzxb/p/11397155.html
Copyright © 2020-2023  润新知