线程池+线程安全
第一 线程池
线程池就是一个存放多线程的容器,作用在于解决了线程的声明周期问题,通过创建好的多条线程让线程重复使用,从而避免了资源浪费紧缺。
线程池的两个方式:
Runnable接口
l Executors:线程池创建工厂类
n public static ExecutorService newFixedThreadPool(int nThreads):返回线程池对象
l ExecutorService:线程池类
n Future<?> submit(Runnable task):获取线程池中的某一个线程对象,并执行
l Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用
l 使用线程池中线程对象的步骤:
n 创建线程池对象
n 创建Runnable接口子类对象
n 提交Runnable接口子类对象
n 关闭线程池
public class MyRunable implements Runnable { public void run() { for(int i=0;i<50;i++){ //匿名对象直接静态获取调取父类方法.getName System.out.println(Thread.currentThread().getName()+":"+i); } } }
创建线程工厂:
public static void main(String[] args) { //从线程池工厂中获取线程池对象 线程工厂中有两条线程 ExecutorService es=Executors.newFixedThreadPool(2); //创建线程任务 MyRunable mr=new MyRunable(); //让线程池选择一条线程给我执行线程任务 //找线程池给我一条 es.submit(mr); es.submit(mr); //如果超过线程工厂的数量,就会等一条线程完成,再去用哪个完成的线程 es.submit(mr); //释放资源 es.shutdown(); }
第二种:Callable接口 可返回
l Callable接口:与Runnable接口功能相似,用来指定线程的任务。其中的call()方法,用来返回线程任务执行完毕后的结果,call方法可抛出异常。
l ExecutorService:线程池类
n <T> Future<T> submit(Callable<T> task):获取线程池中的某一个线程对象,并执行线程中的call()方法
l Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用
l 使用线程池中线程对象的步骤:
n 创建线程池对象
n 创建Callable接口子类对象
n 提交Callable接口子类对象
n 关闭线程池
//分别用两个线程计算1-50的和,以及1-100的和
自定义类: public class YourRunnable implements Callable <Integer> { private int num; public YourRunnable(){}; public YourRunnable(int num){ this.num=num; } public Integer call() throws Exception { //线程任务 int sum=0; for(int i=1;i<=num;i++){ sum=sum+i; } return sum; } //测试类 public class Demo04 { //分别用两个线程计算1-50的和,以及1-100的和 public static void main(String[] args) throws InterruptedException, ExecutionException { //从线程池方法中获取线程池对象 ExecutorService es=Executors.newFixedThreadPool(2); //创建线程任务 YourRunnable yr1=new YourRunnable(50); YourRunnable yr2=new YourRunnable(100); //从线程池丛中取线程执行线程并接受返回值 Future<Integer> fu1=es.submit(yr1); Future<Integer> fu2=es.submit(yr2); //获得返回值 Integer s1=fu1.get(); Integer s2=fu2.get(); System.out.println(s1); System.out.println(s2); //释放资源 es.shutdown(); }
因为Runnable没有返回值,所以是不能够定义泛型,并且不需要future来接收返回值→.get方法来获得返回值,他只需要submit传入参数进行计算即可。
而且这个计算数的关键在于成员变量负责存值,通过有参构造直接赋值,run方法执行线程任务,Runnable方法直接run中打印,通过测试类调取方法显示结果。
callable则通过测试类中es.submit方法接收返回值,fu1.get获得返回值,打印返回值,走了一遍流程。
第二、线程安全问题。
1、线程状态:
案例:360
2、线程安全问题实例:售票
3、代码
如果是正常开启3个线程同时执行,因为抢占式调度,必然出现一张票被卖了几次的情况,甚至出现0 -1张票。而产生这个的原因就是因为线程是可以连续走动。
成员变量tracket判断>0第一个线程判断成功休眠,第二个线程又进入循环一遍休眠,第三个线程循环休眠。最终一线程拿到了最后1张票,二线程拿到了0张票,三线程拿到了-1张票,最终在if判断不符合,退出循环。
通过上面的描述我们发现,出现问题的就是三个线程可以循环无阻的进入线程任务中,导致一票多卖。我们的解决方法就是把可以多通道的路变为只可以单通道的路,让一个进入走完之前不允许另外进入,解决线程安全总共有三个方法。首先:
代码:
总共有三个方法定义线程安全:
测试类===开启线程对象==== package com.oracle.demo01; public class Demo01 { public static void main(String[] args) { //卖票 //创建线程任务 Tickets t=new Tickets(); //创建线程对象,共计三条,将创建的线程任务传入线程对象 Thread t0=new Thread(t); Thread t1=new Thread(t); Thread t2=new Thread(t); //开始线程 t0.start(); t1.start(); t2.start(); } } ===============第一个方法Synchronized,开启同步============ package com.oracle.demo01; //实现Runnable接口 public class Tickets implements Runnable { //多个线程共享,必须要用成员变量 private int ticket=100; private Object obj=new Object(); //重写run public void run() { while(true){ synchronized (obj) {//把会出现问题的代码放入同步代码块中。 if(ticket>0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } //获取名称并让倒售出票,--先赋值再运算,当ticket变成100,成员变量成99 System.out.println(Thread.currentThread().getName()+"售出第"+ticket--+"张票"); } } } } }
==============第二个方法,同步方法================ 同步方法:在方法声明上加上synchronized,锁对象是this,也就是目前存放出现线程安全的代码方法。 =================================================== //实现Runnable接口 public class Tickets2 implements Runnable { //多个线程共享,必须要用成员变量 private int ticket=100; private Object obj=new Object(); //重写run public void run() { while(true){ sale(); } } public synchronized void sale(){ if(ticket>0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } //获取名称并让倒售出票,--先赋值再运算,当ticket变成100,成员变量成99 System.out.println(Thread.currentThread().getName()+"售出第"+ticket--+"张票"); } } }
======================第三种方式,lock接口============== 我的理解他就是一个锁了,在继承了Runnable接口的类里面首先new出lock接口,然后让出现代码问题的最开始调用lock接口,上锁,最后问题代码块执行完毕,unlock开锁。 //实现Runnable接口 public class Tickets04 implements Runnable { //多个线程共享,必须要用成员变量 private int ticket=100; //定义锁对象 Lock lock =new ReentrantLock(); //重写run //第三种解决安全问题 public void run() { while(true){ //获取锁 lock.lock(); if(ticket>0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } //获取名称并让倒售出票,--先赋值再运算,当ticket变成100,成员变量成99 System.out.println(Thread.currentThread().getName()+"售出第"+ticket--+"张票"); } //释放锁 lock.unlock(); } } }