Java多线程启动

一.java创建和启动线程较为常用的方式有继承Thread类、实现Runnable接口和匿名内部类的方式。

1.继承Thread类

1、定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务。因此把run方法称为线程执行体。

2、创建Thread子类的实例,即创建了线程对象。

3、调用线程对象的start()方法来启动该线程。

//Thread线程类
public class Thread1 extends Thread{
    @Override
    public void run(){
        for(int i = 0; i<10:i++){
            System.out.planln(Thread.currentThread().getName() + "执行" + i);
        }
    }
}

//
public class Main {
	
	public static void main(String[] args) {
		new Thread1().start();
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName() + "执行" + i);
		}
	}
}

可以看到main线程和thread1线程的for循环交替执行,线程启动成功。Thread1类继承了Thread类并重写了run方法。可以看到线程是以抢占式的方式运行。虽然只创建了一个线程实例,实际上共有2个线程在运行,还有main方法代表的主线程的线程执行体。

1.1 Thread.currentThread(),是Thread类的静态方法,该方法总是返回当前正在执行的线程对象。

1.2 getName(),该方法是Thread类的实例方法,该方法返回当前正在执行的线程名称。

2.实现Runnale接口

实现Runnable接口创建并启动多线程的步骤:

1.定义Runnable接口的实现类,并重写该接口的run()方法,该方法的方法体同样是该线程的线程执行体

2.创建Runnable实现类的实例对象,并以此实例对象作为Thread的target来创建Thread类,该Thread对象才是真正的线程对象。

3.调用线程对象的start()方法来启动该线程。

//实现Runnable接口
public class Thread2 implements Runnable{
 
	public void run() {
		
		for (int i = 0; i < 50; i++) {
			System.out.println(Thread.currentThread().getName() + "执行" + i);
		}
	}
 
}

//
public class Main {
	
	public static void main(String[] args) {
		new Thread(new Thread2()).start();
		for (int i = 0; i < 50; i++) {
			System.out.println(Thread.currentThread().getName() + "执行" + i);
		}
	}
}

      main函数中名没有直接执行Thread2的run方法,而是将Thread2填入到了Thread中,使用start方法来启动。Runable实现类里包含run方法,仅仅作为线程执行体,而实际的线程对象依然是Thread实例对象,Thread为真正创建线程的对象。

3.匿名内部类:

匿名内部类本质上也是一个类实现了Runnable接口,重写了run方法,只不过这个类没有名字,直接作为参数传入Thread类。

//匿名内部类
public class Main {
	
	public static void main(String[] args) {
		new Thread(new Runnable(){
                public void run(){
                    for (int i = 0; i < 50; i++) {
					System.out.println(Thread.currentThread().getName() + "执行" + i);
				}
                     
            }
        }).start();
		for (int i = 0; i < 50; i++) {
			System.out.println(Thread.currentThread().getName() + "执行" + i);
		}
	}
}

 

二.ExecutorService线程池

在Java5之后,并发线程这块发生了根本的变化,最重要的莫过于新的启动、调度、管理线程的一大堆API了。在Java5以后,通过Executor来启动线程比用Thread的start()更好。在新特征中,可以很容易控制线程的启动、执行和关闭过程,还可以很容易使用线程池的特性。

3.1创建任务

任务就是一个实现了Runnable接口的类。

创建的时候实run方法即可。

3.2执行任务

通过java.util.concurrent.ExecutorService接口对象来执行任务,该接口对象通过工具类java.util.concurrent.Executors的静态方法来创建。

Executors此包中所定义的 Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 类的工厂和实用方法。

ExecutorService提供了管理终止的方法,以及可为跟踪一个或多个异步任务执行状况而生成 Future 的方法。 可以关闭 ExecutorService,这将导致其停止接受新任务。关闭后,执行程序将最后终止,这时没有任务在执行,也没有任务在等待执行,并且无法提交新任务。

            executorService.execute(new TestRunnable());

3.2.1、创建ExecutorService

通过工具类java.util.concurrent.Executors的静态方法来创建。

Executors此包中所定义的 Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 类的工厂和实用方法。

比如,创建一个ExecutorService的实例,ExecutorService实际上是一个线程池的管理工具:

        ExecutorService executorService = Executors.newCachedThreadPool();

        ExecutorService executorService = Executors.newFixedThreadPool(3);

        ExecutorService executorService = Executors.newSingleThreadExecutor();

3.2.2、将任务添加到线程去执行

当将一个任务添加到线程池中的时候,线程池会为每个任务创建一个线程,该线程会在之后的某个时刻自动执行。

3.3关闭执行服务对象

        executorService.shutdown();

3.4获取任务的执行的返回值

在Java5之后,任务分两类:一类是实现了Runnable接口的类,一类是实现了Callable接口的类。两者都可以被ExecutorService执行,但是Runnable任务没有返回值,而Callable任务有返回值。并且Callable的call()方法只能通过ExecutorService的(<T> task) 方法来执行,并且返回一个 <T><T>,是表示任务等待完成的 Future。

public interface Callable<V>

返回结果并且可能抛出异常的任务。实现者定义了一个不带任何参数的叫做 call 的方法。Callable 接口类似于,两者都是为那些其实例可能被另一个线程执行的类设计的。但是 Runnable 不会返回结果,并且无法抛出经过检查的异常。

类包含一些从其他普通形式转换成 Callable 类的实用方法。

Callable中的call()方法类似Runnable的run()方法,就是前者有返回值,后者没有。

