并发面试题:java中有几种方法可以实现一个线程?

本文作者:黄海燕,叩丁狼高级讲师。原创文章,转载请注明出处。

1.创建并启动线程的6种方式:

1)继承Thread类创建线程
2)实现Runnable接口创建线程
3)使用Callable和FutureTask创建线程
4)使用线程池,例如用Executor框架
5)Spring实现多线程(底层是线程池)
6)定时器Timer (底层封装了一个TimerThread对象)

1.1 继承Thread类创建线程

1.1.1继承Thread类方式创建线程的实现步骤:

步骤:
1):定义一个类A继承于java.lang.Thread类.
2):在A类中覆盖Thread类中的run方法.
3):我们在run方法中编写需要执行的操作---->run方法里的,线程执行体.
4):在main方法(线程)中,创建线程对象,并启动线程.
        创建线程类对象: A类 a = new A类();
        调用线程对象的start方法: a.start();//启动一个线程

注意:千万不要调用run方法,如果调用run方法好比是对象调用方法,依然还是只有一个线程,并没有开启新的线程.

1.1.2需求:使用两个线程实现边听歌边打游戏

实现代码:

//音乐线程
public class MusicThread {
public static void main(String[] args) {
//创建游戏线程对象
GameThread game = new GameThread();
//启动游戏线程
game.start();
while(true){
System.out.println(Thread.currentThread().getName()+"听音乐!");
}
}
}
//游戏线程
class GameThread extends Thread{
@Override
public void run() {
while (true) {
System.out.println(Thread.currentThread().getName()+"打游戏!");
}
}
}

注意:有的小伙伴可能觉得音乐线程没有启动,在这里其实音乐线程已经启动起来了,而启动音乐线程的对象就是我们的JVM,此处main方法其实启动的时候会创建一个主线程去执行main方法,所以我在这里使用主线程作为了我的音乐线程.

1.2 实现Runnable接口创建线程

1.2.1实现Runnable接口方式创建线程的实现步骤:

1):定义一个类A实现于java.lang.Runnable接口,注意A类不是线程类.
2):在A类中覆盖Runnable接口中的run方法.
3):我们在run方法中编写需要执行的操作---->run方法里的,线程执行体.
4):在main方法(线程)中,创建线程对象,并启动线程.
        创建线程类对象: Thread t = new Thread(new A());
        调用线程对象的start方法: t.start();

1.2.2需求:使用两个线程实现边听歌边打游戏

实现代码:

//音乐线程
public class MusicThread {
public static void main(String[] args) {
//创建游戏线程对象
Thread game = new Thread(new Game());
//启动游戏线程
game.start();
while(true){
System.out.println(Thread.currentThread().getName()+"听音乐!");
}
}
}


//游戏
class Game implements Runnable{
@Override
public void run() {
while (true) {
System.out.println(Thread.currentThread().getName()+"打游戏!");
}
}
}

1.2.3 继承方式和实现方式的区别

  • 1)继承方式是一个类继承了Thread后成为线程类的子类,实现方式是一个类实现Runnable接口,但是这个类不是线程类,因为该类没有start等方法.
  • 2)启动的时候继承方式直接调用自己的start方法,实现方式是借助了Thread中的start方法启动的,自身没有start方法
  • 3)继承方式调用的run方法是通过方法覆盖,通过继承方式实现的,运行的时候先找子类,没有最后才运行父类的run方法.实现方式是执行Thread的run方法,而Thread中的run方法调用了实现类中的run方法,使用过组合关系的方法调用实现的.

1.3实现 Callable 接口

1.3.1使用Callable和FutureTask创建线程的实现步骤:

  • 1)定义一个Callable接口的实现类
  • 2)创建Callable实现类对象传递给FutureTask构造器
  • 3)将FutureTask对象传递给Thread构造器
  • 4)Thread对象调用start方法启动线程
  • 5)通过FutureTask对象的get方法获取线程运行的结果

注意:
     Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。

使用场景:使用多线程计算结果并返回该结果。

1.3.2需求:使用2个线程异步计算1-1000,000内之和

实现代码:

public class CallableDemo {
public static void main(String[] args) throws Exception  {
//1.创建并启动线程
Callable<Integer> call1 = new CallableImpl(0, 50000);
Callable<Integer> call2 = new CallableImpl(50001, 100000);


FutureTask<Integer> f1 = new FutureTask<>(call1);
FutureTask<Integer> f2 = new FutureTask<>(call2);


new Thread(f1).start();
new Thread(f2).start();
//2.获取每一个线程的结果
int ret1 = f1.get();
int ret2 = f2.get();
int ret= ret1+ret2;
System.out.println(ret);
}
}
class CallableImpl implements Callable<Integer>{


private int min;
private int max;


public CallableImpl(int min, int max) {
this.min = min;
this.max = max;
}


@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = min; i <= max; i++) {
sum+=i;
}
return sum;
}
}

