大数据必学Java基础(七十六):创建线程的三种方式

创建线程的三种方式

一、继承Thread类

在学习多线程之前,以前的代码是单线程的吗?

不是,以前也是有三个线程同时执行的。

现在我想自己制造多线程 --》 创建线程 ?

线程类 --》 线程对象

package com.lanson.test01;

/**
 * @author : Lansonli
 * 线程类叫:TestThread,不是说你名字中带线程单词你就具备多线程能力了(争抢资源能力)
 * 现在想要具备能力,继承一个类:Thread,具备了争抢资源的能力
 */
public class TestThread extends Thread{
    /*
    一会线程对象就要开始争抢资源了,这个线程要执行的任务到底是啥?这个任务你要放在方法中
    但是这个方法不能是随便写的一个方法,必须是重写Thread类中的run方法
    然后线程的任务/逻辑写在run方法中
     */
    @Override
    public void run() {
        //输出1-10
        for (int i = 1; i <= 10 ; i++) {
            System.out.println(i);
        }
    }
}
package com.lanson.test01;

/**
 * @author : Lansonli
 * 测试类
 */
public class Test {
    //这是main方法,程序的入口
    public static void main(String[] args) {
        //主线程中也要输出十个数:
        for (int i = 1; i <= 10 ; i++) {
            System.out.println("main1-----"+i);
        }

        //制造其他线程,要跟主线程争抢资源:
        //具体的线程对象:子线程
        TestThread tt = new TestThread();
        //tt.run();//调用run方法,想要执行线程中的任务 -->这个run方法不能直接调用,直接调用就会被当做一个普通方法
        //想要tt子线程真正起作用比如要启动线程:
        tt.start();//start()是Thread类中的方法

        //主线程中也要输出十个数:
        for (int i = 1; i <= 10 ; i++) {
            System.out.println("main2-----"+i);
        }
    }
}

运行结果:

设置读取线程名字

1、setName,getName方法来进行设置读取

package com.lanson.test01;

/**
 * @author : Lansonli
 * 线程类叫:TestThread,不是说你名字中带线程单词你就具备多线程能力了(争抢资源能力)
 * 现在想要具备能力,继承一个类:Thread,具备了争抢资源的能力
 */
public class TestThread extends Thread{
    /*
    一会线程对象就要开始争抢资源了,这个线程要执行的任务到底是啥?这个任务你要放在方法中
    但是这个方法不能是随便写的一个方法,必须是重写Thread类中的run方法
    然后线程的任务/逻辑写在run方法中
     */
    @Override
    public void run() {
        //输出1-10
        for (int i = 1; i <= 10 ; i++) {
            System.out.println(this.getName()+i);
        }
    }
}
package com.lanson.test01;

/**
 * @author : Lansonli
 * 测试类
 */
public class Test {
    //这是main方法,程序的入口
    public static void main(String[] args) {
        //给main方法这个主线程设置名字:
        //Thread.currentThread()作用获取当前正在执行的线程
        Thread.currentThread().setName("主线程");
        //主线程中也要输出十个数:
        for (int i = 1; i <= 10 ; i++) {
            System.out.println(Thread.currentThread().getName()+"1-------"+i);
        }

        //制造其他线程,要跟主线程争抢资源:
        //具体的线程对象:子线程
        TestThread tt = new TestThread();
        tt.setName("子线程");
        //tt.run();//调用run方法,想要执行线程中的任务 -->这个run方法不能直接调用,直接调用就会被当做一个普通方法
        //想要tt子线程真正起作用比如要启动线程:
        tt.start();//start()是Thread类中的方法

        //主线程中也要输出十个数:
        for (int i = 1; i <= 10 ; i++) {
            System.out.println(Thread.currentThread().getName()+"2-------"+i);
        }
    }
}

2、通过构造器设置名字

package com.lanson.test01;

/**
 * @author : Lansonli
 * 线程类叫:TestThread,不是说你名字中带线程单词你就具备多线程能力了(争抢资源能力)
 * 现在想要具备能力,继承一个类:Thread,具备了争抢资源的能力
 */
public class TestThread extends Thread{
    public TestThread(String name){
        super(name);//调用父类的有参构造器
    }
    /*
    一会线程对象就要开始争抢资源了,这个线程要执行的任务到底是啥?这个任务你要放在方法中
    但是这个方法不能是随便写的一个方法,必须是重写Thread类中的run方法
    然后线程的任务/逻辑写在run方法中
     */
    @Override
    public void run() {
        //输出1-10
        for (int i = 1; i <= 10 ; i++) {
            System.out.println(this.getName()+i);
        }
    }
}

习题:买火车票

1、原理:每个窗口都是一个线程对象

2、代码

package com.lanson.test02;

/**
 * @author : Lansonli
 */
