全站资源开放下载,感谢广大网友的支持
链接失效请移步职涯宝平台的学习路线|资源下载分类
支持用户留言评论_客服实时在线_问题解决更快

第二十五天 多线程-常用方法&线程池【悟空教程】

85
发表时间:2018-09-06 16:26

第二十五天 多线程-常用方法&线程池【悟空教程】

第25天 多线程

第1章 多线程常用方法

1.1 多线程常用方法

1.1.1 常规方法:

  • public final String getName()   返回该线程的名称。

  • public final void setName(String name)

  • public static Thread currentThread()  返回的就是当前的执行线程

  • public void run()

  • public void start()

  • 重写public String toString()

继承Thread方法:

public class MyThread  extends  Thread {

//重写 run

@Override

public void run() {

for (int i = 0; i < 1000 ; i++) {

System.out.println(getName()  + "  ====  "  + i  );

}

}

}

/*

* 多线程 简单方法:

* String getName() 返回该线程的名称。

* void setName(String name)  设置线程名称

* Thread  currentThread() , 返回的就是当前的执行线程.

*/

public class Demo {

public static void main(String[] args) {

// 创建 MyThread 对象

MyThread mt1 = new MyThread();

MyThread mt2 = new MyThread();

mt1.setName("姚明");

mt2.setName("郭敬明");


mt1.start();

mt2.start();

}

}


实现Runnable接口

public class MyRunnable implements Runnable {

@Override

public void run() {

for (int i = 1000; i >= 0; i--) {

// 获取当前方法的执行线程

Thread currentThread = Thread.currentThread(); // 执行当前的方法的线程对象.

System.out.println(currentThread.getName() + " ==== " + i);

}

}

}

public class Demo {

public static void main(String[] args) {

//创建 子类对象

MyRunnable myRunnable = new MyRunnable();

//创建线程对象

Thread thread = new Thread(myRunnable);

Thread thread2 = new Thread(myRunnable);

thread.setName("XXX");

thread2.setName("AAA");


// 开启线程  

thread.start();

thread2.start();


Thread currentThread = Thread.currentThread();

currentThread.setName("^(*(oo))^");

System.out.println(currentThread.getName());

}

}


1.1.2 线程操作方法:

1.1.2.1 优先级

public final void setPriority(int newPriority) 设置优先级,取值:1-10

public final int getPriority()     获取优先级

/*

public final void setPriority(int newPriority) 设置优先级,取值:1-10

public final int getPriority()     获取优先级

*/

public class Demo2 {

public static void main(String[] args) {


 Thread currentThread = Thread.currentThread();

 System.out.println(currentThread);

 int priority = currentThread.getPriority();

 System.out.println(priority);  // 默认是   5  ,最小 1  ,最大10;

 

 

 MyThread myThread = new MyThread();

 myThread.setPriority(10);

 myThread.start();

 

 for (int i = 0; i < 1000 ; i++) {

System.out.println("main  ---"  + i);

}

}

}


1.1.2.2 线程加入&守护线程

public final void join() throws InterruptedException   线程加入(插队,加入方法)