1.3.3Callable和Runnable的区别如下:

  1. Callable定义的方法是call,而Runnable定义的方法是run。

  2. Callable的call方法可以有返回值,而Runnable的run方法不能有返回值。

  3. Callable的call方法可抛出异常,而Runnable的run方法不能抛出异常。

注意:

    FutureTask为Runnable的实现类
    FutureTask可以视为一个闭锁(门闩),因为只有当线程运行完才会出现结果。

1.4使用线程池(Executor框架【后面详细讲解Executor框架】)

     线程池,顾名思义就是一个池子里面放了很多的线程,我们用就将线程从里面拿出来,使用完毕就放回去池子中。设计和数据库连接池相似,存在静态工厂方法用于创建各种线程池。

操作步骤:

1)使用Executors工具类中的静态工厂方法用于创建线程池
      newFixedThreadPool:创建可重用且固定线程数的线程池,
      newScheduledThreadPool:创建一个可延迟执行或定期执行的线程池
      newCachedThreadPool:创建可缓存的线程池
2)使用execute方法启动线程
3)使用shutdown方法等待提交的任务执行完成并后关闭线程。

代码演示如下:

public class Demo4 {
    public static void main(String[] args) {
        Executor executor = Executors.newFixedThreadPool(5);
        executor.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        });
        ((ExecutorService) executor).shutdown();
    }
}

1.5 Spring实现多线程

    在Spring3之后,Spring引入了对多线程的支持,如果你使用的版本在3.1以前,应该还是需要通过传统的方式来实现多线程的。从Spring3同时也是新增了Java的配置方式,而且Java配置方式也逐渐成为主流的Spring的配置方式。

代码演示如下:

导入的包:

<dependencies>
     <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter</artifactId>
          <version>2.1.0.RELEASE</version>
     </dependency>
</dependencies>

配置类:

@Configuration
@ComponentScan("cn.wolfcode")
@EnableAsync //允许使用异步任务
public class SpringConfig {}

服务类:

@Service
public class SpringService {
    @Async // 这里进行标注为异步任务,在执行此方法的时候,会单独开启线程来执行
    public void dowork1() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
    }
    @Async
    public void dowork2() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
    }
}


测试类:

public class SpringThreadDemo {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        SpringService bean = context.getBean(SpringService.class);
        bean.dowork1();
        bean.dowork2();
    }
}

注意:此时会出现一个DEBUG信息

image.png

在这里DEBUG信息不是什么错误,不会影响代码的正常运行,其实可以不用管的,但是为什么出现这个问题呢?

    Spring的定时任务调度器会通过BeanFactory.getBean的方法来尝试获取一个注册过的TaskExecutor对象来做任务调度,获取不到TaskExecutor对象再尝试找ScheduledExecutorService 对象,都找不到就报DEBUG信息。报错之后就找自己本身默认的scheduler定时器对象,这个举动其实是做一个提醒作用,所以如果没有强迫症可以不用管它。

1.5.1解决Spring使用多线程的报错信息

强迫症患者想要解决怎么办,三种方式:

1.在log4j文件中加入log4j.logger.org.springframework.scheduling = INFO(治标不治本)
2.在本配置文件或者配置类中设置一个bean
3.配置类实现AsyncConfigurer接口并覆盖其getAsyncExecutor方法

1.6 定时器

严格来说定时器(Timer)不是线程,他只是调度线程的一种工具,它里面封装了一个线程,所以我们可以使用定时器来使用线程。

image.png
image.png

操作步骤:

1)创建Timer 对象
2)调用schedule方法
3)传入TimerTask子类对象

代码演示如下:

Timer timer = new Timer();
timer.schedule(new TimerTask() {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }


    }
}, 100, 100);

想获取更多技术干货,请前往叩丁狼官网:http://www.wolfcode.cn/all_article.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • 进程和线程 进程 所有运行中的任务通常对应一个进程,当一个程序进入内存运行时,即变成一个进程.进程是处于运行过程中...
    胜浩_ae28阅读 5,084评论 0 23
  • 进程和线程 进程 所有运行中的任务通常对应一个进程,当一个程序进入内存运行时,即变成一个进程.进程是处于运行过程中...
    小徐andorid阅读 2,797评论 3 53
  • 下面是我自己收集整理的Java线程相关的面试题,可以用它来好好准备面试。 参考文档:-《Java核心技术 卷一》-...
    阿呆变Geek阅读 14,735评论 14 507
  • 在有薄雾的早晨 我打着哈欠推开古色古香的雕花窗户 吱的一声响 吓飞了一群肥嘟嘟的幼鸟 大片大片浓丽的月季肆意盛开 ...
    蜜糖陶阅读 368评论 0 2
  • 这是一篇文章,写我的大学室友,在一起相处了两年的人,遇到事情才知道她们有多么的心胸狭窄。 近来不知是因为什么事情,...
    _终归阅读 254评论 2 1