Java高级-线程池

1.死锁

        死锁是指多个线程或进程在访问共享资源时,由于彼此占用的资源被其它线程或进程占用而互相等待,导致所有线程或进程都无法继续执行的状态。

        实例操作:

创建类,并创建两个静态对象变量

public class MySuo {
    public static Object lockA = new Object();
    public static Object lockB = new Object();
}

再创建两个类实现Runnable接口,并实现run方法添加自动锁调用上方类中的静态变量

public class HeSuo implements Runnable{
    @Override
    public void run() {
        synchronized (MySuo.lockA){
            System.out.println("获取到了锁A");
            synchronized (MySuo.lockB){
                System.out.println("获取到了锁B");
                System.out.println("可以开门了");
            }
        }
    }
}
public class SheSuo implements Runnable{
    @Override
    public void run() {
        synchronized (MySuo.lockB){
            System.out.println("获取到了锁B");
            synchronized (MySuo.lockA){
                System.out.println("获取到了锁A");
                System.out.println("可以开门了");
            }
        }
    }
}

        两个线程类,第一个调用了A锁,第二个调用了B锁,此时,第一个线程执行完任务等待B锁的释放,但是第二个线程也执行完任务在等待A锁的释放,两个锁就在互相等待对方释放,就会出现死锁的情况

创建测试类测试

public class Test {
    public static void main(String[] args) throws Exception{
        new Thread(new HeSuo()).start();
        new Thread(new SheSuo()).start();
    }
}

 可以看到输出结果,并没有输出“可以开门了”,锁到这里就死了。需要避免这种情况,可以使用Thread.sleep在两个线程中,让第二个线程等一等第一个线程

2.线程的创建

        线程有三种创建方式,第一种,继承Thread类,第二种,实现Runnable接口,第三种实现Callable接口。三种创建方式都需要实现run方法。一下是三种创建方式的代码。

public class MyThread extends Thread{
    @Override
    public void run() {
        super.run();
    }
}
public class MyThread implements Runnable{
    @Override
    public void run() {
        
    }
}
public class MyThread implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        return null;
    }
}

        三种方式各有优点,第一种更方便的创建以及启动,但是继承只能继承一个类,第二章,使用实现的方式,可以继承其他类,也可以继续实现其他接口,第三种是在第二种的方式上添加了返回值。

3.线程通信

        线程通信是通过Object类中的wait()和notify()方法实现的,wait方法可以使当前执行该方法的线程进入等待状态,直到另一个线程调用该对象的notify()或notifyAll()方法才能唤醒该线程。

        wait和sleep的区别:

        父类:wait的父类是Object,sleep的父类是Thread

        状态:wait执行时,线程是等待或者阻塞状态,sleep是休眠状态

        锁不同:wait执行时,会释放锁资源,而sleep不会释放锁

        案例实操:A负责在没钱的时候往银行卡存入钱,B负责在银行卡有钱的时候花掉钱

说明:案例中有存入和取出的方法,可以使用两个线程来表示。分别创建存入和去除的线程类并实现Runnable接口中的run方法。还有银行卡类,银行卡类中有金额变量,标记位(标记银行卡内是否余额),还有存入和取出的方法。

        因为AB使用的是同一张银行卡,所以两个线程类需要调用同一个银行卡类。创建私有银行卡类变量,并添加构造函数,使两个线程类都调用传入进来的银行卡类。

public class SaveTask implements Runnable{
    private BankRunnable bankRunnable;
    public SaveTask(BankRunnable b){
        bankRunnable = b;
    }
    @Override
    public void run() {
      
    }
}
public class TakeTask implements Runnable{
    private BankRunnable bankRunnable;
    public TakeTask(BankRunnable b){
        bankRunnable = b;
    }
    @Override
    public void run() {
        
    }
}

        银行卡类中创建金额变量、标记位以及存入取出方法

public class BankRunnable {
    private static int money = 0;
    private static boolean flag = false;

    public synchronized void save(int num) throws Exception{
        
    }

    public synchronized void take(int num) throws Exception{
        
    }
}

        如果余额不为0的话标记为为true,反之false

        存入金额需要先判断是否还有余额,如果有则不存入

if(flag){
    this.wait();
}

        在save方法内添加判断,如果为true则表示还有余额,使用该线程调用wait方法,使此线程进入等待,并释放锁资源,使得其他线程进入锁

        如果没有余额则执行存入

money+=num;
System.out.println(Thread.currentThread().getName()+"存入"+num+",余额"+money);
flag = true;
notify();

        将余额增加,并输出一下当前余额,将标记位改为true表示卡内有余额,并调用notify方法唤醒其他的线程进入方法锁。

        取出方法则与存入方法相反