当将一个Callable的对象传递给ExecutorService的submit方法,则该call方法自动在一个线程上执行,并且会返回执行结果Future对象。

同样,将Runnable的对象传递给ExecutorService的submit方法,则该run方法自动在一个线程上执行,并且会返回执行结果Future对象,但是在该Future对象上调用get方法,将返回null。

 

四.ExecutorService的execute和submit方法区别,以及submit使用的具体Demo

1、接收的参数不一样

2、submit有返回值,而execute没有

Method submit extends base method Executor.execute by creating and returning a Future that can be used to cancel execution and/or wait for completion. 

用到返回值的例子,比如说我有很多个做validation的task,我希望所有的task执行完,然后每个task告诉我它的执行结果,是成功还是失败,如果是失败,原因是什么。然后我就可以把所有失败的原因综合起来发给调用者。

个人觉得cancel execution这个用处不大,很少有需要去取消执行的。

而最大的用处应该是第二点。

3、submit方便Exception处理

There is a difference when looking at exception handling. If your tasks throws an exception and if it was submitted with execute this exception will Go to the uncaught exception handler (when you don't have provided one explicitly, the default one will just print the stack trace to System.err). If you submitted the task with submit any thrown exception, checked or not, is then part of the task's return status. For a task that was submitted with submit and that terminates with an exception, the Future.get will rethrow this exception, wrapped in an ExecutionException.

意思就是如果你在你的task里会抛出checked或者unchecked exception,而你又希望外面的调用者能够感知这些exception并做出及时的处理,那么就需要用到submit,通过捕获Future.get抛出的异常。

比如说,我有很多更新各种数据的task,我希望如果其中一个task失败,其它的task就不需要执行了。那我就需要catch Future.get抛出的异常,然后终止其它task的执行,代码如下:

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.*;
 
/**
 *
 */
public class ExecutorServiceTest {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        List<Future<String>> resultList = new ArrayList<>();
 
        // 创建10个任务并执行
        for (int i = 0; i < 10; i++) {
            // 使用ExecutorService执行Callable类型的任务,并将结果保存在future变量中
            Future<String> future = executorService.submit(new TaskWithResult(i));
            // 将任务执行结果存储到List中
            resultList.add(future);
        }
        executorService.shutdown();
 
        // 遍历任务的结果
        for (Future<String> fs : resultList) {
            try {
                System.out.println(fs.get()); // 打印各个线程(任务)执行的结果
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                executorService.shutdownNow();
                e.printStackTrace();
                return;
            }
        }
    }
}
class TaskWithResult implements Callable<String> {
    private int id;
 
    public TaskWithResult(int id) {
        this.id = id;
    }
 
    /**
     * 任务的具体过程,一旦任务传给ExecutorService的submit方法,则该方法自动在一个线程上执行。
     *
     * @return
     * @throws Exception
     */
    public String call() throws Exception {
        System.out.println("call()方法被自动调用,干活!!!             " + Thread.currentThread().getName());
        //下面的判读是模拟一个抛出异常的操作
        if (new Random().nextBoolean())
            throw new TaskException("Meet error in task." + Thread.currentThread().getName());
        // 一个模拟耗时的操作
        for (int i = 999999999; i > 0; i--)
            ;
        return "call()方法被自动调用,任务的结果是:" + id + "    " + Thread.currentThread().getName();
    }
}
class TaskException extends Exception {
    public TaskException(String message) {
        super(message);
    }
}

 

五.有关线程池ExecutorService,submit的使用

5.1

可创建的类型如下:

private static ExecutorService pool = Executors.newFixedThreadPool(20);
//创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

private static ExecutorService pool1 = Executors.newCachedThreadPool();
//创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

private static ExecutorService pool2 = Executors.newScheduledThreadPool(20);
//创建一个定长线程池,支持定时及周期性任务执行。

private static ExecutorService pool3 = Executors.newSingleThreadExecutor();
//创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

在使用多线程时需要每个线程的返回值,了解到自己的类实现Callable接口可以实现,所以就写了测试用例,但是测试时发现不是并发执行。经过多处查证,发现端倪。
1.启动线程时会返回一个Future对象。
2.可以通过future对象获取现成的返回值。
3.在执行future.get()时,主线程会堵塞,直至当前future线程返回结果。
也是因为第三点,导致我每次运行都是顺序执行。。。。。

说一下submit(Callable<T> task)的用法。
Future<Integer> result = pool.submit(new ThreaTest());会返回一个自定义类型的Future对象。
注意,如果调用result.get()方法,会阻塞主线程,最坏的结果是所有线程顺序执行。
程序执行完后记得shutdown。
自己的类implements Callable<Integer>,并重写call方法,在call方法里完成业务逻辑,并添加返回值。

import java.util.ArrayList;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class ThreaTest implements Callable<Integer> {
    private int nowNumber = 0;
    private static ExecutorService pool = Executors.newFixedThreadPool(20);// 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

    public ThreaTest(int nowNumber) {
        this.nowNumber = nowNumber;
    }

    public static void main(String[] args) {
        ArrayList<Future<Integer>> result = new ArrayList<Future<Integer>>();

        for (int i = 0; i < 5000; i++) {
            Future<Integer> submit = pool.submit(new ThreaTest(i));
            result.add(submit);
        }

        pool.shutdown();
    }

    public Integer call() throws Exception {
        System.out.println(this.nowNumber);
        return this.nowNumber;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值