zoukankan      html  css  js  c++  java
  • 这几种实现线程的方法你一定要知道,月薪20k以上的面试都会问到

    实现线程的三种方式总结

    最近有看到Java线程的实现相关问题,在此对线程实现方式做一个小小的总结,当做笔记,便于日后查看。

    平时常用的线程方式有三种:

    (1)、继承Thread类,并重写其run()方法。

    (2)、实现Runnable接口,并实现其run()方法。

    (3)、实现Callable接口,并实现其call()方法。

    一、继承Thread类

    Thread 类中创建线程最重要的两个方法为:

    	public void start();
    	public void run();
    

    采用 Thread 类创建线程,用户只需要继承 Thread,覆盖 Thread 中的 run 方法,父类 Thread 中的 run 方法没有抛出异常,那么子类也不能抛出异常,最后采用 start 启动线程即可。

    【示例代码1】不使用线程

    public class ThreadTest01 {
    	public static void main(String[] args) {
    		Processor p = new Processor();
    		p.run();
    		method1();
    	}
    	
    	private static void method1() {
    		System.out.println("--------method1()----------");
    	}
    }
    class Processor {
    	public void run() {
    		for (int i=0; i<10; i++) {
    			System.out.println(i);
    		}
    	}
    }
    

    【执行结果】

    0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    --------method1()----------
    

    以上顺序输出相应的结果(属于串行) , 也就是 run 方法完全执行完成后,才执行 method1 方法, 也就是 method1 必须等待前面的方法返回才可以得到执行,这是一种“同步编程模型”。

    这样执行存在什么样的弊端呢?

    按照顺序执行,这就极大的降低了程序的执行效率。无法同时执行多个代码片段,这也是多线程并发所要达到的目的。

    【示例代码2】使用线程

    public class ThreadTest02 {
    	public static void main(String[] args) {
    		Processor p = new Processor();
    		/*
    			如果是手动调用该方法,
    			则并不能采用 run 来启动一个场景(线程),
    			run 就是一个普通方法调用。
    		*/
    		//p.run();
    		
    		/*
    			采用 start 启动线程,不是直接调用 run,
    			start 不是马上执行线程,而是使线程进入就绪状态
    			线程的真正执行是由 Java 的线程调度机制完成的。
    		*/
    		p.start();
    		
    		//线程只能启动一次,无法启动多次
    		//p.start();
    		
    		method1();
    	}
    	
    	private static void method1() {
    		System.out.println("--------method1()----------");
    	}
    }
    class Processor extends Thread {
    	/*
    		覆盖 Thread 中的 run 方法,该方法没有异常
    		该方法是由 java 线程调度机制调用的,因此
    		我们不应该手动调用该方法
    	*/
    	public void run() {
    		for (int i=0; i<10; i++) {
    			System.out.println(i);
    		}
    	}
    }
    

    【执行结果】

    --------method1()----------
    0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    

    通过输出结果大家会看到,没有按照顺序执行,而在输出数字的同时执行了 method1()方法,如果从效率上看,采用多线程的示例要快些,因为我们可以看作他是同时执行的, mthod1()方法没有等待前面的操作完成才执行, 这叫“异步编程模型”。

    那么,为什么会是这样的执行结果呢?

    这就涉及到Java线程的调度机制了,该程序包含两个线程一个是主线程也就是main线程,另外一个是用户创建的p线程,当类加载完成后,主线程启动,开始执行main方法栈帧,按照代码自上而下的执行顺序,先创建Processor的实例化对象p,接着是执行p.start();启动p线程,这时method1();方法还没有执行,此时两个线程均已经启动,按照Java线程调度的规则,两个线程开始抢夺执行程序的时间片(即CPU的执行权),注意,这种抢夺是随机的,也就是说,不一定输出结果就是method1方法先执行,for循环语句后执行。可以多执行几次即可看到不一样的执行结果。

    二、实现 Runnable 接口

    其实 Thread 对象本身就实现了 Runnable 接口,但一般建议直接使用 Runnable 接口来写多线程程序,因为接口会比类带来更多的好处(面向接口编程的原则)。

    【示例代码3】

    public class ThreadTest03 {
    	public static void main(String[] args) {
    		//Processor r1 = new Processor();
    		/*
    			使用多态机制父类型引用指向子类型对象,
    			因为这样可以调用Runnable接口的方法
    		*/
    		Runnable r1 = new Processor();
    		
    		//不能直接调用 run方法,原因见上文
    		//p.run();
    		
    		//创建线程对象,并将r1对象作为参数传入(Thread的构造方法)
    		Thread t1 = new Thread(r1);
    		
    		//启动线程
    		t1.start();
    		
    		method1();
    	}
    	
    	private static void method1() {
    		System.out.println("--------method1()----------");
    	}
    }
    //实现 Runnable 接口
    class Processor implements Runnable {
    	//实现 Runnable 中的 run 方法
    	public void run() {
    		for (int i=0; i<10; i++) {
    			System.out.println(i);
    		}
    	}
    }
    

    【执行结果】

    --------method1()----------
    0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    

    结果分析见上,原因是一样的,只不过是换了一种方式实现线程而已。

    三、实现Callable接口

    观察上文两种线程的执行方式,存在什么缺点。显然,以上两种线程的执行un方法时是没有返回值的,而实际上也会存在需要得到线程执行的返回结果的情况,那么怎么办呢?这时就可以考虑使用第三种线程的实现方式。

    优点:可以获取到线程的执行结果。
    缺点:效率比较低,在获取t线程执行结果的时候,当前线程受阻塞,效率较低。

    【示例代码4-1】使用匿名内部类

    import java.util.concurrent.Callable;
    import java.util.concurrent.FutureTask;
    
    public class ThreadTest04 {
        public static void main(String[] args) throws Exception {
            // 第一步:创建一个“未来任务类”对象。
            // 参数非常重要,需要给一个Callable接口实现类对象。
            FutureTask task = new FutureTask(new Callable() {
                @Override
                public Object call() throws Exception { // call()方法就相当于run方法。只不过这个有返回值
                    // 线程执行一个任务,执行之后可能会有一个执行结果
                    // 模拟执行
                    System.out.println("call method begin");
                    Thread.sleep(1000 * 10);//当前线程睡眠10秒
                    System.out.println("call method end!");
                    int a = 100;
                    int b = 200;
                    return a + b; //自动装箱(300结果变成Integer)
                }
            });
    
            // 创建线程对象
            Thread t = new Thread(task);
    
            // 启动线程
            t.start();
    
            // 这里是main方法,这是在主线程中。
            // 在主线程中,怎么获取t线程的返回结果?
            // get()方法的执行会导致“当前线程阻塞”
            Object obj = task.get();
            System.out.println("线程执行结果:" + obj);
    
            // main方法这里的程序要想执行必须等待get()方法的结束
            // 而get()方法可能需要很久。因为get()方法是为了拿另一个线程的执行结果
            // 另一个线程执行是需要时间的。
            System.out.println("hello world!");
        }
    }
    

    【执行结果】

    call method begin
    call method end!
    线程执行结果:300
    hello world!
    

    【示例代码4-2】不使用匿名内部类

    import java.util.concurrent.Callable;
    import java.util.concurrent.FutureTask;
    
    public class ThreadTest01 {
        public static void main(String[] args) throws Exception {
            //创建Callable接口的实现类的实例化对象
            CallableImpl callable = new CallableImpl();
    
            // 第一步:创建一个“未来任务类”对象。
            // 参数非常重要,需要给一个Callable接口实现类对象。
            FutureTask task = new FutureTask(callable);
    
            // 创建线程对象
            Thread t = new Thread(task);
    
            // 启动线程
            t.start();
    
            // 这里是main方法,这是在主线程中。
            // 在主线程中,怎么获取t线程的返回结果?
            // get()方法的执行会导致“当前线程阻塞”
            Object obj = task.get();
            System.out.println("线程执行结果:" + obj);
    
            // main方法这里的程序要想执行必须等待get()方法的结束
            // 而get()方法可能需要很久。因为get()方法是为了拿另一个线程的执行结果
            // 另一个线程执行是需要时间的。
            System.out.println("hello world!");
        }
    }
    //实现Callable接口
    class CallableImpl implements Callable{
        @Override
        public Object call() throws Exception { // call()方法就相当于run方法。只不过这个有返回值
            // 线程执行一个任务,执行之后可能会有一个执行结果
            // 模拟执行
            System.out.println("call method begin");
            Thread.sleep(1000 * 10);//当前线程睡眠10秒
            System.out.println("call method end!");
            int a = 100;
            int b = 200;
            return a + b; //自动装箱(300结果变成Integer)
        }
    }
    

    【执行结果】

    call method begin
    call method end!
    线程执行结果:300
    hello world!
    

    最后

    感谢你看到这里,看完有什么的不懂的可以在评论区问我,觉得文章对你有帮助的话记得给我点个赞,每天都会分享java相关技术文章或行业资讯,欢迎大家关注和转发文章!

  • 相关阅读:
    5.2 二叉树的创建和遍历
    5 树
    Word2013发布博客到博客园的详细方法
    synergy--共享你的键鼠
    二十三 Python分布式爬虫打造搜索引擎Scrapy精讲—craw母版l创建自动爬虫文件—以及 scrapy item loader机制
    二十二 Python分布式爬虫打造搜索引擎Scrapy精讲—scrapy模拟登陆和知乎倒立文字验证码识别
    二十一 Python分布式爬虫打造搜索引擎Scrapy精讲—爬虫数据保存
    二十 Python分布式爬虫打造搜索引擎Scrapy精讲—编写spiders爬虫文件循环抓取内容—meta属性返回指定值给回调函数—Scrapy内置图片下载器
    十九 Python分布式爬虫打造搜索引擎Scrapy精讲—css选择器
    十八 Python分布式爬虫打造搜索引擎Scrapy精讲—Scrapy启动文件的配置—xpath表达式
  • 原文地址:https://www.cnblogs.com/lwh1019/p/13418758.html
Copyright © 2011-2022 走看看