public synchronized void take(int num) throws Exception{
        if(!flag){
            this.wait();
        }
        money-=num;
        System.out.println(Thread.currentThread().getName()+"存入"+num+",余额"+money);
        flag = false;
        notify();
    }

        如果为false表示没有余额了让此线程等待并释放锁资源,反之则让余额减少。

        存入线程类中的run方法则是for循环执行银行卡中的存入方法

@Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                bankRunnable.save(1000);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

        取出线程类中的run方法则是for循环执行银行卡中的取出方法

@Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                bankRunnable.take(1000);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

        测试类,创建银行卡类,并将银行卡传入创建的线程类中

public class Test {
    public static void main(String[] args) {
        BankRunnable bankRunnable = new BankRunnable();
        new Thread(new SaveTask(bankRunnable)).start();
        new Thread(new TakeTask(bankRunnable)).start();
    }
}

        可以看到输出结果,有余额执行取出,没有余额执行存入

        不仅notify方法可以唤醒线程,notifyAll方法也可以唤醒线程,他们两个的区别是:notify方法是唤醒的随机一个线程,而notifyAll方法唤醒的是全部线程。

4.线程状态

        通过线程类中的getState方法来获取线程的状态,线程有6中状态

NEW:线程创建的状态

RUNNABLE:start()是就绪状态-时间片-运行状态,统称为RUNNABLE

WAITING:无期等待,调用wait方法会进入此状态

TIMED_WATING:有期等待,当调用sleep方法会进入此状态

TERMINATED:终止状态,代码任务执行完毕或程序遇到异常的状态

5.线程池

        线程池有多种创建方法

第一种:固定长度的线程池

第二种:单一线程池

第三种:可变线程池

第四种:延迟线程池

public class TestPool {
    public static void main(String[] args) {
        //创建一个固定长度的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(5);//传入的参数表示创建几个线程池
        //创建单一的线程池,实现只有一个线程
        ExecutorService executorService1 = Executors.newSingleThreadExecutor();
        //创建可变线程池,线程池的数量是可以变换的
        ExecutorService executorService2 = Executors.newCachedThreadPool();
        //创建延迟线程池,以及5个固定长度的线程池
        Executors.newScheduledThreadPool(5);
    }
}

        但上面都是通过Executors工具类创建的,但是阿里巴巴不建议使用,阿里建议使用原生的模式创建线程池。

ArrayBlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(5);//等待的队列最大数
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 10, TimeUnit.SECONDS, workQueue);

        ThreadPoolExecutor 的传入五个参数分别是:核心线程池个数,最多线程个数,线程池空闲时间(空闲了多久撤掉此线程池),空闲时间的单位,等待队列

        这样能更灵活的创建线程池

        Executors工具类有多个方法可供使用

根接口:

void execute(Runnable command):执行Runnable类型的任务

子接口:

void shutdown():关闭线程池。需要等任务执行完毕。

shutdownNow(); 立即关闭线程池。 不在接受新的任务。

isShutdown(): 判断是否执行了关闭。

isTerminated(): 判断线程池是否终止。表示线程池中的任务都执行完毕,并且线程池关闭了

submit(Callable<T> task);提交任务,可以提交Callable

submit(Runnable task): 提交任务,可以提交Runnable任务

shutdown的操作

public class TestExecutors {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 20; i++) {
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                }
            });
        }
        executorService.shutdown();
    }
}

        使用shutdown和shutdownNow的区别,看输出结果

 左边使用了shutdown方法,右边使用了shutdownNow的方法,shutdown是等待程序执行完毕停止线程,而shutdownNow则是立即停止线程。

6.Callable创建线程

        创建类并实现Callable接口传入泛型,需要什么类型返回值就传入什么类型。

public class MyCall implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i < 101; i++) {
            sum+=i;
        }
        System.out.println("线程的结果:"+sum);
        return sum;
    }
}

        随便返回一个数字,我这返回的是1-100相加。然后在测试类中测试。

public class MyCallTest {
    public static void main(String[] args) throws Exception{
        //如果使用普通的线程来执行线程类,则会比较的麻烦
        MyCall myCall = new MyCall();
        FutureTask<Integer> futureTask = new FutureTask<>(myCall);
        new Thread(futureTask).start();

        //如果使用线程池中的对象,则相对简单一些
        Future<Integer> submit = Executors.newCachedThreadPool().submit(new MyCall());
        System.out.println("返回的结果:"+ submit.get());
    }
}

        两种测试的方式,都可以得到结果,但相同第二种会比第一种简单一些,第一种使用的是线程来执行,第二种使用线程池中的对象来执行

        可以看到,使用线程执行得不到值,使用线程池对象可以使用.get方法得到返回的值 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值