一、继承Thread类
1. 自定义一个类 ,继承extends Thread类
2. 重写run(),并指定执行任务
3. 通过new创建线程对象,并由start() 启动线程
优势:代码简洁 ;
劣势:单继承,一旦继承Thread类,就不可以再继承其他类拓展性不好。
class MyThread extends Thread{
public MyThread(String name){
super(name);
}
//定义执行的任务
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
Thread thread = Thread.currentThread();
System.out.println(thread+" : "+i);
}
}
}
public class ThreadDemo {
//打印输出线程信息 Thread[线程名字 , 线程优先级 , 所属的线程组]
public static void main(String[] args) {
MyThread my = new MyThread("T1"); //副线程
my.start(); //start() 才是真正启动当前线程my的方法
//my.run(); //run() 并不是启动当前线程my,由main主线程去执行任务
//这就是main主线程的任务
for (int i = 0; i <10 ; i++) {
Thread thread = Thread.currentThread();
System.out.println(thread+" : "+i);
}
/*System.out.println(my); //Thread[T1,5,main]
Thread main = Thread.currentThread();
System.out.println(main); //Thread[main,5,main]*/
}
}
二、实现 Runnable接口
1. 自定义一个任务类,实现implements Runnable接口
2. 重写run(),并指定执行任务
3. 通过new创建线程对象,并由start() 启动线程
优势:
解决了Java单继承带来的局限;
允许创建多条线程,执行同一个任务;
线程池中只能放入Runnable或Callable的实例
增强了程序的拓展性,实现解耦操作,降低耦合度:多个线程可以共享同一个任务的代码快,但任务代码和线程独立。
劣势:相比较于第一种较为繁琐。
class MyRunnable implements Runnable{
//制定执行的任务
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() +" : "+i);
}
}
}
public class RunnableDemo {
public static void main(String[] args) {
//创建任务
MyRunnable myRun = new MyRunnable();
//创建线程,并传入任务,启动线程
new Thread(myRun , "t1").start();
new Thread(myRun , "t2").start();
//主线程执行如下任务
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread() +" : "+i);
}
}
}
三、实现 Callable接口
1. 自定义一个类,实现implements Callable接口
2. 重写call(),并指定执行的任务
3. 创建Callable 对象
4. 创建FutureTask任务对象,并传入Callable对象
5. 通过new创建线程对象,传入FutureTask任务对象,并由start()启动线程
6. 通过FutureTask对象,get()获取执行完毕后的结果
优势:
解决了Java单继承带来的局限;
允许创建多条线程,执行同一个任务;
线程池中只能放入Runnable或Callable的实例
增强了程序的拓展性,实现解耦操作,降低耦合度:多个线程可以共享同一个任务的代码快,但任务代码和线程独立。
劣势:相比较于第一种较为繁琐。
public class CallableDemo {
public static void main(String[] args) {
//创建Callable对象
MyCallable MyCall = new MyCallable();
//创建FutureTask对象,将来获取执行任务后的结果
//FutureTask 间接实现 Runnable接口
FutureTask task1 = new FutureTask(MyCall);
FutureTask task2 = new FutureTask(MyCall);
//创建线程,启动线程,执行任务
new Thread(task1 , "线程1").start();
new Thread(task2 , "线程2").start();
//获取当前任务的返回值
try {
String string1 = task1.get().toString();
System.out.println("string1 = "+string1);
String string2 = task2.get().toString();
System.out.println("string2 = "+string2);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class MyCallable implements Callable<String>{
//制定的执行的任务,可以携带返回值
@Override
public String call() throws Exception {
int sum = 0;
for (int i = 0; i < 10; i++) {
sum+=i;
System.out.println(Thread.currentThread().getName()+" : "+i );
}
return Thread.currentThread().getName()+" , sum = "+sum;
}
}
四、通过线程池
1. newSingleThreadExecutor --创建单线程的线程池
* 这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。 * 如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。 * 此线程池保证所有任务的执行顺序按照任务的提交顺序执行。 //单线程的线程池 ExecutorService pool = Executors.newSingleThreadExecutor(); |
2. newCachedThreadPool --创建可缓存的线程池
* 如果线程池的大小超过了处理任务所需要的线程,那么会回收部分空闲(60秒不执行任务)的线程, * 当任务数增加时,此线程池又可以智能的添加新线程来处理任务。 * 此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。 //创建一个可缓存的线程池 ExecutorService pool = Executors.newCachedThreadPool(); |
3. newFixedThreadPool --创建固定数量线程的线程池
* 每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。 * 线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束, * 那么线程池会补充一个新线程。 //创建一个固定大小的线程池 ExecutorService pool = Executors.newFixedThreadPool(3); |
4. newScheduledThreadPool --创建可执行周期任务的线程池
*创建一个大小无限的线程池,此线程支持定时以及周期性执行任务的需求 public static void main(String[] args) { //ScheduledExecutorService 定时执行任务的线程池 ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1); /** * scheduleAtFixedRate() * 第一个参数:定时执行的任务 Runnable对象 * 第二个参数:延迟时间 * 第三个参数:周期时间 * 第四个参数:时间单位 */ scheduledExecutorService.scheduleAtFixedRate(()->{ LocalDateTime now = LocalDateTime.now(); System.out.println("now = "+now); },1000 , 1000 , TimeUnit.MILLISECONDS); } } |