线程的4种实现方法
1、继承Thread类
Thread类本质上就是一个实现了Runnable接口的实例,代表一个线程对象
public class Thread implements Runnable{
public void run() {
if (target != null) {
target.run();
}
}
}
//定义线程最简单的方法就是继承Thread类,需要覆盖定义run方法即可
启动线程的唯一方法就是通过Thread类对象的start()实例方法,不能直接调用run()方法。start方法本身是一个native方法,用于启动一个新线程,并指定run()方法。最大的限制实际上就是java的单根继承体系,这种方式基本不用
public class LeftThread extends Thread{
public void run(){
for(int i=0;i<50;i++){
System.out.println("画条龙...."+i);
//停止运行,阻塞300ms
try{
this.sleep(300);
} catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
//可以在主线程种启动这个线程
Thread t1=new LeftThread();
t1.start();//可以启动线程,可以通过Thread.currentThread().getName()获取线程名称
匿名内部类的定义方法
new Thread(){
public void run(){....}
}.start();
t1.run();//没有语法错误,但是会发现输出的线程名称为main,不是其它线程,而是当前的main主
线程。不是多线程
2、实现Runnable接口
接口的定义
@FunctionalInterface //注解用于声明当前接口是一个函数式接口,可以使用lambda表达式替代
匿名内部类的写法
public interface Runnable {
public abstract void run();
}
独立类实现接口
public class RightThread implements Runnable{
public void run(){
for(int i=0;i<50;i++){
System.out.println("画彩虹...."+i);
}
}
}
//main方法调用
Thread t1=new Thread(new RightThread());
t1.start();
匿名内部类
Thread t1=new Thread(new Runnable(){
public void run(){
for(int i=0;i<50;i++){
System.out.println("画彩虹...."+i);
}
}
});
t1.start();
lambda表达式,要求接口必须是函数式接口
Thread t1=new Thread(()->{
for(int i=0;i<50;i++){
System.out.println("画彩虹...."+i);
}
});
t1.start();
使用Callable和Future接口创建线程
创建Callable接口的实现类,并实现call()方法使用FutureTask类来包装Callable接口实现类的对象,并且以FutureTask对象作为Thread对象的target来创建线程
接口定义
Callable接口用于定义线程的执行逻辑。java.util.concurrent.Callable【juc】
@FunctionalInterface //属于函数式接口,所以可以直接使用lambda表达式进行定义
public interface Callable<V> { //<>写法叫做泛型,用于定义返回的数据类型
V call() throws Exception; //call方法就是线程的具体执行逻辑,有返回值
}
Future接口用于接收Callable接口中call方法的执行结果,并提供测试Callable执行情况
java.util.concurrent.Future
public interface Future<V> { //用于获取Callable接口中call方法的执行结果
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit) throws InterruptedException,
ExecutionException, TimeoutException;
}
最终启动new Thread(Runnable).start的方式进行启动。有了FutureTask类则可以将Callable接口实现封装到FutureTask对象中,由于FutureTask实现了Runnable接口,所以可以作为Thread类的构造器参数传入到Thread对象中,供线程执行对应的call方法
使用线程池创建线程
xxx池---享元模式
享元模式
享元模式Flyweight Pattern主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。
优点:大大减少对象的创建,降低系统的内存,使效率提高。
缺点:提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。
使用场景:1、系统有大量相似对象。 2、需要缓冲池的场景。
原型模式
原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。
线程池
创建线程需要花费昂贵的资源和时间,如果每次都是任务到来时才创建线程对象,那么响应时间会变长,而且一个进程能够创建的线程数有限。为了避免这些问题,在程序启动时就创建若干个线程对象用于响应处理,这个就称为线程池,里面的每个线程就叫工作线程
从JDK1.5开始Java提供了Executor框架可以创建不同的线程池。例如单线程池,每次只能处理一个任务;数目固定的线程池或者缓存线程池等
问题
new Thread的弊端:
1. 新建thread性能差
2. 线程缺乏统一管理,可能会出现无限制创建新线程的问题,相互之间竞争,可能占用过多的系统资源导致宕机或者OOM
3.缺乏更多的功能,例如定期执行、定时指定、线程中断处理等
使用线程池的优势
1.重用存在的线程,减少对象的创建和消亡的开销,性能较好
2.可以有效地控制最大并发线程数,提供系统资源的利用率,同时避免过多的资源竞争
3.提供了定时执行、定期执行、单线程、并发数控制等功能
编程使用
java提供了ExecutorService、Callable和Future实现等工具以简化线程池的使用,线程池的具体实现实际上是依赖于ThreadPoolExecutor
Callable接口的实现类
public class MyCallable implements Callable<Integer> {
int begin = 0;
int end = 0;
public MyCallable(int begin, int end) {
this.begin = begin;
this.end = end;
}
public Integer call() throws Exception {
int res = 0;
for (int i = begin; i <= end; i++) {
res += i;
}
System.out.println(Thread.currentThread() + ":" + res);
return res;
}
}
线程池的使用
在ali的文档中不建议直接使用java封装好的线程池,建议直接使用ThreadPoolExecutor自己定制线程池
1 2 3 4 5 6 | public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue) public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler) |
corePoolSize设置线程池的核心大小
maximumPoolSize线程池中允许的最大线程数
workQueue阻塞工作队列
选用
1.一般创建线程对象时不建议使用extends Thread方法:单根继承体系
2.如果没有返回结果建议使用Runnable接口
3.如果有返回值建议使用Callable接口
4.如果使用线程比较频繁建议使用线程池,ThreadPoolExecutor
守护线程
正常情况下,非守护线程的执行时间和主线程无关,即使主线程已经结束,也不会影响子线程【为了说
明方便,在Java中所有的线程都是平级的】的运行
public class Test1 {
public static void main(String[] args) {
System.out.println("开始程序......");
new Thread(()->{
for(int i=0;i<1000;i++)
System.out.println(Thread.currentThread()+":"+i);
}).start();
System.out.println("结束程序......");
}
}
守护线程就是为其它线程提供服务的线程。守护线程的特点是当程序中的主线程结束时,守护线程会自动终止。
如何设置一个线程为守护线程
1.在一个线程调用start启动之前,调用方法thread.setDaemon(true)就可以将 thread线程设置为守
护线程
2.守护线程一般应该是一个独立的线程,run方法一般是无限寻循环
3.守护线程和其它线程的区别是:如果守护线程是唯一运行着的线程,程序会自动退出
线程组
Thread类中toString方法的定义
public String toString() {
ThreadGroup group = getThreadGroup(); //获取当前线程对象所属的线程组
if (group != null) {
return "Thread[" + getName() + "," + getPriority() + "," +
group.getName() + "]"; //输出格式为【Thread[线程名
称,优先级,线程组的名称]】
} else {
return "Thread[" + getName() + "," + getPriority() + "," +
"" + "]";
}
}
所有线程一般都会隶属于某个线程组,也可以是默认线程组【main】,也可以是创建线程时明确指定的一个组
说明:
1.在创建之初,线程被限制到一个组里,而且不能改变到不同的组new Thread(group,"myThread");
2.如果创建多个线程而不指定线程组,它们就会与创建这个线程的线程在同一个组中
使用线程组创建线程对象的方法
1.public Thread(ThreadGroup group, Runnable target)
2.public Thread(ThreadGroup group, String name)
3.public Thread(ThreadGroup group, Runnable target, String name)
线程组的使用
public class Test5 {
public static void main(String[] args) {
//这个线程组yan1是在main线程中创建的,所以系统识别yan1为main线程组的后代线程组
ThreadGroup group = new ThreadGroup("yan1");
Thread t1=new Thread(
group,
()->{
int res=0;
for(int i=0;i<10000000;i++) {
//System.out.println(Thread.currentThread());
//System.out.println("");
res+=i;
}
});
t1.start();
System.out.println(Thread.currentThread()); //Thread[main,5,main]
//线程组的用途
ThreadGroup tg=Thread.currentThread().getThreadGroup(); //获取当前线
程所属的线程组 main
int acc=tg.activeCount();//获取main线程组下的所有活跃线程的线程数
Thread[] arr=new Thread[acc];
//获取main线程组下中所有的线程对象,包括后代线程组中,并将其存放到arr数组中
//当前main线程组中应该有2个线程:1、main主线程,2、这里启动的线程t1
tg.enumerate(arr);
//可以通过获取到的线程对象应用针对线程组中的所有活跃线程进行操作
for(Thread tmp:arr) {
System.out.println("\t"+tmp);
}
/* 输出
* Thread[main,5,main]
* Thread[Thread-0,5,yan1]
*/
//以树状结构显示所有线程组信息
tg.list();
/* 输出结果为:
* java.lang.ThreadGroup[name=main,maxpri=10]
* Thread[main,5,main]
* java.lang.ThreadGroup[name=yan1,maxpri=10]
* Thread[Thread-0,5,yan1]
*/
}
}
线程组的主要用途:
可以通过线程组,对线程组中的一组线程进行统一管理,而不是一个一个的进行管理
注意:显示线程时,如果线程显示为null,表示这个线程已经执行完毕,线程已经消亡。
在具体开发中很少使用
多线程编程细节
Thread类
Thread类实现了Runnable接口,所以Thread类对象也是Runnable对象,同时Thread类也是线程类
Runnable r=new Thread();
构造器
Thread() //一般用于在Thread类中覆盖定义run方法,可以使用匿名内部类的方式进行定义
Thread(Runnable) //使用最多的情形,run方法是通过Runnable对象提供的
Thread(String) //创建线程对象的同时设置线程名称
Thread(Runnable,String)
//常见的简化写法
Thread t=new Thread(()->{
System.out.println(Thread.currentThread());
});
t.start();
常见方法
void start() | 使该线程对象开始执行,处于就绪状态,注意不是立即执行,不是一般 方法调用;JVM调用的是该线程对象的run方法 |
void run() | 线程的执行体,其中是当前线程的具体处理逻辑 |
void setName(String) | 改变该线程对象的名称 |
void setPriority(int) | 更改该线程对象的优先级,Java中线程的优先级可以分为1-10整数,默 认为5。优先级越高则获取更多的运行机会 |
void setDaemon(boolean) | 设置该线程对象为守护线程,一般守护线程中使用的都是死循环,在会 所有的非守护线程退出后自动关闭 |
void join()/join(long) | 等待该线程对象终止,调用的当前线程会阻塞等待,如果有参数long则 表示最多等待long毫秒 |
void interrupt() | 中断该线程,不是中断线程的执行,而是修改中断参数 |
boolean isActive() | 判断该线程对象是否处于活跃状态。活跃状态是指线程已经启动但是还 没有终止。 |
static void yield() | 暂停正在执行的线程对象,并执行其它线程。具体执行哪个线程取决于 系统的线程调度机制,也可有可能该线程对象再次运行 |
static void sleep(long) | 在指定的long毫秒数的时间内使当前正在执行的线程休眠(暂停执 行),此操作受到系统计时器和调度程序精度和准确性的影响 |
static Thread currrentThread() | 返回当前正在执行的线程对象的引用 |
Runnable接口
Runnable接口中只定义了一个方法public void run(),Runnable对象一般称为可运行对象,一个线程对象运行的就是Runnable对象中的run方法
重点:run方法没有返回值,而且不能抛出异常
public class MyRunnable implements Runnable{
public void run() throws Exception{ //语法报错,因为不允许抛出异常,如果run方
法中有可能会有受检型异常,则需要使用try/catch结构进行处理。
//没有返回值,如果需要记录处理结果,必须自己编程实现
}
}
//简化写法
new Thread(()->{
for(int i=0;i<100;i++){
System.out.println("左手画条龙....");
try{
Thread.sleep(100); //这里有一个受检型异常,因为run不允许抛出异常,所以
必须使用try/catch进行处理
} catch(InterruptedException e){
e.printStackTrace();
}
}
}).start();
Callable接口
继承Thread类或者实现Runnable接口都有一个缺陷:在执行完成后无法获取执行结果。如果需要获取执行结果,就必须通过共享变量或者使用线程之间通信Pipedxxx管道流的方式来实现,这样使用非常的麻烦。引入Callable接口的目的就是获取执行结果
Thread和Runnable都在java.lang包中,而Callable是在juc包java.util.concurrentCallable接口中有一个方法call,call方法要求有返回值,这个返回值可以通过泛型进行约束,并且允许抛出异常
public class MyCallable implements Callable<Number> {
public Number call() throws Exception{
return 100; --- int
return 100.0; --- double
} //call方法的返回值类型要和Callable接口后面<>中的类型一致。数值型的包装类的父类
型就是Number
}
//简化写法
new Thread(new FutureTask<>(()->{
for(int i=0;i<10;i++){
Sytem.out.println("右手画彩虹");
Thread.sleep(30);//允许抛出异常
}
return null; //必须有返回值
})).start();
Future接口
Future接口对象用于表示一个任务的生命周期,提供了一组方法用于判断是否已经完成或者取消,以及获取任务的执行结果或取消任务。必要时可以通过get()方法获取线程的执行结果,该方法可以阻塞当前线程直到任务结束为止或者超时
1.cancel方法用于取消任务。如果取消成功则返回true,否则false。
2.isCancelled方法用于获取任务是否被成功取消。任务正常完成前被取消则返回true
3.isDone方法表示任务是否已经完成,如果已经完成返回true
4.get()获取执行结果,这个方法会产生阻塞,直到任务执行完毕才返回
5.get(long,TimeUnit)获取执行结果,这个方法会阻塞一段时间,直到任务执行完毕或者超过指定时间才返回。如果在指定时间内没有获取到结果则产生超时异常
public class Test1 {
public static void main(String[] args) throws Exception {
Future f = new FutureTask(() -> {
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread() + "...start...");
Thread.sleep(10);
System.out.println(Thread.currentThread() + "...end...");
}
return 100;
});
if(f instanceof Runnable) {
new Thread((Runnable)f).start();
int count=0;
while(true) {
Thread.sleep(20);
System.out.println("任务是否被取消:"+f.isCancelled()+"---
"+count);
System.out.println("任务是否已经完成:"+f.isDone());
count++;
if(count>10)
f.cancel(true); //取消任务的执行
if(count>20)
break;
}
}
//如果任务是取消的方式完成的,这里获取返回值出现异常
java.util.concurrent.CancellationException。可以通过这个异常判定取消的方式完成的任
务,不是正常结束,如果正常结束会有返回值,哪怕是null
System.out.println("获取到的返回止:"+f.get());
}
}
使用get方法获取线程执行的返回值
public class Test2 {
public static void main(String[] args) throws Exception {
Future f = new FutureTask(() -> {
int res = 0;
for (int i = 0; i < 1000; i++) {
Thread.sleep(10);
res += i;
}
return res;
});
// 启动线程
if (f instanceof Runnable) {
new Thread((Runnable) f).start();
int count = 0;
long start = System.currentTimeMillis();
// Object obj = f.get(); // 调用get会一直阻塞当前线程main到子线程执行结束
为止
//阻塞当前mian线程等待子线程执行返回值,最长时间为5秒,如果超时则抛出异常
TimeoutException
Object obj = f.get(5, TimeUnit.SECONDS);// 参数1为具体的时间值,参数
2是一个TimeUnit枚举类型的值,用于表示时间的单位,例如毫秒、秒等
long end = System.currentTimeMillis();
System.out.println("get方法执行的时间为:" + (end - start) + "ms");
System.out.println("线程执行结果为:" + obj);
}
}
}
FutureTask类
FutureTask实现了两个接口Runnable和Future,所以它既可以作为Runnable被线程执行,也可以作为Future获取Callable的返回值
FutureTask是一个可取消的异步计算,FutureTask实现了Future接口的基本方法,提供了cancel操作,可以查询是否已经完成,并获取最终的计算结果。结果只能在计算完成后获取,get方法具备阻塞等待的功能,一旦计算完成,那么计算就不能再次启动或者取消
使用方法:
Callable<Integer> callable=new MyCallable();
FutureTask<Integer> future=new FutureTask<>(callable);
new Thread(future).start();