文章目录
1、前言🧂
一、概述🥚
程序
:为完成特定任务,用某种编程语言编写的一组指令的结合(一段静态代码,静态对象)java
进程
:是程序的一次执行的过程或是正在运行的一个程序,是一个动态的过:有他自身的产生、存在、消亡的过程(生命周期),进程做为资源分配的单位,系统在运行时会为每一个进程分配不一样的内存区域。web
线程
:进程能够进一步细化为线程,是一个程序内部的一条执行路径。面试
知识小盲区🍰:算法
若一个进程同一时间并行多个线程,就是支持多线程的。编程
线程做为调度和执行的单位,每一个线程拥有独立的运行栈和程序计数器(PC),线程切换的开销小。安全
一个进程的多个线程共享相同的内存单元、内存地址空间—>他们从同一堆中分配对象,能够访问相同的变量和对象。就使得线程间通讯更加简便,高效。但多个线程操做共享的系统资源可能就会带来安全的隐患。网络
线程做为调度和执行的单位,每一个线程拥有独立的运行栈和程序计数器(PC)。多线程
本地方法栈— native架构
虚拟机栈和PC 每个线程都有一份并发
方法区和堆 是一个进程一份
一个进程中有多个线程 多个线程共享方法区和堆
二、CPU🍮
单核CPU,是一种假的多线程
多核的话,能更好地发挥多线程的效率
三、并行和并发🥛
并行
:多个CPU同时执行多个任务 ,例如:多我的同时作不一样的事儿
并发
: 一个CPU同时执行多个任务 ,例如:秒杀多我的同时作一件事儿
四、优势🧊
-
提升应用程序的响应。对图形化界面更有意义,可加强用户体验
-
提升计算机系统CPU的利用率
-
改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
五、多线程时机🍿
- 程序须要同时执行两个或多个任务
- 程序须要实现一些须要风带的任务时,好比: 用户输入,文件读写操做,网络操做,搜索等
- 须要一些后台运行的程序时
2、线程的建立和使用🥢
方式一🍵
java语言的JVM容许程序运行多个线程经过继承 java.lang.Thread
Thread类的特性🍯
- 每一个线程都是经过某个特性Thread对象的run()方法来完成操做,常常把run()方法的主体称为线程体
- 经过该Thread对象的start()方法来启动这个线程,而非直接调用run()
多线程的建立
方式一 继承Thread类
- 建立一个继承Thread类的子类
- 重写Thread类的run(); --------- 将此线程执行的操做声明在run方法中
- 建立Thread类的子类的对象
- 经过对象调用start();
//1.建立一个继承Thread类的子类
class MyThread extends Thread {
//2.重写Thread类的run();
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//3.建立Thread类的子类的对象
MyThread t1 = new MyThread();
//4.经过对象调用start(); 两个:启动当前线程,调用当前线程的run方法
t1.start();//经过t1调用run 是不能够的 啊
//如下方法仍然是在main线程中执行的
System.out.println("hello");
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i + "main");
}
}
t1.start();
}
}
为何是经过对象调用start();方法?🏓
答:
- 启动当前线程
- 调用当前线程的run方法
经过t1调用run 是不建议的
Thread 类的相关方法🍾
void start()
:启动线程,并执行对象run方法
run()
:线程被调度时执行的操做
String getName()
:返回线程的名字
void setNAme (String name )
:设置该线程的名称
static Thread currentThread()
:返回当前线程。在Thread子类中就是this ,一般用于主线 程和Runnable实现类
static void yield()
:线程让步
暂停当前正在执行的线程,把执行的机会让给优先级相同的或者更高的线程
join()
:当某个成序执行流中调用其余线程的join();方法,调用线程将被阻塞,直到join方法加 入的join线程执行完为止
static void sleep(long millitime)
:令当前活动线程在指定时间段内放弃对CPU的控 制,是其余线程有机会被执行,时间到后从新排队
stop()
:强制线程生命期结束,不推荐使用
boolean isAlive()
:返回boolean,判断线程是否还活着
线程的调度🧉
-
调度策略
- 时间片
- 抢占式:高优线假的线程抢占CPU
-
java调度方法
- 同优先级线程—先来先服务,使用时间片策略
- 对高优先级的,使用优先调度的抢占式策略
线程的优先级🍹
线程的优先级等级
-
MAX_PRIOPITY :10
-
MIN_PRIOPRITY:1
-
NPRM_PRIORITY:5
涉及的方法:
-
getPriority:返回县城优先值
-
setPriority(int newPriority): 改变线程的优先级
说明:
-
线程建立时继承父线程的优先级
-
低优先级只是得到调度的几率低,并不是必定是高优先级线程被调度后才会被调用
方式二🎱
实现Runnable接口
-
建立一个实现了Runnable接口的类
-
子类中重写Runnable接口中的run方法
-
建立实现类的对象/经过Thread类含参构造器建立线程对象
-
将Runnable接口的子类对象做为实现参数传递给Thread类的构造器中
-
经过Thread类的对象调用start();方法:开启线程,调用Runnable子类接口的run方法
//1.建立一个实现了Runnable接口的类
class MThread implements Runnable{
//2.实现类去实现Runnable中的抽象方法run();
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 ==0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
public class ThreadTest1 {
public static void main(String[] args) {
//3.建立实现类的对象
MThread mThread = new MThread();
// 4.将此对象做为参数传递到Thread类的构造器中,建立Thread类的对象
Thread t1 = new Thread(mThread);
// 5.经过Thread类的对象调用start();方法
t1.setName("线程1");
t1.start();
//这是t1线程
//再启动一个线程
Thread t2 = new Thread(mThread);
t2.start();
}
}
经过Thread类的对象调用start()方法
两个功能 :
- 启动线程
- 调用当前线程的run方法 ----调用了Runnable类型的target的
比较线程的两种方式
- 一个对象三个线程
- 三个对象三个线程
开发当中优先选择实现Runnable接口的方式
缘由
1:实现的方式没有类的单继承的局限性
2:实现的方式更适合来处理多个线程有共享数据的状况
联系
:public class Thread implements Runnable
相同点
:都是须要去重写run(),而且将线程要执行的逻辑声明在run()中
线程的分类:
java中,线程分两种:守护线程
和用户线程
- 他们俩几乎在每一个方面都是相同的,惟一的区别是判断JVM什么时候离开。
- 守护线程是用来服务用户线程的,经过在stat();方法前调用
- thread.setDaemon(true)能够吧一个用户线程变成一个守护线程
- java垃圾回收就是一个经典的守护线程
- 若JVM中都是守护线程,JVM将退出
守护线程依赖用户线程
问题一
:
-
咱们不能直接经过调用run方法的方式启动线程
-
咱们要调用start
问题二
:
-
再启动一个线程,遍历100之内的偶数
-
应该再建立一个对象
-
IllegalThreadStartException
-
不可让已经start的线程去再执行
-
要想建立多个线程就要建立多个对象
-
建立Thread类的匿名子类的方式
方式三🧇
经过Callable和Future建立线程
实现步骤:
①
建立Callable接口的实现类,并实现call()方法,改方法将做为线程执行体,且具备返回值。
②
建立Callable实现类的实例,使用FutrueTask类进行包装Callable对象,FutureTask对象封装了Callable对象的call()方法的返回值
③
使用FutureTask对象做为Thread对象启动新线程。
④
调用FutureTask对象的get()方法获取子线程执行结束后的返回值。
优劣对比🍦
继承Thread类和实现Runnable接口、实现Callable接口的区别
继承Thread
:线程代码存放在Thread子类run方法中。
-
优点:编写简单,可直接用this.getname()获取当前线程,没必要使用Thread.currentThread()方法。
-
劣势:已经继承了Thread类,没法再继承其余类。
实现Runnable
:线程代码存放在接口的子类的run方法中。
-
优点:避免了单继承的局限性、多个线程能够共享一个target对象,很是适合多线程处理同一份资源的情形。
-
劣势:比较复杂、访问线程必须使用Thread.currentThread()方法、无返回值。
实现Callable
:
-
优点:有返回值、避免了单继承的局限性、多个线程能够共享一个target对象,很是适合多线程处理同一份资源的情形。
-
劣势:比较复杂、访问线程必须使用Thread.currentThread()方法
建议使用实现接口的方式建立多线程
3、线程的生命周期🫖
新建 NEW
:当一个Thread类或其子类的对象被声明并建立时,新生的线程对象处于新建状态
就绪RUNNABLE
:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具有了运行的条件,只是没分配到CPU资源
运行BLOCKED
:当就绪的线程被调度并得到CPU资源时便进入运行状态,run()方法定义了线程的操做和功能
阻塞WAITING
:在某种特殊状况下,被人为挂起或执行输入输出操做时,让出CPU并临时停止本身的执行,进入阻塞状态
死亡
:线程完成了它的所有工做或线程被提早强制性地停止或出现异常致使结束
4、线程的同步 🍚
同步方法
同步代码块
Lock
🍕小案例
建立三个窗口买票
总票数100张 使用实现Runnable接口的方式
存在线程安全问题 须要解决
- 问题: 买票过程当中出现了重票错票 —>出现线程安全问题
- 问题出现缘由:当某个线程操做车票中,还没有完成时,其余线程参与进来进行操做
- 解决:进来的时候关个门 当一个线程a在操做时 其它线程不能参与进来。直到线程a操做完 成,其余线程才能够操做ticket,这样 即便线程a出现了阻塞,也不要紧
- 在java中咱们经过同步机制来解决线程安全问题
方式一:同步代码块
synchronized(同步监视器){
大括号中是须要被同步的代码
}
说明 :
- 操做共享数据的代码,即为须要被同步的代码---->不能包含多,也不可包含少代码
- 共享数据:多个线程共同操做的变量,好比:ticket就是共享数据
- 同步监视器: 俗称 锁 任何一个类的对象均可以称为锁
要求: 多个线程必须共用一把锁
补充: 在实现Runnable接口建立多线程的方式中,咱们能够考虑使用this充当同步监视器
方式二:同步方法
若是操做共享数据的代码完整的声明在一个方法中 ,
咱们不妨将此方法声明同步的
- 同步方法仍然涉及到同步监视器,只是不须要咱们显式声明。
- 非静态的同步方法,同步监视器是:this
静态的同步方法,同步监视器是:当前类自己
同步的好处:解决了线程安全问题
操做同步代码时只能有一个线程参与其余线程等待 至关于单线程的过程 效率低
使用同步代码块的方式解决
class Window2 extends Thread {
private static int ticket = 100;
private static Object obj = new Object();
@Override
public void run() {
while (true) {
// synchronized(obj) {
synchronized(Window2.class) {
if (ticket > 0) {
//还有余票
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + ":出售的票编号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class WindowTest2 {
public static void main(String[] args) {
//这是由三个对象 不能用this
Window2 w1 = new Window2();
Window2 w2 = new Window2();
Window2 w3 = new Window2();
w1.setName("窗口一");
w2.setName("窗口二");
w3.setName("窗口三");
w1.start();
w2.start();
w3.start();
}
}
class Window1 implements Runnable{
private int ticket = 100;
// Object obj = new Object();
@Override
public void run() {
// Object obj = new Object();
while (true){
synchronized(this){
//this惟一的window的对象
if (ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":所售票的票号为:"+ ticket);
ticket--;
}else {
break;
}
}
}
}
}
public class WindowTest1 {
public static void main(String[] args) {
Window1 w = new Window1();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
使用同步方法
class Window3 implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
show();
}
}
private synchronized void show() {
//同步监视器就是this
// synchronized(this){
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":所售票的票号为:" + ticket);
ticket--;
}
}
}
public class WindowTest3 {
public static void main(String[] args) {
Window3 w = new Window3();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Window4 extends Thread {
private static int ticket = 100;
@Override
public void run() {
while (true) {
show();
}
}
private static synchronized void show(){
//方法改成静态的 同步监视器是惟一 的了 就是当前类Window4.class惟一的
// private synchronized void show(){//同步监视器 t1 t2 t3 此种解决方法是错误的
if (ticket > 0) {
//还有余票
try {
Thread.sleep(
10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":出售的票编号为:" + ticket);
ticket--;
}
}
}
public class WindowTest4 {
public static void main(String[] args) {
Window4 w1 = new Window4();
Window4 w2 = new Window4();
Window4 w3 = new Window4();
w1.setName("窗口一");
w2.setName("窗口二");
w3.setName("窗口三");
w1.start();
w2.start();
w3.start();
}
}
线程安全问题之懒汉式
package com.yer.java1;
/**
* 使用同步机制 将单例模式中的懒汉式 改写为 线程安全的
*
* @author Darling
* @create 2022-02-11-17:28
*/
public class BankTest {
}
class Bank {
//单例的 建立一个当前类的实例
private static Bank instance = null;
private Bank() {
}//构造器私有化
public static Bank getInstance() {
// public static synchronized Bank getInstance() { //当前类自己
//方式一:效率稍差 第一个建立完,后面的就不必等了 啊
// synchronized (Bank.class){
// if (instance == null)
// instance = new Bank();//就实例化一会儿
// return instance;
// }
//方式二 :效率更高
if (instance == null) {
synchronized (Bank.class) {
if (instance == null)
instance = new Bank();
}
}
return instance;
}
}
线程的死锁问题
死锁:不一样的几线程分别占用对方的资源不释放,都在等待对方释放本身须要的同步资源,就想成了线程的死锁
出现思索后,不会出现异常,么有提示,全部线程都处于阻塞状态
解决方法:
- 专门的算法,原则
- 尽可能减小同步资源的定义
- 尽可能避免嵌套同步
Lock锁🍯
java.util.concurrent.locks.Lock
package com.yer.java1;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
*
* 解决线程安全问题的方式三: Lock锁 ---- JDK5.0 新增的新特性
*
*
* 1.面试题synchronized 与lock的异同
* 解答: 相同点:都是解决线程安全问题
* 不一样点:synchronized机制在执行完相应对的同步代码之后,自动释放同步监视器
* Lock手动启动同步(lock.lock()),同时结束同步也须要手动的实现 (lock.unlock();)
* @author Darling
* @create 2022-02-11-19:33
*/
class Window implements Runnable{
private int ticket = 100;
//1.实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock();//true 公平 先进先出
@Override
public void run() {
while (true){
try{
//2.调用lock方法 获取了同步监视器
lock.lock();
//就至关于同步代码块中的同样
if (ticket > 0){
try {
Thread.sleep(99);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"售出车票编号为:"+ticket);
ticket--;
}else {
break;
}
}finally {
//3.调用解锁的方法
lock.unlock();
}
}
}
}
public class LockTest {
public static void main(String[] args) {
Window w = new Window();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
面试题synchronized 与lock的异同
解答:
-
相同点:都是解决线程安全问题
-
不一样点:synchronized机制在执行完相应对的同步代码之后,自动释放同步监视器
Lock手动启动同步(lock.lock()),同时结束同步也须要手动的实现(lock.unlock()😉
synchronized与Lock的对比🫖
-
Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),
synchronized是隐式锁,出了做用域自动释放
-
Lock只有代码块锁,synchronized有代码块锁和方法锁
-
使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。而且具备
更好的扩展性(提供更多的子类)
优先使用顺序:
Lock→同步代码块(已经进入了方法体,分配了相应资源)→同步方法(在方法体以外)
5、线程的通讯🎂
线程通讯
wait();
notify();唤醒一个(优先级比较高的)
notifyAll(); 唤醒全部
这三个方法必须使用在在同步代码块 或者 同步方法
Lock不能够! 有其余的
这三个方法的调用者,必须是同步代码块 同步方法的调用者 的不然报异常
这三个方法是Object类中的
面试题:关于sleep(); wait();的相同点和不一样点
相同点:一旦执行,均可以使得当前的线程进入阻塞状态
不一样点:1)两个方法声明的位置不同Thread类中声明sleep();
Object类中声明wait();
2)调用的要求不同,sleep();能够在任何须要的场景下调用
wait();必须在同步代码块或同步方法中调用
3)关因而否释放同步监视器:若是两个方法都是用在同步代码块或同步方法中,
sleep不会释放锁,wait会释放同步 监视器
6、JDK5.0新增的线程建立方式🍮
实现Callable接口
package com.yer.java1;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 建立线程方式三 :实现Callable接口 ---JDK5.0新增
*
*
* 如何理解实现Callable接口 建立多线程,比实现Runnable接口强大呢?
* 1.call方法是能够有返回值的
* 2.call方法是能够抛出异常的 被外面的操做捕获,获取异常的信息
* 3.Callable支持泛型的
* @author Darling
* @create 2022-02-11-20:24
*/
//1.建立一个实现Callable 的实现类
class NumThread implements Callable {
//2.实现call方法,将此线程须要执行的操做声明在call()中
@Override
public Object call() throws Exception {
//遍历100之内偶数,而且返回偶数的和
int sum = 0;
for (int i = 0; i < 100; i++) {
if (i%2 == 0){
System.out.println(i);
sum += i;
}
}return sum;
}
}public class ThreadNew {
public static void main(String[] args) {
//3.建立Callable 接口实现类的对象
NumThread numThread = new NumThread();
//4.将此Callable接口实现类的对象做为传递到FutureTask构造器中,建立FutureTask对象
FutureTask futureTask = new FutureTask(numThread);
//5.将FutureTask的对想作微参数传递到Thread类的构造器中,建立Thread对象,并调用start();方法
new Thread(futureTask).start();
try {
//6.获取Callable中的call方法的返回值 ---不用返回值能够省略啊
//get()返回值即为FutureTask构造器参数Callable实现类重写的返回值
Object sum = futureTask.get();
System.out.println("总和:"+sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
实现Callable接口
相比run();call();有返回值
方法能够抛出异常
支持泛型的返回值
须要借助FuntureTask类
Future fut = new FutureTask(numThread)
7、线程池🍸
背景
: 常常建立和销毁、使用量特别大的资源,好比并发状况下的线程,对性能影响很大。
思路: 提早建立好多个线程,放入线程池中,使用时直接获取,使用完放回池中。能够避免频繁建立销毁、实现重复利用。相似生活中的公共交通工具。
好处
:
- 提升响应速度(减小了建立新线程的时间)
- 下降资源消耗(重复利用线程池中线程,不须要每次都建立)
- 便于线程管理
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
线程池相关API
JDK5.0起提供了线程池相关APl:
ExecutorService和Executors
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
-
voidexecute(Runnablecommand):执行任务/命令,没有返回值,通常用来执行 Runnable
-
< T>Future< T>submit(Callable< T>task):执行任务,有返回值,通常又来执行Callable
-
void shutdown():关闭链接池
Executors:工具类 、线程池的工厂类,用于建立并返回不一样类型的线程池
-
ExecutorsnewCachedThreadPool():建立一个可根据须要建立新线程的线程池
-
ExecutorsnewFixedThreadPool(n);建立一个可重用固定线程数的线程池
-
ExecutorsnewSingleThreadExecutor():建立一个只有一个线程的线城池
-
ExecutorsnewScheduledThreadPool(n):建立一个线程池,它可安排在给定延迟后运行命令或者按期地执行。
package com.yer.java1;
import java.util.concurrent.AbstractExecutorService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 建立线程的方法四:
* 使用线程池
* <p>
* 1.提升响应速度(减小了建立新线程的时间)
* 2.下降资源消耗(重复利用线程池中的线程,不须要每次都建立)
* 3.便于线程管理
* <p>
* corePoolSize-------核心池的大小
* maximumPoolSize-----最大线程数
* keepAliveTime--线程没有任务时最多保持多长时间后会终止
*
* @author Darling
*/
class NumberThread implements Runnable {
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
class NumberThread1 implements Runnable {
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//1.提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//建立一个能够重用的固定线程数的线程池
//在执行以前去设置线程池的属性 接口 在接口的实现类中--
System.out.println(service.getClass());//class java.util.concurrent.ThreadPoolExecutor
// ThreadPoolExecutor
// public class ThreadPoolExecutor extends AbstractExecutorService {
// public abstract class AbstractExecutorService implements ExecutorService {
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
service1.setCorePoolSize(20);
// service1.setKeepAliveTime();
//2.执行指定的线程的操做,须要提供实现Runnable或Callable接口实现类的对象
//service.execute();//适合Runnable
//提交submit 执行execute
service.submit(new NumberThread());//适合Callable
service.submit(new NumberThread1());//适合Callable
//3.关闭链接池
service.shutdown();
//真正在开发使用线程池
}
}
//建立多线程有四种方式
static void main(String[] args) {
//1.提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//建立一个能够重用的固定线程数的线程池
//在执行以前去设置线程池的属性 接口 在接口的实现类中--
System.out.println(service.getClass());//class java.util.concurrent.ThreadPoolExecutor
// ThreadPoolExecutor
// public class ThreadPoolExecutor extends AbstractExecutorService {
// public abstract class AbstractExecutorService implements ExecutorService {
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
service1.setCorePoolSize(20);
// service1.setKeepAliveTime();
//2.执行指定的线程的操做,须要提供实现Runnable或Callable接口实现类的对象
//service.execute();//适合Runnable
//提交submit 执行execute
service.submit(new NumberThread());//适合Callable
service.submit(new NumberThread1());//适合Callable
//3.关闭链接池
service.shutdown();
//真正在开发使用线程池
}
}
//建立多线程有四种方式