多线程四种实现方式及售票案例实现

一、多线程实现的四种方式

1.继承Thread类

查看Thread源码可知,Thread本质是实现了Runnable接口的一个实例,代表一个线程的实例;启动线程的唯一方法就是通过Thread类的start()方法。start()方法是一个native方法,他启动一个线程并执行run()方法;通过继承extend Thread,重写run()方法,可启动新线程并执行自己定义的run()方法;

继承Thread实现多线程售票案例:

1.创建需要执行的任务:

package com.chorany.admin.thread;

/**
 * @Author: chorany
 * @Date: 2022/4/7 19:28
 * @Description:
 * @Version:
 */
public class ThreadSample extends Thread {

    /**
     * 定义售卖票数
     */
    private static int ticket = 200;

    @Override
    public void run() {
        //死循环,保证有票就卖
        while (true) {
            //便于观察线程执行过程,这里使线程休眠一下
            try {
                Thread.sleep(500L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //为了线程安全,防止超卖现象,增加锁机制
            synchronized (this) {
                if (ticket <= 0) {
                    break;
                }
                System.out.println(Thread.currentThread().getName() + "卖了第" + ticket + "张票");
                ticket--;
            }
        }
    }
}

2.开启多线程进行任务执行

package com.chorany.admin.thread;

/**
 * @Author: chorany
 * @Date: 2022/4/7 19:38
 * @Description:
 * @Version:
 */
public class ThreadSampleDemo {
    public static void main(String[] args) {
        ThreadSample threadSample = new ThreadSample();
        //创建线程消费窗口
        new Thread(threadSample, "窗口1").start();
        new Thread(threadSample, "窗口2").start();
        new Thread(threadSample, "窗口3").start();
    }
}
2.实现Runnable接口

鉴于java单继承多实现的特性,当Runnable任务类已继承一个其他类时,则无法再通过继承Thread来实现多线程,因此可考虑实现Runnable接口来实现多线程任务创建

实现Runnable接口实现售票案例

1,创建多线程任务

package com.chorany.admin.thread;

/**
 * @Author: chorany
 * @Date: 2022/4/7 20:13
 * @Description:
 * @Version:
 */
public class RunnableSample implements Runnable {
    /**
     * 定义售卖票数
     */
    private static int ticket = 200;

    @Override
    public void run() {
        //死循环,保证有票就卖
        while (true) {
            //便于观察线程执行过程,这里使线程休眠一下
            try {
                Thread.sleep(500L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //为了线程安全,防止超卖现象,增加锁机制
            synchronized (this) {
                if (ticket <= 0) {
                    break;
                }
                System.out.println(Thread.currentThread().getName() + "卖了第" + ticket + "张票");
                ticket--;
            }
        }
    }
}

2.开启多线程进行任务执行

package com.chorany.admin.thread;

/**
 * @Author: chorany
 * @Date: 2022/4/7 20:15
 * @Description:
 * @Version:
 */
public class RunnableSampleDemo {
    public static void main(String[] args) {
        RunnableSample runnableSample = new RunnableSample();
        //开启线程
        new Thread(runnableSample,"窗口1").start();
        new Thread(runnableSample,"窗口2").start();
        new Thread(runnableSample,"窗口3").start();
    }
}
3.实现Callable接口

考虑到子线程执行完成后需要将执行后的数据结果带回的情况,此时实现Runnable接口则不能实现了此功能了,当然java中提供了实现Callable接口,通过FutureTask包装器来实现子线程返回结果带回

实现Callable接口验证售票案例

1.创建任务

package com.chorany.admin.thread;

import java.util.concurrent.Callable;

/**
 * @Author: chorany
 * @Date: 2022/4/7 20:21
 * @Description:
 * @Version:
 */
public class CallableSample implements Callable<String> {

    /**
     * 定义售卖票数
     */
    private static int ticket = 20;

    @Override
    public String call() throws Exception {
        //死循环,保证有票就卖
        while (true) {
            //便于观察线程执行过程,这里使线程休眠一下
            try {
                Thread.sleep(500L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //为了线程安全,防止超卖现象,增加锁机制
            synchronized (this) {
                if (ticket <= 0) {
                    break;
                }
                System.out.println(Thread.currentThread().getName() + "卖了第" + ticket + "张票");
                ticket--;
            }
        }
        return "票卖完了。。。";
    }
}

2.创建多线程进行任务执行

package com.chorany.admin.thread;

import java.util.concurrent.FutureTask;

/**
 * @Author: chorany
 * @Date: 2022/4/7 20:24
 * @Description:
 * @Version:
 */
public class CallableSampleDemo {
    public static void main(String[] args) {
        CallableSample callableSample = new CallableSample();
        //创建FutureTask包装器
        FutureTask<String> callableSampleFutureTask1 = new FutureTask<>(callableSample);
        FutureTask<String> callableSampleFutureTask2 = new FutureTask<>(callableSample);
        FutureTask<String> callableSampleFutureTask3 = new FutureTask<>(callableSample);

        //开启线程
        new Thread(callableSampleFutureTask1, "窗口1").start();
        new Thread(callableSampleFutureTask2, "窗口2").start();
        new Thread(callableSampleFutureTask3, "窗口3").start();


        try {
            String future1 = callableSampleFutureTask1.get();
            String future2 = callableSampleFutureTask2.get();
            String future3 = callableSampleFutureTask3.get();
            System.out.println("future1:"+future1+",future2"+future2+",future3"+future3);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
4.通过线程池获取线程

为了便于管理线程资源,编程中不推荐使用继承Thread、实现Runnable和实现Callable接口来实现多线程并发任务;因为线程的调度是基于服务器的cpu调度创建的,创建线程非常消耗cpu资源,并且创建出来的线程不便于管理,无限的创建线程会造成栈溢出风险;使用线程池统一管理线程资源,统一管理线程的创建和销毁,实际上线程池及时java中典型的拿空间换时间的表现;

线程池实现售票案例的实现

1.创建售票任务–此处创建Runnable类型任务

package com.alipay.sofabootdemo.service.thread.more;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @author chaoran.chen
 * @date 2022/4/7 10:42
 */

public class RunnableSample implements Runnable {
    private static int ticket = 2000;
    /**
     * 手动创建锁
     */
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        //加入死循环,保证有票就卖
        while (true) {
            try {
                //便于观察线程执行情况,此处休眠线程
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //防止票的超卖
            lock.lock();
            try {
                if (ticket <= 0) {
                    break;
                }
                System.out.println(Thread.currentThread().getName() + "卖了第" + ticket + "张票");
                ticket--;
            } finally {
                //保证不管执行成功还是失败,锁必须释放
                lock.unlock();
            }
        }
    }
}

2.使用线程池提交任务–此处同时使用submit()和execute()提交任务

package com.alipay.sofabootdemo.service.thread.pool;

import com.alipay.sofabootdemo.service.thread.more.RunnableSample;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author chaoran.chen
 * @date 2022/4/7 10:41
 */
public class ThreadPool {

    private static final int CORE_POOL_SIZE = 3;
    private static final int MAX_POOL_SIZE = 8;
    private static final int KEEP_ALIVE_TIME = 60;
    private static final int CAPACITY = 500;

    private final static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_TIME, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(CAPACITY), new CustomizableThreadFactory("售票pool-"));


    public static void main(String[] args) {
        RunnableSample runnableSample = new RunnableSample();
        //使用submit提交任务
        threadPoolExecutor.submit(runnableSample);
        //使用execute提交任务
        threadPoolExecutor.execute(runnableSample);
        threadPoolExecutor.execute(runnableSample);
    }
}

二、多线程学习过程中遇到的相关问题

1.多线程测试时,使用Junit测试框架的问题(建议直接使用main函数进行测试)

1.使用Junit测试多线程时,执行到Thread.sleep()时,程序会自动返回。这是junit框架的原因,建议采用main主函数测试;如果一定要使用junit测试,让线程休眠时,可使用下方方式:

TimeUnit.SECONDS.sleep(300);

最后:线程池的各种创建方式及其相关内容可参考可查看线程池章节解答

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值