当前位置: 移动技术网 > IT编程>开发语言>Java > 并发基础之实现多线线程的正确姿势

并发基础之实现多线线程的正确姿势

2019年11月05日  | 移动技术网IT编程  | 我要评论

实现多线程的方式到底是几种?

针对于java多线程的实现方式,不论是网上还是各种书籍都有同的观点,有的说的两种,有的说有四种,随便百度一下我们就能看到很多种答案:

image.md.png

那究竟是几种呢?下面我们来查找一下java的api文档:https://docs.oracle.com/javase/9/docs/api/java/lang/thread.html

从文档中我们可以清晰的看到,实现多线程有两种方式,一种是将一个类声明为的子类thread,还有一种是声明一个实现runnable接口的类。

imageab3074b7eba1911e.md.png

两种实现方式对比

实现runnable接口:

/**
 * @author chen
 * @description 使用runnable接口实现多线程
 * @create 2019-11-04 21:38
 */
public class runnablestyle implements runnable{
    public static void main(string[] args) {
        thread thread = new thread(new runnablestyle());
        thread.start();
    }

    @override
    public void run() {
        system.out.println("使用runnable接口实现多线程");
    }
}

继承thread类:

/**
 * @author chen
 * @description 使用thread方式实现多线程
 * @create 2019-11-04 21:41
 */
public class threadstyle extends thread{
    public static void main(string[] args) {
        thread thread = new threadstyle();
        thread.start();
    }

    @override
    public void run() {
        system.out.println("使用thread类实现多线程");
    }
}

使用runable还是thread?

既然都可以实现多线程,那实际我们应该使用选择一种方式创建还是随便都可以呢?

答案是实现runable方式更好。

原因主要有以下几点:

1.从解耦的角度来说,多线程执行的任务(也就是run方法的内容)应该与thread类是解耦的,

而不是都写在一起。

2.如果使用继承thread类来实现多线程,我们每次想新创建一个任务只能新建一个独立的线程,而这样的损耗是比较大的,需要去创建,销毁等。而使用runnable接口的话后续我们可以使用线程池,这样可以减少新建线程带来的损耗。

3.因为java是单继承,一旦继承了thread就不能在去继承其他的类,限制了程序的可扩展性。

runable和thread创建线程区别?

使用runable的方式创建,我们是使用了一个thread类一个带参的构造方法,使用thread方法创建,是使用了thread类的一个无参的构造方法,这两种方法都重写了run方法,接下来我们来看一看thread类中的run方法是如何实现的:

    /* what will be run. */
    private runnable target;

    /**
     * if this thread was constructed using a separate
     * <code>runnable</code> run object, then that
     * <code>runnable</code> object's <code>run</code> method is called;
     * otherwise, this method does nothing and returns.
     * <p>
     * subclasses of <code>thread</code> should override this method.
     *
     * @see     #start()
     * @see     #stop()
     * @see     #thread(threadgroup, runnable, string)
     */
    @override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

以上是一部分thread类的源码,我们可以看到执行run方法的时候,会先判断是否传入runable接口的实现类,如果传入了,就执行接口实现类中的run方法,如果没传入,则会直接执行子类重写的run方法,所以本质上来说,这两种方法本质上都是执行了run方法,只不过run方法的来源不同。

错误观点

线程池创建线程也算是一种新建线程的方式

/**
 * @author chen
 * @description 使用线程池创建线程
 * @create 2019-11-05 9:40
 */
public class threadpoolstyle {
    public static void main(string[] args) {
        executorservice executorservice = executors.newcachedthreadpool();
        for (int i = 0; i <1000 ; i++) {
            executorservice.submit(new task());
        }
    }

    static class  task implements runnable{
        @override
        public void run() {
            try {
                thread.sleep(500);
            } catch (interruptedexception e) {
                e.printstacktrace();
            }
            system.out.println(thread.currentthread().getname());
        }
    }
}

下面简单看一下excutors内部是如何创建线程的:

