6、多线程
一、线程创建方法
1、继承Thread类
-
自定义线程类,继承(extends)Thread
-
重写run()方法
-
创建线程对象,调用start()方法启动线程
MyThread thread1 = new MyThread(); thread1.start();
2、实现Runnable类(常用,因为接口可以多继承)
-
自定义线程类RunnableTest,实现(implements)Runnable类
-
重写run()方法
-
创建线程对象p,new Thread(p,“线程名称”).start()方法启动线程(线程名称可省略,主要用于区分多个相同对象)
RunnableTest p=new RunnableTest(); Thread thread = new Thread(p); thread.start;
3、实现Callable类、通过FutureTask获取返回值
public class MyCallable2 implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 0; i < 10; i++) {
System.out.println("子线程 ");
}
return "我爱中国";
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
//FutureTask的泛型是MyCallable2类中call()的返回值类型
FutureTask<String> myCallable2FutureTask = new FutureTask<>(new MyCallable2());
Thread thread = new Thread(myCallable2FutureTask);
thread.start();
System.out.println("获取线程返回值,会阻塞线程,导致程序不往后执行:" + myCallable2FutureTask.get());
//当前,下面的for会等待子线程中代码执行完毕后才会开始执行,原因:被前一行myCallable2FutureTask.get()阻塞了
for (int i = 0; i < 10; i++) {
System.out.println(" main ");
}
}
二、常用方法
1、线程休眠:thread.sleep();
2、线程礼让(停止当前线程,重新让CPU调度):thread.yield();
3、线程插队(强制优先执行p这个线程):thread.join();
4、线程状态获取:thread.getState();
5、获取线程名称:thread.currentThread.getName();
6、获取线程优先级(设置用set,1-10之间,先设置再启动,优先级是占比相对大,不是绝对优先):thread.getPriority();
7、守护线程(默认位false,即用户线程,设为true时,是守护线程,守护其他线程结束便结束) :thread.setDaemon(true);
8、同步监视器,隐式锁,synchronized(Ojb){增删改方法}。
三、线程的生命周期
- 新建
- 可运行
- 被终止
- 阻塞
- 无限等待
- 计时等待
四、锁
1、隐式锁:synchronized
public class Demo01 {
public static void main(String[] args) {
/**
* 1、同步代码块
* 格式:
* synchronized(同步锁对象){
* 操作共享资源的代码
* }
*
* 2、同步方法
* 格式:
* 修饰符 synchronized 返回值类型 方法名称(形参列表) {
* 操作共享资源的代码
* }
*/
Runnable runnable = new Runnable() {
int ticket = 100;
@Override
public void run() {
while (true){
/*
synchronized
1、确保多个线程使用的是同一个锁对象即可,锁对象可以是任意对象。
2、如果多个线程共用同一个runnable对象,runnable的run方法内可以使用this作为锁对象。
3、对于静态方法建议使用字节码(类名.class)对象作为锁对象,其他方法也能使用字节码(类名.class)对象作为锁对象
*/
//1、同步代码块
synchronized (this){
if(ticket>0){
System.out.println(Thread.currentThread().getName()+" 卖出了第 "+ticket+" 张票");
ticket--;
}
else {
break;
}
}
}
}
//2、同步方法
private synchronized void tellticket(){
if(ticket>0){
System.out.println(Thread.currentThread().getName()+" 卖出了第 "+ticket+" 张票");
ticket--;
}
}
};
Thread thread1 = new Thread(runnable, "窗口1");
Thread thread2 = new Thread(runnable, "窗口2");
Thread thread3 = new Thread(runnable, "窗口3");
thread1.start();
thread2.start();
thread3.start();
}
}
2、显式锁,ReentrantLock,手动开启关闭锁。
public class Demo03 {
public static void main(String[] args) {
/**
* Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来构建Lock锁对象
* void lock():获得锁
* void unlock():释放锁
*/
ReentrantLock lock = new ReentrantLock();
Runnable runnable = new Runnable() {
int ticket = 100;
@Override
public void run() {
while (true) {
try {
//获得锁
lock.lock();
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + " 卖出了第 " + ticket + " 张票");
ticket--;
} else {
break;
}
} finally {
//释放锁
lock.unlock();
}
}
}
};
Thread thread1 = new Thread(runnable, "窗口1");
Thread thread2 = new Thread(runnable, "窗口2");
Thread thread3 = new Thread(runnable, "窗口3");
thread1.start();
thread2.start();
thread3.start();
}
}
五、线程池
1、线程创建
-
使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象
public static void main(String[] args) throws ExecutionException, InterruptedException { /** * ExecutorService的常用api: * execute(Runnable runnable):执行任务/命令,没有返回值,一般用来执行 Runnable 任务 * Future<T> submit(Callable<T> task):执行任务,返回未来任务对象获取线程结果,一般拿来执行Callable任务 * shutdown():等任务执行完毕后关闭线程池 * shutdownNow():立刻关闭,停止正在执行的任务,并返回队列中未执行的任务 */ //线程池创建方式1 ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor( 3,//核心线程数 5,//最大线程数 5,//临时线程保存时间 TimeUnit.HOURS,//时间单位 new LinkedBlockingQueue<>(10),//任务队列 Executors.defaultThreadFactory(),//创建工厂 new ThreadPoolExecutor.AbortPolicy()//拒绝策略 ); //poolExecutor.submit创建线程任务 //当创建线程任务的数量>任务队列的数量+核心线程数时,才开始新增调用临时线程 for (int i = 0; i < 14; i++) { Future<String> future = poolExecutor.submit(() -> { System.out.println(Thread.currentThread().getName() + "线程 执行了"); return Thread.currentThread().getName() + " 返回了"; }); System.out.println("future.get() = " + future.get()); } /* //poolExecutor.execute创建线程任务 //当创建线程任务的数量>任务队列的数量+核心线程数时,才开始新增调用临时线程 for (int i = 0; i < 14; i++) { poolExecutor.execute(() -> System.out.println(Thread.currentThread().getName()+"线程执行了")); }*/ /* //线程池立刻关闭,停止正在执行的任务,返回未执行队列中 List<Runnable> runnables = poolExecutor.shutdownNow(); runnables.forEach(runnable -> System.out.println("runnable = " + runnable)); */ //线程池关闭 poolExecutor.shutdown(); }
-
使用Executors(线程池的工具类),调用方法返回不同特点的线程池对象
public static void main(String[] args) { /** * newCachedThreadPool(): * 线程数量随着任务增加而增加,如果线程任务执行完毕且空闲了一段时间则会被回收掉 * newFixedThreadPool(int nThreads): * 创建固定线程数量的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程替代它 * newSingleThreadExecutor(): * 创建只有一个线程的线程池对象,如果该线程出现异常而结束,那么线程池会补充一个新线程 * newScheduledThreadPool(int corePoolSize): * 创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务 */ ExecutorService executorService = Executors.newCachedThreadPool(); }
2、线程池工作原理
- 当有新的任务交给线程池时,先由核心线程来处理。
- 当核心线程都在处理任务时,多的任务将存放到任务队列(LinkedBlockQueue)中。
- 当任务队列满了,核心线程都在处理任务,且处理任务的线程总数还没有达到最大线程数,线程工厂开始创建临时线程处理任务。
- 当核心线程和临时线程都在处理任务,且线程总数达到了最大线程数,,任务队列也满了,还有新的任务进来的就会开始拒绝策略。
六、其他
1、管程法,生产者/消费者模式,消费者不直接拿生产者的数据,而是从缓冲区拿数据
public static void main(String[] args) {
//管程法
Buffer buffer=new Buffer();
new Thread(new Producer(buffer)).start();
new Thread(new Consumer(buffer)).start();
}
//生产者
static class Producer implements Runnable{
Buffer buffer;
public Producer(Buffer buffer) {
this.buffer = buffer;
}
@Override
public void run() {
for (int i = 1; i < 100; i++) {
System.out.println("生产了第"+i+"个产品");
buffer.add(new Product());
}
}
}
//消费者
static class Consumer implements Runnable{
Buffer buffer;
public Consumer(Buffer buffer) {
this.buffer = buffer;
}
@Override
public void run() {
for (int i = 1; i < 100; i++) {
System.out.println(" 消费者消费了 第"+i+"个产品");
buffer.reduce();
}
}
}
//产品
static class Product{
int id;
}
//缓冲区
static class Buffer {
Product[]products=new Product[10];
int count =0;
//生产这进行新增
public synchronized void add(Product product){
if(count==products.length){
//缓冲区满了,等待减少
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
products[count]=product;
count++;
System.out.println(" 缓存区有"+count+"个产品");
this.notifyAll();
}
//消费者进行减少
public synchronized void reduce(){
if(count==0){
//缓冲区空了,等待新增
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count--;
System.out.println(" 缓存区有"+count+"个产品");
this.notifyAll();
}
}
2、信号灯法,生产者/消费者模式,根据一个变量去确定是等待,还是执行
public class ThreadTest3 {
public static void main(String[] args) {
TV tv=new TV();
new Thread(new Seeer(tv)).start();
}
}
//表演者
class Player implements Runnable{
private TV tv;
private String project;
public Player(TV tv,String project) {
this.tv = tv;
this.project = project;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
tv.play(project);
if(i%2==0){
tv.play("吉祥如意的一家");
}
else{
tv.play(project);
}
}
}
}
//观看者
class Seeer implements Runnable{
private TV tv;
public Seeer(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
tv.see();
}
}
}
class TV {
private String project;
private boolean flag=true;
synchronized void play(String project){
if(flag==true){
System.out.println("表演者表演了<"+project+">");
//Thread.currentThread().notifyAll();//这里只能用this,用这个会报错
this.flag=false;
this.project=project;
this.notifyAll();
}
else {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
synchronized void see(){
if(flag==false){
System.out.println(" 观看者看了 "+project);
this.flag=true;
this.notifyAll();
}
else {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}