线程池+线程安全

第一 线程池

线程池就是一个存放多线程的容器,作用在于解决了线程的声明周期问题,通过创建好的多条线程让线程重复使用,从而避免了资源浪费紧缺。

 线程池的两个方式:

Runnable接口

l Executors:线程池创建工厂类

n public static ExecutorService newFixedThreadPool(int nThreads):返回线程池对象

l ExecutorService:线程池类

n Future<?> submit(Runnable task):获取线程池中的某一个线程对象,并执行

l Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用

使用线程池中线程对象的步骤:

创建线程池对象

创建Runnable接口子类对象

提交Runnable接口子类对象

关闭线程池

 

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接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用

使用线程池中线程对象的步骤:

创建线程池对象

创建Callable接口子类对象

提交Callable接口子类对象

关闭线程池

//分别用两个线程计算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();
            }
    }
}

猜你喜欢

转载自www.cnblogs.com/a199706/p/11451121.html