可以看到在下面的代码中线程池的内部新建线程也是通过传入一个runnable,然后在new thread来创建的,所以虽然外表看起来创建的方式不太一样,但是原理都是通过runnable接口来实现的多线程。

    /**
     * the default thread factory
     */
    static class defaultthreadfactory implements threadfactory {
        private static final atomicinteger poolnumber = new atomicinteger(1);
        private final threadgroup group;
        private final atomicinteger threadnumber = new atomicinteger(1);
        private final string nameprefix;

        defaultthreadfactory() {
            securitymanager s = system.getsecuritymanager();
            group = (s != null) ? s.getthreadgroup() :
                                  thread.currentthread().getthreadgroup();
            nameprefix = "pool-" +
                          poolnumber.getandincrement() +
                         "-thread-";
        }

        public thread newthread(runnable r) {
            thread t = new thread(group, r,
                                  nameprefix + threadnumber.getandincrement(),
                                  0);
            if (t.isdaemon())
                t.setdaemon(false);
            if (t.getpriority() != thread.norm_priority)
                t.setpriority(thread.norm_priority);
            return t;
        }
    }

通过callable和futuretask创建线程也算是一种新建线程的方式

通过callablefuture

/**
 * @author chen
 * @description 使用callable、future以及futuretask
 * @create 2019-11-05 10:03
 */
public class futuretaskstyle {
    public static void main(string[] args) throws executionexception, interruptedexception {
        executorservice executorservice = executors.newcachedthreadpool();
        future<string> future = executorservice.submit(new callable<string>() {
            @override
            public string call() throws exception {
                thread.sleep(500);
                return "future result";
            }
        });
        system.out.println(system.currenttimemillis());
        system.out.println(future.get());
        system.out.println(system.currenttimemillis());
    }
}

通过callablefuturetask

/**
 * @author chen
 * @description 使用callable、future以及futuretask
 * @create 2019-11-05 10:03
 */
public class futuretaskstyle {
    public static void main(string[] args) throws executionexception, interruptedexception {
        executorservice executorservice = executors.newcachedthreadpool();
        futuretask<string> futuretask = new futuretask<string>(new callable<string>() {
            @override
            public string call() throws exception {
                thread.sleep(500);
                return "future result";
            }
        });
        new thread(futuretask).start();
        system.out.println(system.currenttimemillis());
        system.out.println(futuretask.get());
        system.out.println(system.currenttimemillis());
    }
}

使用ctrl+alt+u查看futuretask的继承关系图:可以看到runablefuture继承了futurerunnable接口,而futuretask又实现了runablefuture,所以futuretask也间接的实现了runable接口。所以说此种方式和使用runnable实现的方式原理是相同的。

image7f03cb1928004065.md.png

通过定时器

/**
 * @author chen
 * @description 使用定时器创建新的线程
 * @create 2019-11-05 10:26
 */
public class timmerstyle {
    public static void main(string[] args) {
        timer timer = new timer();
        timer.scheduleatfixedrate(new timertask() {
            @override
            public void run() {
                system.out.println(thread.currentthread().getname());
            }
        },1000,1000);

    }
}

看看timertask也是实现了runnable接口,本质上也是使用runnable接口方式创建。

通过匿名内部类或lambda表达式

/**
 * @author chen
 * @description 使用匿名内部类 和lamada表达式实现多线程
 * @create 2019-11-05 10:36
 */
public class anonymousinnerclassstyle {
    public static void main(string[] args) {
        //匿名内部类
        new thread(new runnable() {
            @override
            public void run() {
                system.out.println(thread.currentthread().getname());
            }
        }).start();

        //lambda 表达式
        new thread(()-> system.out.println(thread.currentthread().getname())).start();

    }
}

这种形式只是语法改变了一下,其实匿名内部类和lambda 表达式实现的效果都是一样的,只是更换了一种写法。

总结

实现多线程到底有几种方法?从不同角度看,会有不同的答案,如果从实现多线程的本质上看,一般有两种,一种是实现runnable接口,一种是继承thread类;而从代码实现层面看,会有很多种,如线程池,futuretask匿名内部类和lambda表达式等。

其实看原理,本质上的两种实现方式也是一样的,都是调用了thread类的run方法,而run方法中有一个判断,如果传入的runnable不为空,就执行run方法本身的方法内的代码。

一般情况下我们会使用runable接口的方式新建线程,主要出于以下原因:

从程序耦合的角度来看,把要提交的任务代码和thread类中的代码分离开,实现低耦合;

从可拓展角度来看,因为java是单继承的,使用继承thead类的方式就无法在继承其他的基类,降低了程序的课拓展性;

从资源消耗的角度看,使用runnable的方式我们可以使用线程池来管理线程,可以减少新建线程销毁线程等带来的资源损耗。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网