public class BuyTicketThread extends Thread {
    public BuyTicketThread(String name){
        super(name);
    }
    //一共10张票:
    static int ticketNum = 10;//多个对象共享10张票
    //每个窗口都是一个线程对象:每个对象执行的代码放入run方法中
    @Override
    public void run() {
        //每个窗口后面有100个人在抢票:
        for (int i = 1; i <= 100 ; i++) {
            if(ticketNum > 0){//对票数进行判断,票数大于零我们才抢票
                System.out.println("我在"+this.getName()+"买到了从北京到哈尔滨的第" + ticketNum-- + "张车票");
            }
        }
    }
}
package com.lanson.test02;

/**
 * @author : Lansonli
 */
public class Test {
    public static void main(String[] args) {
        //多个窗口抢票:三个窗口三个线程对象:
        BuyTicketThread t1 = new BuyTicketThread("窗口1");
        t1.start();
        BuyTicketThread t2 = new BuyTicketThread("窗口2");
        t2.start();
        BuyTicketThread t3 = new BuyTicketThread("窗口3");
        t3.start();
    }
}

二、实现Runnable接口

代码:

package com.lanson.test03;

/**
 * @author : Lansonli
 * TestThread实现了这个接口,才会变成一个线程类
 */
public class TestThread implements Runnable{
    @Override
    public void run() {
        //输出1-10数字:
        for (int i = 1; i <= 10 ; i++) {
            System.out.println(Thread.currentThread().getName()+"----"+i);
        }
    }
}
package com.lanson.test03;

/**
 * @author : Lansonli
 */
public class Test {
    public static void main(String[] args) {
        //创建子线程对象:
        TestThread tt = new TestThread();
        Thread t = new Thread(tt,"子线程");
        t.start();

        //主线程里面也是打印1-10数字:
        for (int i = 1; i <= 10 ; i++) {
            System.out.println(Thread.currentThread().getName()+"---"+i);
        }
    }
}

运行结果:

习题:买火车票

代码

package com.lanson.test04;

/**
 * @author : Lansonli
 */
public class BuyTicketThread implements Runnable {
    int ticketNum = 10;
    @Override
    public void run() {
        for (int i = 1; i <= 100 ; i++) {
            if(ticketNum > 0){
                System.out.println("我在"+Thread.currentThread().getName()+"买到了北京到哈尔滨的第" + ticketNum-- + "张车票");
            }
        }
    }
}
package com.lanson.test04;

/**
 * @author : Lansonli
 */
public class Test {
    //这是main方法,程序的入口
    public static void main(String[] args) {
        //定义一个线程对象:
        BuyTicketThread t = new BuyTicketThread();
        //窗口1买票:
        Thread t1 = new Thread(t,"窗口1");
        t1.start();
        //窗口2买票:
        Thread t2 = new Thread(t,"窗口2");
        t2.start();
        //窗口3买票:
        Thread t3 = new Thread(t,"窗口3");
        t3.start();
    }
}

实际开发中,方式1 继承Thread类 还是 方式2 实现Runnable接口这种方式多呢?

方式2

  1. 方式1的话有 Java单继承的局限性,因为继承了Thread类,就不能再继承其它的类了
  2. 方式2的共享资源的能力也会强一些,不需要非得加个static来修饰

Thread类 Runnable接口 有联系吗?

三、实现Callable接口

对比第一种和第二种创建线程的方式发现,无论第一种继承Thread类的方式还是第二种实现Runnable接口的方式,都需要有一个run方法, 但是这个run方法有不足:

  • 没有返回值
  • 不能抛出异常

基于上面的两个不足,在JDK1.5以后出现了第三种创建线程的方式:

实现Callable接口

实现Callable接口好处:

(1)有返回值

(2)能抛出异常

缺点:线程创建比较麻烦

package com.lanson.test05;

import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * @author : Lansonli
 */
public class TestRandomNum implements Callable<Integer> {
    /*
    1.实现Callable接口,可以不带泛型,如果不带泛型,那么call方式的返回值就是Object类型
    2.如果带泛型,那么call的返回值就是泛型对应的类型
    3.从call方法看到:方法有返回值,可以跑出异常
     */
    @Override
    public Integer call() throws Exception {s
        return new Random().nextInt(10);//返回10以内的随机数
    }
}

class Test{
    //这是main方法,程序的入口
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //定义一个线程对象:
        TestRandomNum trn = new TestRandomNum();
        FutureTask ft = new FutureTask(trn);
        Thread t = new Thread(ft);
        t.start();
        //获取线程得到的返回值:
        Object obj = ft.get();
        System.out.println(obj);
    }
}

版权声明:
作者:Lansonli
链接:https://jkboy.com/archives/9432.html
来源:随风的博客
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
海报
大数据必学Java基础(七十六):创建线程的三种方式
实际开发中,方式1 继承Thread类 还是 方式2 实现Runnable接口这种方式多呢?
<<上一篇
下一篇>>