一、多线程实现的四种方式
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);
最后:线程池的各种创建方式及其相关内容可参考可查看线程池章节解答