1.0 有哪些方法创建线程
new Thread(); 的方式
// 继承 Thread 、实现 Runable接口、实现 Callable<V> 接口 都不是创建线程方式 而是 运行 线程的方式
// 都是 重写 run() 方法
1.1 Java 提供了三种运行线程的方式:
- 通过实现 Runnable 接口;
- 通过继承 Thread 类本身;
- 通过 Callable 和 Future 创建线程。
1.1.2 继承Thread类:
步骤:①、定义类继承Thread;
②、复写Thread类中的run方法;
目的:将自定义代码存储在run方法,让线程运行
③、调用线程的start方法:
该方法有两步:启动线程,调用run方法。
1.1.3 实现Runnable接口:
接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为run 的无参方法。
步骤: ①、定义类实现Runnable接口
②、覆盖Runnable接口中的run方法
将线程要运行的代码放在该run方法中。
③、通过Thread类建立线程对象。
④、将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
自定义的run方法所属的对象是Runnable接口的子类对象。所以要让线程执行指定对象的run方法就要先明确run方法所属对象
⑤、调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。
实现Runnable 接口 Demo
1. 创建线程
package com.thread;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RunnableThread implements Runnable {
private static final Logger logger = LoggerFactory.getLogger(RunnableThread.class);
@Override
public void run() {
logger.info("执行线程!");
}
}
2.
package com.thread;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
public class ThreadTest02 {
public static void main(String[] args){
ThreadPoolTaskExecutor poolTaskExecutor = new ThreadPoolTaskExecutor();
//线程池维护线程的最少数量
poolTaskExecutor.setCorePoolSize(5);
//线程池维护线程的最大数量
poolTaskExecutor.setMaxPoolSize(15);
//线程池所使用的缓冲队列
poolTaskExecutor.setQueueCapacity(200);
//线程池维护线程所允许的空闲时间
poolTaskExecutor.setKeepAliveSeconds(30000);
poolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
poolTaskExecutor.initialize();
RunnableThread testThread = new RunnableThread();
poolTaskExecutor.execute(testThread);
}
}
运行如下:
1.1.4 通过Callable和Future运行线程:
各种线程池其实都是实现了 ExecutorService 的,Callable 任务需要提交到线程池中才能运行:
步骤:1、创建Callable接口的实现类(自定义一个类实现java.util.concurrent包下的Callable接口),
2、重写call()方法,将要在线程中执行的代码编写在call方法中,且具有返回值。
3、创建Callable实现类的实例(创建ExecutorService线程池),使用FutrueTask类进行包装Callable对象,FutureTask对象封装了Callable对象的call()方法的返回值
4、使用FutureTask对象作为Thread对象启动新线程。
5、调用FutureTask对象的get()方法获取子线程执行结束后的返回值。
6、关闭线程池,不再接收新的线程,未执行完的线程不会被关闭
Callable<V> 接口 Demo
1. 实现 Callable<V> 接口,本例实现 Callable<Student> 接口。
package hy_demo.thread;
import java.util.concurrent.Callable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CallableStudent implements Callable<Student> {
private Logger logger = LoggerFactory.getLogger(CallableStudent.class);
private Student student;
public CallableStudent(Student student) {
this.student = student;
}
@Override
public Student call() throws Exception {
logger.info(">>>>>>>>进入call() 方法-------------------------");
if(Integer.parseInt(student.getAge()) > 10){
return student;
}else{
return null;
}
}
}
package com.thread;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.FutureTask;
public class ThreadTest {
private static Logger logger = LoggerFactory.getLogger(ThreadTest.class);
public static void main(String[] args) {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
//如果池中的实际线程数小于corePoolSize,无论是否其中有空闲的线程,都会给新的任务产生新的线程
taskExecutor.setCorePoolSize(5);
//连接池中保留的最大连接数。
taskExecutor.setMaxPoolSize(15);
//queueCapacity 线程池所使用的缓冲队列
taskExecutor.setQueueCapacity(6000);
//强烈建议一定要给线程起一个有意义的名称前缀,便于分析日志
taskExecutor.setThreadNamePrefix("Student-Thread-");
taskExecutor.initialize();
Random randmo = new Random();
List<Student> list = new ArrayList<Student>();
for(int i = 0;i < 100000;i++){
Student stu = new Student();
stu.setName("stu" + i );
stu.setAge(String.valueOf(randmo.nextInt(20)));
list.add(stu);
}
long time1 = System.currentTimeMillis();
for (Student student : list) {
CallableStudent callableStudent =new CallableStudent(student);
FutureTask<Student> future = (FutureTask<Student>) taskExecutor.submit(callableStudent);
try {
if(null != future.get()){
//输出 CallableStudent ( Callable<Student> ) 线程实现类里,call() 方法返回的数据
//本例中返回 Student 对象,若是 Callable<Boolean>,则返回 boolean☞
System.out.println("future.get():" + future.get().getName() + " | " + future.get().getAge());
}
} catch (Exception e) {
e.printStackTrace();
}
}
long time2 = System.currentTimeMillis();
long time = time2 - time1;
logger.info("时间:" + time);
}
}
实体类
package hy_demo.thread;
public class Student {
private String name;
private String age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
运行结果如下:符合年龄大于10的打印出来
1.5 CompletableFuture
CompletableFuture是Java 8中对Future的增强版。
CompletableFuture跟性能上关系不大,更多的是为了支持函数式编程,在功能上的增强。当然开放了完成时间的设置是一大亮点。
Future接口的局限性:
1.将多个异步计算的结果合并成一个
2.等待Future集合中的所有任务都完成
3.通过编程方式完成一个Future任务的执行(即以手工设定异步操作结果的方式)。
4.Future完成事件(即,任务完成以后触发执行动作)
不管是Runnable任务还是Callable任务,线程池执行的任务可以划分为4个生命周期阶段:
- 创建:创建任务对象的时期。
- 提交:调用线程池的excute或者submit方法后,将任务塞到任务队列的时期。
- 执行中:某个线程从任务队列中将任务取出开始执行的时期。
- 完成:任务执行结束。
四、继承Thread类和实现Runnable接口、实现Callable接口的区别。
继承Thread:线程代码存放在Thread子类run方法中。
优势:编写简单,可直接用this.getname()获取当前线程,不必使用Thread.currentThread()方法。
劣势:已经继承了Thread类,无法再继承其他类。
实现Runnable:线程代码存放在接口的子类的run方法中。
优势:避免了单继承的局限性、多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。
劣势:比较复杂、访问线程必须使用Thread.currentThread()方法、无返回值。
实现Callable:
优势:有返回值、避免了单继承的局限性、多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。
劣势:比较复杂、访问线程必须使用Thread.currentThread()方法
建议使用实现接口的方式创建多线程。
总结:
同步的前提:
1、必须要有两个或者两个以上的线程。
2、必须是多个线程使用同一个锁。
3、必须保证同步中只能有一个线程在运行。
4、只能同步方法,不能同步变量和类。
5、不必同步类中所有方法,类可以拥有同步和非同步的方法。
6、如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法。
7、线程睡眠时,它所持的任何锁都不会释放。
好处:解决了多线程的安全问题。
弊端:多个线程需要判断,消耗资源,降低效率。
如何找问题?
1、明确哪些代码是多线程运行代码。
2、明确共享数据。
3、明确多线程运行代码中哪些语句是操作共享数据的。
死锁形成的必要条件总结(都满足之后就会产生):
①、互斥条件:资源不能被共享,只能被同一个进程使用;
②、请求与保持条件:已经得到资源的进程可以申请新的资源;
③、非剥夺条件:已经分配的资源不能从相应的进程中强制剥夺;
④、循环等待条件:系统中若干进程形成环路,该环路中每个进程都在等待相邻进程占用的资源。