-
程序(program):完成特定任务、用某种语言编写的一组指令集合。一段静态代码,静态对象。
-
进程(process):程序的一次执行,或者正在运行的一个程序。是一个动态的过程:产生,存在,消亡。——生命周期
- 程序是静态的,进程是动态的。
- 进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域。
-
线程(thread):进程可进一步细化为线程,是程序内部的一条执行路径。
- 若一个进程同一时间并行执行多个线程,就i是支持多线程的
- 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小。——每一个都有各自的虚拟机栈和程序计数器
- 一个进程中的多个线程共享相同的内存单元/内存地址 --> 他们从同一堆中分配对象,可以访问 相同的变量和对象。——共享进程的方法区和堆
- 线程之间的通信更加简便,高效。但是多个线程操作共享的系统资源就会带来安全隐患。
- 并行:同一时刻,多个任务同时执行。
- 并行:同一时间段,多个任务同时执行。
- 提高应用程序的相应。对图形化界面更有意义,增强用户体验。
- 提高计算机系统CPU的利用率。
- 改善程序结构。将即长又复杂的进程分为多个线程,独立运行,利用理解和修改。
- 程序需要同时执行两个或者多个任务。
- 程序需要实现一些需要等待的任务时,例如用户的输入,文件读写,网络操作,搜索等。
- 需要一些后台运行的程序时。
- 守护线程:垃圾回收就是守护线程。用来服务用户线程的通常在start方法前调用thread。setDaemon(true)可以把用户线程变成守护线程,
- 用户线程:主方法就是用户线程。
Java的JVM允许程序运行多个线程,通过java.lang.Thread类来体现。
Thread类的特征- 每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常把run()方法的主体成为线程体。
- 通过Thread对象的start()方法来启动这个线程,而非直接调用run()。
-
viod start();启动线程,并执行对象的run()方法。
-
run():线程在被调用时执行的操作。
-
String getName():返回线程的名称
-
void setName(String name):设置该线程名称 ——必须在start()之前设置名字
///通过重写构造方法改名字 class MyThread extends Thread{ public MyThread(String name){ super(name); } //2.重写run @Override public void run() { for (int i = 0; i < 10; i++) { if(i%2==0){ System.out.println(Thread.currentThread().getName()+":"+i); } } } } ==================================== ///直接通过setname()修改名字 public class ThreadMethodTest { public static void main(String[] args) { MyThread t1 = new MyThread("zixianc"); t1.setName("线程一"); //t1改名 t1.start(); Thread.currentThread().setName("主线程"); //主线程改名 for (int i = 0; i < 10; i++) { //主线程执行 if(i%2==0){ System.out.println(Thread.currentThread().getName()+":"+i); } } } } -
static Thread currentThread():返回当前线程,在Thread子类中就是this,通常用于主线程和Runnable实现类。
-
static void yield():线程让步,释放当前CPU的执行权。
public class ThreadMethodTest { public static void main(String[] args) { MyThread t1 = new MyThread("zixianc"); t1.setName("线程一"); //t1改名 t1.start(); Thread.currentThread().setName("主线程"); //主线程改名 for (int i = 0; i < 10; i++) { //主线程执行 if(i%2==0){ System.out.println(Thread.currentThread().getName()+":"+i); } if (i%1==0){ Thread.currentThread().yield(); //释放当前CPU的执行权 } } } } -
join():B.join方法,在线程A中调用B.join方法,此时线程A进入阻塞状态,直到线程B执行完毕,线程A才结束阻塞状态。——会抛异常
public class ThreadMethodTest { public static void main(String[] args) { MyThread t1 = new MyThread("zixianc"); t1.setName("线程一"); //t1改名 t1.start(); Thread.currentThread().setName("主线程"); //主线程改名 for (int i = 0; i < 10; i++) { //主线程执行 if(i%2==0){ System.out.println(Thread.currentThread().getName()+":"+i); } if (i== 4){ try { t1.join(); //执行完t1之后才会继续执行主线程 } catch (InterruptedException e) { e.printStackTrace(); } } } } } -
static void sleep(long millis):使当前线程睡眠指定的millitime毫秒,在指定的的时间内当前线程是阻塞状态。——会抛异常
public class ThreadMethodTest { public static void main(String[] args) { MyThread t1 = new MyThread("zixianc"); t1.setName("线程一"); //t1改名 t1.start(); Thread.currentThread().setName("主线程"); //主线程改名 for (int i = 0; i < 10; i++) { //主线程执行 if(i%2==0){ try { Thread.sleep(1000); //一秒执行一次 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+":"+i); } } } } -
stop():强制线程生命周期结束。
-
boolean isAlive():判断线程是否存活。
- MAX_PRIORITY:10
- MIN_PRIORITY:1
- NORM_PRIORITY:5 ——默认优先级
- 设置和获取当前线程的优先级:
- getPriority():获取线程优先级
- setPriority():设置线程的优先级
注:并不是优先级高就一定先执行
说明:高优先级的线程抢占低优先级cpu的执行权,但是只是从概率上讲,高优先级的线程具有更高概率被执行。并不意味着只有高优先级的线程执行完以后,低优先级的线程才会执行。
public class ThreadMethodTest {
public static void main(String[] args) {
MyThread t1 = new MyThread("zixianc");
t1.setName("线程1"); //t1改名
//设置分线程的优先级
t1.setPriority(Thread.MAX_PRIORITY);
t1.start();
Thread.currentThread().setName("主线程"); //主线程改名
///设置主线程的优先级
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
for (int i = 0; i < 10; i++) { //主线程执行
if(i%2==0){
System.out.println(Thread.currentThread().getName()+":"+Thread.currentThread().getPriority()+":"+i);
}
}
System.out.println(t1.isAlive());
}
}
线程创建
方式一:继承于Thread类
- 创建一个继承于Thread类的子类。
- 重写Thread类的run()方法。 ——此线程执行的操作声明在run()方法中。
- 创建Thread类的子类的对象。
- 通过此对象调用start()。
- 两个作用,启动当前线程和调用当前线程的run方法
- 不可以通过重复使用start()来创建新线程(IllegalThreadStateException),只能通过重新创建一个新的对象。
注:无法通过run方法来启动新的线程,只使用t1.run();相当于主线程调用发方法,还在主线程中,顺序执行。
//1.创建一个子类
class MyThread extends Thread{
//2.重写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.创建子类对象
MyThread t1 = new MyThread(); //主线程执行
//4.通过此对象调用start
t1.start(); //自动调用了run()方法。 t1线程
t1.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) {
//通过匿名子类直接调用
new Thread(){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if(i%2==0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}.start();
}
}
==========================================
public class ThreadTest {
public static void main(String[] args) {
//通过匿名子类直接调用
new Thread(new Runnable() {
@Override
public void run() {
//操作
}
}).start();
}
}
方法二:实现Runnable接口
- 创建一个实现Runnable接口的类
- 实现类去实现Runnable中的抽象方法:run()
- 创建实现类的对象
- 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
- 通过Thread类的对象调用start()
//1.实现类继承Runnable接口
class MTread implements Runnable{
//重写run方法
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if(i%2==0){
System.out.println(i);
}
}
}
}
public class Test {
public static void main(String[] args) {
//3.创建实现类对象
MTread m1 = new MTread();
//4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
Thread t1 = new Thread(m1);
//5.通过Thread类调用start()————调用了Runnable类型的target的run()
t1.start();
//再创建一个线程
Thread t2 = new Thread(m1);
t2.start();
}
}
两种创建方式的比较
开发中优先选择,实现Runnable接口的方式
原因:
- 实现的方式没有类的单继承性的局限
- 实现的方式更适合来处理多个线程共享数据的情况
联系:
- Thread类本身也是实现了Runnable接口
- 两种方式都要重写run(),将线程要执行的逻辑声明在run中
Thread.State类定义的线程的几种状态
五种状态:
- 新建:一个Thread或者它的子类被声明并创建时。
- 就绪:处于新建状态的线程被start后,进入线程队列等待CPU时间片。
- 运行:当就绪的线程被调度并获得CPU资源时。
- 阻塞:在特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行。
- 死亡:线程完成了她的全部工作或者线程被提前强制性中止或出现异常导致结束,
synchronized(同步监视器){
//需要被同步的代码
}
说明:
-
操作共享数据的代码,即为需要被同步的代码。 ——不能包含过多或者过少,会改变执行情况
-
共享数据:多个线程共同操作的变量。
-
同步监视器,俗称锁。任何一个类的对象,都可以充当。
要求:多个线程必须共用同一把锁
补充:
- 在创建方式一通过继承Thread类创建多线程,要慎用this,使用其他对象要加static,可以考虑使用类.class来做同步监视器。
- 在创建方式二通过实现Runnable接口创建多线程,可以考虑使用this来做同步监视器。
class ChildThread1 implements Runnable{
private int n = 100;
Object object = new Object();
@Override
public void run() {
//synchronized同步代码块
while (true){
synchronized (this) {
if(n>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + n);
n--;
}else{
break;
}
}
}
}
}
public class Demo01 {
public static void main(String[] args) {
ChildThread1 c1 = new ChildThread1();
Thread t1 = new Thread(c1);
Thread t2 = new Thread(c1);
Thread t3 = new Thread(c1);
t1.setName("窗口1");
t1.start();
t2.setName("窗口2");
t2.start();
t3.setName("窗口3");
t3.start();
}
}
================================
使用继承子类的方式创建多线程要注意唯一锁的
1.创建static对象
2.使用childThread.class来作为同步监视器。
方式二:同步方法 synchronized
- 同步方法依然需要同步监视器,只不过不需要显式的声明。
- 非静态的同步方法的锁时this,对于静态的同步方法的锁是当前类本身(类.class)。
两种不同多线程实现方式区别:
- 采用继承Thread类实现,需要使用static和synchronized,此时同步监视器为类.class
- 采用实现Runnable接口可直接synchronized关键字,此时同步监视器为this
class Window implements Runnable{
private int n = 100;
@Override
public void run() {
while (true){
show();
}
}
private synchronized void show(){ //通过加关键字变成同步方法 默认同步监视器为this
if(n>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + n);
n--;
}
}
}
public class WindowThread {
public static void main(String[] args) {
Window w1 = new Window();
Thread t1 = new Thread(w1);
Thread t2 = new Thread(w1);
Thread t3 = new Thread(w1);
t1.setName("窗口1");
t1.start();
t2.setName("窗口2");
t2.start();
t3.setName("窗口3");
t3.start();
}
}
===========================================
//继承的方式使用同步方法
private static synchronized void show(){ //同步监视器:类.class
//private synchronized void show(){ //通过加关键字变成同步方法 无法实现方法同步 默认同步监视器为:t1,t2,t3
if(n>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + n);
n--;
}
}
死锁
- 分别占用对方的同步资源不放弃。等待对方放弃自己所需要的资源。
- 出现死锁后不会出现异常,不会出现踢死,只是所有的线程都处于阻塞状态。
- 使用同步时避免死锁的发生。
必须保证使用的lock是同一个对象,保证同步监视器的唯一
import java.util.concurrent.locks.ReentrantLock;
class Window1 implements Runnable{
private int ticket = 100;
//1.实例化lock
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try {
//2.调用锁定lock方法
lock.lock(); //也要保证lock是唯一的,在多个线程中调用时。
if (ticket>0){
try {
Thread.sleep(100);
} 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) {
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();
}
}
synchronized 和 lock 的异同点
不同点:
- synchronized执行完同步代码以后,自动的释放同步监视器
- Lock需要手动的启动同步lock(),同时也需要手动的实现unlock()
- wait() :阻塞当前进程,会释放当前线程所占用的同步监视器(锁)。
- notify():一旦执行此方法,就会唤醒被wait方法阻塞的线程。如果有多个就唤醒优先级高的那个。
- notifyAll():会唤醒所有被wait方法阻塞的线程。
说明:
- 以上三个方法只能出现在同步方法或者同步代码块之中。Lock无法使用。
- 三个方法的调用者必须是同步代码块或者同步方法的同步监视器,否则 IllegalMonitorStateException 异常。
- 事实上三个方法是定义在Java.lang.Object中。
class Number implements Runnable{
private int num = 1;
@Override
public void run() {
while (true){
synchronized (this) {
notify(); //只唤醒一个
// notifyAll(); 唤醒整个线程
if(num<=100){
System.out.println(Thread.currentThread().getName() + ":" + num);
num++;
try {
wait(); //使得调用如下wait方法的线程进入阻塞状态——会释放同步监视器(锁)
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
break;
}
}
}
}
}
public class CommunicationTest {
public static void main(String[] args) {
Number number = new Number();
Thread t1 = new Thread(number);
Thread t2 = new Thread(number);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
================================================================
class Number implements Runnable{
private int num = 1;
private Object obj = new Object();
@Override
public void run() {
while (true){
synchronized (obj) { //式子一
obj.notify(); //同步监视器调用方法——必须和式子一中同步监视器保持一致
// notifyAll(); 唤醒整个线程
if(num<=100){
System.out.println(Thread.currentThread().getName() + ":" + num);
num++;
try {
obj.wait(); //同步监视器直接调用——必须和式子一中同步监视器保持一致
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
break;
}
}
}
}
}
public class CommunicationTest {
public static void main(String[] args) {
Number number = new Number();
Thread t1 = new Thread(number);
Thread t2 = new Thread(number);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
wait和sleep异同点:
- 相同:都可以是=使线程阻塞。
- 不同点:
- 声明位置不同:Thread类中声明sleep,Object类声明wait
- 调用要求不同:sleep可以在任何场景使用。wait只能使用在同步代码块和同步方法中。
- 是否释放同步监视器:sleep不会释放,wait会释放锁。
- 创建一个实现Callable接口的实现类
- 实现call方法,将线程需要执行的操作声明在call()中
- 创建Callable接口实现类的实例对象。
- 将Callable接口实现类的实例对象作为参数传递到FutureTask构造器中,创建FutureTask实例对象。
- 将FutureTask的对象作为参数传递到Thread类的构造器中,创建thread对象
与Runnable接口相比
- 相比Run方法,Call方法有返回值
- 方法可以抛出异常
- 支持泛型的返回值
- 需要借助FutureTask类,比如获取返回结果。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
//1.创建一个实现Callable接口的实现类
class NumThread implements Callable{
//2.实现call方法,将线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
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 n1 = new NumThread();
//4.将Callable接口实现类的实例对象作为参数传递到FutureTask构造器中,创建FutureTask实例对象。
FutureTask futureTask = new FutureTask(n1);
//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建thread对象
Thread t1 = new Thread(futureTask);
//启动线程
t1.start();
try {
//get()方法的返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
//6.获取callable中call方法的返回值
Object sum =futureTask.get();
System.out.println(sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
方式四:线程池的方式
-
提供指定线程数量的线程池
-
执行指定的线程的操作,需要提供指定的实现Runnable或Callable接口实现类的对象。
-
关闭线程池
好处:
- 提高响应速度(减少了创建线程的时间)
- 降低资源消耗(重复利用线程池中的线程,不需要每次创建)
- 便于线程管理
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
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 executorService = Executors.newFixedThreadPool(10);
//2.执行指定的线程的操作,需要提供指定的实现Runnable或Callable接口实现类的对象。
executorService.execute(new NumberThread());//适合使用于Runnable
executorService.execute(new NumberThread1());//提供参数为了说明执行什么操作
// executorService.submit();//适合使用于Callable
//3.关闭线程池
executorService.shutdown();
}
}
线程池的管理
public class ThreadPool {
public static void main(String[] args) {
//1.提供指定线程数量的线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
//设置线程池的属性
ThreadPoolExecutor service = (ThreadPoolExecutor) executorService;//无法对接口进行操作,需要转化成对象对数学进行操作。
// service.setCorePoolSize(15);//核心池的大小
// service.setKeepAliveTime();//线程没有任务最多保持多长时间终止
// service.setMaximumPoolSize();//最大线程数
//2.执行指定的线程的操作,需要提供指定的实现Runnable或Callable接口实现类的对象。
executorService.execute(new NumberThread());//适合使用于Runnable
executorService.execute(new NumberThread1());//提供参数为了说明执行什么操作
// executorService.submit();//适合使用于Callable
//3.关闭线程池
executorService.shutdown();
}
}
生产者消费者问题
继承Thread类实现
package com.feng.java.ThreadTest;
class Clerk{
public static int num = 0;
public synchronized void product(){
try {
Thread .sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (num<20){
num++;
notify();
System.out.println("生产了一个产品,当前产品数量为:" + num);
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void custom(){
try {
Thread .sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (num>0){
num--;
notify();
System.out.println("消耗了一个产品,当前产品数量为:" + num);
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class productor extends Thread{
private static Clerk clerk = new Clerk();
public productor(Clerk clerk) {
this.clerk=clerk;
}
@Override
public void run() {
while (true) {
clerk.product();
}
}
}
class customer extends Thread{
private static Clerk clerk = new Clerk();
public customer(Clerk clerk) {
this.clerk=clerk;
}
@Override
public void run() {
while (true) {
clerk.custom();
}
}
}
public class ProductTest1 {
public static void main(String[] args) {
Clerk c1 = new Clerk();
productor p1 = new productor(c1);
customer cu1 = new customer(c1);
p1.setName("生产者");
cu1.setName("消费者");
p1.start();
cu1.start();
}
}
实现runnable接口实现
package com.feng.java.ThreadTest;
class ProductThread implements Runnable{
private final int PRODUCT_NUMBER = 20;
private int pron = 0;
@Override
public void run() {
while (true) {
synchronized (this) {
if(Thread.currentThread().getName()=="productor"){
if(pron>PRODUCT_NUMBER-1){ //大于等于20停止生产,阻塞等待
try {
this.wait(); //阻塞生产者进程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
pron++;
notify(); //生产一个就可以取消消费者阻塞
System.out.println("productor生产了一个产品现在产品数目为:" + pron);
}
if(Thread.currentThread().getName()=="customer"){
if(pron<1){ //小于等于0停止消费,阻塞等待
try {
this.wait(); //阻塞消费者进程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
pron--;
notify(); //消费一个就可以取消生产者阻塞
System.out.println("customer消费了一个产品现在产品数目为:" + pron);
}
}
}
}
}
public class ProductTest {
public static void main(String[] args) {
ProductThread p1 = new ProductThread();
Thread t1 = new Thread(p1);
Thread t2 = new Thread(p1);
t1.setName("productor");
t2.setName("customer");
t1.start();
t2.start();
}
}
面试题
1、线程的生命周期
2、同步监视器和共享数据
- 同步监视器是一个对象任何对象都可以,但是多个线程需要同步需要同一个
- 多个线程都需要进行操作的数据。
- 相同:都可以是使线程阻塞。
- 不同点:
- 声明位置不同:Thread类中声明sleep,Object类声明wait
- 调用要求不同:sleep可以在任何场景使用。wait只能使用在同步代码块和同步方法中。
- 是否释放同步监视器:sleep不会释放,wait会释放锁。
public static Bank getInstance(){
if(instance==null){
syschronized(Bank.class){
if (instance==null){
instance = new Bank();
}
}
}
return instance;
}
5、创建多线程有哪几种方式
-
方式一:继承Thread类
- 创建一个继承于Thread类的子类。
- 重写Thread类的run()方法。 ——此线程执行的操作声明在run()方法中。
- 创建Thread类的子类的对象。
- 通过此对象调用start()。
-
方式二:实现Runnable接口
- 创建一个实现Runnable接口的类
- 实现类去实现Runnable中的抽象方法:run()
- 创建实现类的对象
- 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
- 通过Thread类的对象调用start()
-
方式三:实现Callable接口
- 创建一个实现Callable接口的实现类
- 实现call方法,将线程需要执行的操作声明在call()中
- 创建Callable接口实现类的实例对象。
- 将Callable接口实现类的实例对象作为参数传递到FutureTask构造器中,创建FutureTask实例对象。
- 将FutureTask的对象作为参数传递到Thread类的构造器中,创建thread对象
-
方式四:线程池
-
提供指定线程数量的线程池
-
执行指定的线程的操作,需要提供指定的实现Runnable或Callable接口实现类的对象。
-
关闭线程池
-