public final void setDaemon(boolean on)     设置守护线程(坦克大战,守护main

public class T1 extends Thread {

@Override

public void run() {

for (int i = 0; i < 1000; i++) {

if (i == 200) {

// 加入另外一条线程

try {

T2 t2 = new T2();

t2.start();

t2.join();  // 加入方法, 就是插队.

} catch (Exception e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

System.out.println(i    + " ==== t1"  );

}

}

}

public class T2 extends Thread  {

 @Override

public void run() {

 for (int i = 0; i < 500 ; i++) {

System.out.println(" t2  ---- "  + i );

}

 }

}

/*

* join . 加入

*  setDaemon(boolean on) 设置守护线程

*

*/

public class Demo3 {

public static void main(String[] args) {


T1 t1 = new T1();

t1.setDaemon(true);  // t1 就是守护线程 了

 

t1.start();

for (int i = 0; i < 10; i++) {

System.out.println(i);   // 主线程 一完成,  守护也就结束.

}

}

}

public void interrupt()   被中断的线程会报被中断异常,这时需要使用try/catch语句解决相关问题,线程后代码仍然可以继续执行

public final void stop()  (已过时)  直接停止线程,线程后代码无法被执行

1.1.2.3 线程睡眠到指定时间自动恢复

public static void sleep(long millis) throws InterruptedException

/*

* public static void sleep(long millis)  睡眠 .

*/

public class Demo {

public static void main(String[] args) {

for (int i = 10; i > 0; i--) {

// 暂停

try {

Thread.sleep(10000);  // 到指定的时间,自动醒过来.

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(i);

}

System.out.println("点火 ");

}

}


1.2 测试JVM多线程_垃圾回收线程

/*

* 获取垃圾回收线程的名字.

*/

public class Demo {

public static void main(String[] args) {

for (int i = 0; i < 100000; i++) {

new Person();

System.out.println(i);

}

}

}

public class Person {


public Person() {

System.out.println(" 我来了, HELLO==WORLD");

}

@Override

protected void finalize() throws Throwable {

System.out.println(" 我是 垃圾 了, 我挂了, ByeBye World ");


Thread currentThread = Thread.currentThread();

System.out.println(currentThread.getName());

}

}


1.3 等待唤醒机制

在开始讲解等待唤醒机制之前,有必要搞清一个概念——线程之间的通信:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制。

等待唤醒机制所涉及到的方法:

  • wait() :等待,将正在执行的线程释放其执行资格 和 执行权,并存储到线程池中。

  • notify():唤醒,唤醒线程池中被wait()的线程,一次唤醒一个,而且是任意的。

  • notifyAll(): 唤醒全部:可以将线程池中的所有wait() 线程都唤醒。

其实,所谓唤醒的意思就是让 线程池中的线程具备执行资格。必须注意的是,这些方法都是在 同步中才有效。同时这些方法在使用时必须标明所属锁,这样才可以明确出这些方法操作的到底是哪个锁上的线程。

仔细查看JavaAPI之后,发现这些方法 并不定义在 Thread中,也没定义在Runnable接口中,却被定义在了Object类中,为什么这些操作线程的方法定义在Object类中?

因为这些方法在使用时,必须要标明所属的锁,而锁又可以是任意对象。能被任意对象调用的方法一定定义在Object类中。

接下里,我们先从一个简单的示例入手:

如上图说示,输入线程向Resource中输入name ,sex , 输出线程从资源中输出,先要完成的任务是:

  • 1.当input发现Resource中没有数据时,开始输入,输入完成后,叫output来输出。如果发现有数据,就wait();

  • 2.当output发现Resource中没有数据时,就wait() ;当发现有数据时,就输出,然后,叫醒input来输入数据。

下面代码,模拟等待唤醒机制的实现:

  • 模拟资源类

public class Resource {

private String name;

private String sex;

private boolean flag = false;

public synchronized void set(String name, String sex) {

if (flag)

try {

wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

// 设置成员变量

this.name = name;

this.sex = sex;

// 设置之后,Resource中有值,将标记该为 true ,

flag = true;

// 唤醒output

this.notify();

}

public synchronized void out() {

if (!flag)

try {

wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

// 输出线程将数据输出

System.out.println("姓名: " + name + ",性别: " + sex);

// 改变标记,以便输入线程输入数据

flag = false;

// 唤醒input,进行数据输入

this.notify();

}

}

  • 输入线程任务类

public class Input implements Runnable {

private Resource r;

public Input(Resource r) {

this.r = r;

}

@Override

public void run() {

int count = 0;

while (true) {

if (count == 0) {

r.set("小明", "男生");

} else {

r.set("小花", "女生");

}

// 在两个数据之间进行切换

count = (count + 1) % 2;

}

}

}

  • 输出线程任务类

public class Output implements Runnable {

private Resource r;

public Output(Resource r) {

this.r = r;

}

@Override

public void run() {

while (true) {

r.out();

}

}

}

  • 测试类

public class ResourceDemo {

public static void main(String[] args) {

// 资源对象

Resource r = new Resource();

// 任务对象

Input in = new Input(r);

Output out = new Output(r);

// 线程对象

Thread t1 = new Thread(in);

Thread t2 = new Thread(out);

// 开启线程

t1.start();

t2.start();

}

}


1.4 线程生命周期


第2章 线程池

2.1 线程池概念

线程池,其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。

我们详细的解释一下为什么要使用线程池?

在java中,如果每个请求到达就创建一个新线程,开销是相当大的。在实际使用中,创建和销毁线程花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间和资源要多的多。除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。如果在一个jvm里创建太多的线程,可能会使系统由于过度消耗内存或“切换过度”而导致系统资源不足。为了防止资源不足,需要采取一些办法来限制任何给定时刻处理的请求数目,尽可能减少创建和销毁线程的次数,特别是一些资源耗费比较大的线程的创建和销毁,尽量利用已有对象来进行服务。

线程池主要用来解决线程生命周期开销问题和资源不足问题。通过对多个任务重复使用线程,线程创建的开销就被分摊到了多个任务上了,而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使用应用程序响应更快。另外,通过适当的调整线程中的线程数目可以防止出现资源不足的情况。

2.2 使用线程池方式--Runnable接口

通常,线程池都是通过线程池工厂创建,再调用线程池中的方法获取线程,再通过线程去执行任务方法。

  • Executors:线程池创建工厂类

    • public static ExecutorService newFixedThreadPool(int nThreads):返回线程池对象

  • ExecutorService:线程池类

    • Future<?> submit(Runnable task):获取线程池中的某一个线程对象,并执行

  • Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用

  • 使用线程池中线程对象的步骤:

    • 创建线程池对象

    • 创建Runnable接口子类对象

    • 提交Runnable接口子类对象

    • 关闭线程池

代码演示:

public class ThreadPoolDemo {

public static void main(String[] args) {

//创建线程池对象

ExecutorService service = Executors.newFixedThreadPool(2);//包含2个线程对象

//创建Runnable实例对象

MyRunnable r = new MyRunnable();

//自己创建线程对象的方式

//Thread t = new Thread(r);

//t.start(); ---> 调用MyRunnable中的run()

//从线程池中获取线程对象,然后调用MyRunnable中的run()

service.submit(r);

//再获取个线程对象,调用MyRunnable中的run()

service.submit(r);

service.submit(r);

//注意:submit方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭。将使用完的线程又归还到了线程池中

//关闭线程池

//service.shutdown();

}

}


  • Runnable接口实现类

public class MyRunnable implements Runnable {

@Override

public void run() {

System.out.println("我要一个教练");


try {

Thread.sleep(2000);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println("教练来了: " +Thread.currentThread().getName());

System.out.println("教我游泳,交完后,教练回到了游泳池");

}

}

2.3 使用线程池方式Callable接口

  • Callable接口:与Runnable接口功能相似,用来指定线程的任务。其中的call()方法,用来返回线程任务执行完毕后的结果,call方法可抛出异常。

  • ExecutorService:线程池类

    • <T> Future<T> submit(Callable<T> task):获取线程池中的某一个线程对象,并执行线程中的call()方法

  • Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用

  • 使用线程池中线程对象的步骤:

    • 创建线程池对象

    • 创建Callable接口子类对象

    • 提交Callable接口子类对象

    • 关闭线程池

代码演示:

public class ThreadPoolDemo {

public static void main(String[] args) {

//创建线程池对象

ExecutorService service = Executors.newFixedThreadPool(2);//包含2个线程对象

//创建Callable对象

MyCallable c = new MyCallable();

//从线程池中获取线程对象,然后调用MyRunnable中的run()

service.submit(c);

//再获取个教练

service.submit(c);

service.submit(c);

//注意:submit方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭。将使用完的线程又归还到了线程池中

//关闭线程池

//service.shutdown();

}

}


  • Callable接口实现类,call方法可抛出异常、返回线程任务执行完毕后的结果

public class MyCallable implements Callable {

@Override

public Object call() throws Exception {

System.out.println("我要一个教练:call");

Thread.sleep(2000);

System.out.println("教练来了: " +Thread.currentThread().getName());

System.out.println("教我游泳,交完后,教练回到了游泳池");

return null;

}

}

2.4 线程池练习:返回两个数相加的结果

要求:通过线程池中的线程对象,使用Callable接口完成两个数求和操作

  • Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用

    • V get() 获取Future对象中封装的数据结果

代码演示:

public class ThreadPoolDemo {

public static void main(String[] args) throws InterruptedException, ExecutionException {

//创建线程池对象

ExecutorService threadPool = Executors.newFixedThreadPool(2);

//创建一个Callable接口子类对象

//MyCallable c = new MyCallable();

MyCallable c = new MyCallable(100, 200);

MyCallable c2 = new MyCallable(10, 20);

//获取线程池中的线程,调用Callable接口子类对象中的call()方法, 完成求和操作

//<Integer> Future<Integer> submit(Callable<Integer> task)

// Future 结果对象

Future<Integer> result = threadPool.submit(c);

// Future get 方法所返回的结果类型

Integer sum = result.get();

System.out.println("sum=" + sum);

//再演示

result = threadPool.submit(c2);

sum = result.get();

System.out.println("sum=" + sum);

//关闭线程池(可以不关闭)

}

}


  • Callable接口实现类

public class MyCallable implements Callable<Integer> {

//成员变量

int x = 5;

int y = 3;

//构造方法

public MyCallable(){

}

public MyCallable(int x, int y){

this.x = x;

this.y = y;

}

@Override

public Integer call() throws Exception {

return x+y;

}

}

第3章 多线程总结

  • 同步锁

多个线程想保证线程安全,必须要使用同一个锁对象

    • 同步代码块

        synchronized (锁对象){

   可能产生线程安全问题的代码

}

同步代码块的锁对象可以是任意的对象

    • 同步方法

        public synchronized void method()

             可能产生线程安全问题的代码

}

同步方法中的锁对象是 this

  • 静态同步方法

public synchronized void method()

             可能产生线程安全问题的代码

}

静态同步方法中的锁对象是 类名.class

  • 多线程有几种实现方案,分别是哪几种?

a, 继承Thread

b, 实现Runnable接口

c, 通过线程池,实现Callable接口

  • 同步有几种方式,分别是什么?

a,同步代码块

b,同步方法

 静态同步方法

  • 启动一个线程是run()还是start()?它们的区别?

启动一个线程是start()

区别:

start: 启动线程,并调用线程中的run()方法

run  : 执行该线程对象要执行的任务

  • sleep()和wait()方法的区别

sleep: 不释放锁对象, 释放CPU使用权

在休眠的时间内,不能唤醒

wait(): 释放锁对象, 释放CPU使用权

在等待的时间内,能唤醒

  • 为什么wait(),notify(),notifyAll()等方法都定义在Object类中

锁对象可以是任意类型的对象


第4章 本日自习作业:

4.1 知识点相关题

4.1.1 请描述并画出线程生命周期图,越详细越好


4.1.2 简单使用一次今天介绍的线程方法


4.1.3 使用线程池完成多线程卖票


4.1.4 启动一个线程是用run()还是start()?

启动一个线程是调用start()方法,使线程就绪状态,以后可以被调度为运行状态,一个线程必须关联一些具体的执行代码,run()方法是该线程所关联的执行代码。


4.1.5 用实现runable接口的方式创建创建一个多线程买票小程序,使用卖票的例子引出线程安全问题(用sleep()方法)并用synchronized代码块解决线程安全问题。

答案:

public class Ticket implements Runnable {

//多个对象 共享同一个资源, 需要static修饰

private int ticket = 100;

//创建锁对象(可以是任意对象)

private Object obj = new Object();


@Override

public void run() {

while (true) {

synchronized ( obj ){

if (ticket >0) {

try {

Thread.sleep(100);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName()+":正在卖第 "+ ticket-- +"

");

}

}

}

}

}

public class SellTicket {

public static void main(String[] args) {

//创建自定义类对象

Ticket ticket = new Ticket();

//创建线程对象

Thread t1 = new Thread(ticket, "窗口1");

Thread t2 = new Thread(ticket, "窗口2");

Thread t3 = new Thread(ticket, "窗口3");

//启动线程

t1.start();

t2.start();

t3.start();

}

}


4.1.6 能够理解死锁的概念。


4.1.7 理解什么是线程间通信

其实就是多个线程在操作同一个资源,但是操作的动作不同。如果一个进程中的所有线程都不需要相互传递数据就可以顺利完成,那么程序运行的性能自然是最好的,但是实际上,很少有现成能够在所有的时间都独立的进行操作,通常在以下两种情况下,线程之间需要进行通信。 多个线程都对共享资源资源进行访问,但不希望共享资源被破坏。 一个线程完成了任务,要通知其他的线程。


4.1.8 多线程有几种实现方法?同步有几种实现方法?

多线程有两种实现方法,分别是继承Thread类与实现Runnable接口

同步的实现方面有两种,分别是synchronized,wait与notify

wait():使一个线程处于等待状态,并且释放所持有的对象的lock。

sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException异常。

notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。

Allnotity():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。


4.2 代码题

4.2.1 使用线程池完成以下功能(线程池的线程个数为2):

1、通过一个线程获取一个0-100内的随机数并在将随机数返回到main方法中,在main方法中将该随机数添加到list集合中;

2、将该任务向线程池提交3次,每次生成随机数之前让线程休眠1000毫秒,然后打印“”“线程XXXX生成的随机数为:XXX”;

3、main方法中打印集合的内容

import java.util.ArrayList;

import java.util.List;

import java.util.concurrent.ExecutionException;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import java.util.concurrent.Future;

public class Demo {

public static void main(String[] args) throws InterruptedException, ExecutionException {

m2();

}

public static void m2() throws InterruptedException, ExecutionException{

//1、使用线程池工厂创建线程池对象

ExecutorService pool = Executors.newFixedThreadPool(2);

//2、向线程池中提交任务

MyCallable my = new MyCallable();

List<Integer> list = new ArrayList<Integer>();

for(int i=0;i<3;i++){

//循环向线程池中提交任务

Future<Integer> s1 = pool.submit(my);

//解析线程执行的结果

Integer num1 = s1.get();

list.add(num1);

}

System.out.println(list);

//5、关闭线程池(可可不

pool.shutdown();

}

}

public class MyCallable implements Callable<Integer>{

@Override

public Integer call() throws Exception {

Thread thread = Thread.currentThread();

thread.sleep(1000);

int i = new Random().nextInt(100);

System.out.println("线程:"+thread.getName()+"生成的随机数为:"+i);

return i;

}

}

4.2.2 创建线程计算 1--100的和,并将结果返回给主线程

答案:

public class Test02 {

public static void main(String[] args) throws InterruptedException, ExecutionException {

ExecutorService es = Executors.newSingleThreadExecutor();

//创建的是Callable对象

Future<Integer> f = es.submit(new Callable<Integer>() {

@Override

public Integer call() throws Exception {

//求和并返回结果

int sum = 0;

for(int i = 1; i <= 100; i++){

sum += i;

}

return sum;

}

});

//获取结果

System.out.println(f.get());

es.shutdown();

}

}

4.2.3 写出一个锁嵌套的死锁代码

  • 定义锁对象类

public class MyLock {

public static final Object lockA = new Object();

public static final Object lockB = new Object();

}

  • 线程任务类

public class ThreadTask implements Runnable {

int x = new Random().nextInt(1);//0,1

//指定线程要执行的任务代码

@Override

public void run() {

while(true){

if (x%2 ==0) {

//情况一

synchronized (MyLock.lockA) {

System.out.println("if-LockA");

synchronized (MyLock.lockB) {

System.out.println("if-LockB");

System.out.println("if大口吃肉");

}

}

} else {

//情况二

synchronized (MyLock.lockB) {

System.out.println("else-LockB");

synchronized (MyLock.lockA) {

System.out.println("else-LockA");

System.out.println("else大口吃肉");

}

}

}

x++;

}

}

}

  • 测试类

public class ThreadDemo {

public static void main(String[] args) {

//创建线程任务类对象

ThreadTask task = new ThreadTask();

//创建两个线程

Thread t1 = new Thread(task);

Thread t2 = new Thread(task);

//启动线程

t1.start();

t2.start();

}

}


4.2.4 生产一个,消费一个;等待唤醒机制(单生产者单消费者)

答案:

public class Phone {

private String bland; //型号

private String color; //颜色

private boolean isNewPhone = false;//是否有新手机


//购买手机

public synchronized void get(){

//判断是否有新手机

if (!this.isNewPhone) {

//进来了,代表没有新手机

try {

this.wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

//代码执行到了这里, 说明此刻有新手机

//购买手机

System.out.println("购买了 :" + this.bland + "..." + this.color);

//更新新 手机的状态

this.isNewPhone = false;

//通知生产商, 没有新手机了, 要生产新手机

this.notify();

}


//生产手机

public synchronized void set(String bland, String color){

//判断是否有新手机

if (this.isNewPhone) {

//进来了,代表有手机

try {

this.wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

}


//如果走到了这个位置, 说明当前没有新手机

//生产一部新手机

this.bland = bland;

this.color = color;

System.out.println("生产了:" + this.bland + "---" + this.color);


//更新手机的状态为 有新手机

this.isNewPhone = true;

//通知消费者,有新手机了,可以购买

this.notify();

}

}

/*

* 生产商

*/

public class SetPhone implements Runnable {

private Phone phone;

int num = 0;


public SetPhone(Phone phone){

this.phone = phone;

}


@Override

public void run() {

//生产手机

while(true){

//生产一部新手机

if (num%2==0) {

phone.set("Iphone6 Plus", "土豪金");

} else {

phone.set("金立语音王", "红色");

}

num++;

}

}

}

/*

* 消费者类(顾客)

*/

public class GetPhone implements Runnable {

private Phone phone;

public GetPhone(Phone phone){

this.phone = phone;

}


@Override

public void run() {

while(true){

phone.get();

}

}

}

/*

* 测试类

*/

public class PhoneTest {

public static void main(String[] args) {

//定义一个手机对象

Phone phone = new Phone();


SetPhone setPhone = new SetPhone(phone);

GetPhone getPhone = new GetPhone(phone);

//创建线程对象

Thread setThread = new Thread(setPhone);

Thread getThread = new Thread(getPhone);

//启动线程

setThread.start();

getThread.start();

}

}


4.2.5 现有红包个数共50,模拟三个人在抢红包过程

每个人相当于一条线程(需要给三个命名),每次抢到的红包随机为1-10,要求每人每次只能抢一个红包,抢红包的过程中需要睡眠300毫秒,并且抢完的人还可以继续参与抢红包.抢到红包后在控制台打印输出”XXX线程抢到第X个红包,红包金额为X,还剩余X个红包...,另外在所有红包抢完后提示”红包已抢完”,程序结束.(要求使用Thread类和Runnable中的一种方式去实现).

4.2.6 一共有1000份盒饭,可以在两个窗口领取,假设每次装盒饭的时间为3000毫秒请用线程模拟取饭过程并打印剩余盒饭数量(分别设置线程名称 窗口一,窗口二).

4.2.7 同时开启两个线程(并且设置线程名称),共同输出1-10之间的所有数字,并且将输出偶数的线程名称打印出来.


支付宝赞助-Java帮帮社区
微信赞助-Java帮帮社区
Java帮帮公众号生态

Java帮帮公众号生态

总有一款适合你

Java帮帮-微信公众号

Java帮帮-微信公众号

将分享做到极致

Python帮帮-公众号

Python帮帮-公众号

人工智能,爬虫,学习教程

大数据驿站-微信公众号

大数据驿站-微信公众号

一起在数据中成长

九点编程-公众号

九点编程-公众号

深夜九点学编程

程序员生活志-公众号

程序员生活志-公众号

互联网,职场,程序员那些事儿

Java帮帮学习群生态

Java帮帮学习群生态

总有一款能帮到你

Java学习群

Java学习群

与大牛一起交流

大数据学习群

大数据学习群

在数据中成长

九点编程学习群

九点编程学习群

深夜九点学编程

python学习群

python学习群

人工智能,爬虫

测试学习群

测试学习群

感受测试的魅力

Java帮帮生态承诺

Java帮帮生态承诺

一直坚守,不负重望

初心
勤俭
诚信
正义
分享
合作品牌 非盈利生态-优质内容分享传播者
友链交换:加帮主QQ2524138991 留言即可 24小时内答复  
会员登录
获取验证码
登录
登录
我的资料
留言
回到顶部