上节讲了下线程和过程的基础知识,然而对于Java来说,可能探讨线程的工夫会更多些,所以接下来的一系列文章都是着重在探讨线程。

创立线程

创立的线程的形式是陈词滥调也是面试中喜爱问的问题之一了,网上的说法七嘴八舌,说什么实现Runnable接口和实现Callable接口是同一种类型,这种说法也不是说谬误,只不过须要看站在哪个角度看。然而这种细节其实没有必要太在意,不要钻牛角尖。

实现Runnable接口

实现Runnable接口,而后重写run() 办法,该办法定义了线程的工作形式和工作内容。

public class ImplementsRunnable implements Runnable {    @Override    public void run() {        System.out.println(Thread.currentThread().getName() + "------Runnable线程工作中。。。");    }    public static void main(String[] args) {        for (int i = 0; i < 50; i++) {            new Thread(new ImplementsRunnable()).start();        }    }}

在main办法中,开启了50个线程运行,开启线程其实就是新建了一个Thread,而后把实现Runnable接口的类作为参数传进去,当初咱们来看看运行的后果

能够看到尽管咱们是依照程序来新建线程的,然而线程的先后执行程序是由CPU来管制的,能够说是不可控的,也正是这样能力阐明了多线程在运行。

实现Callable接口

实现了接口后须要重写的是call() 办法

public class ImplementsCallable implements Callable {    @Override    public Object call() {        System.out.println(Thread.currentThread().getName() + "--------callable线程工作中");        return "实现callable,有返回值";    }    public static void main(String[] args) throws ExecutionException, InterruptedException {        for (int i = 0; i < 50; i++) {            ImplementsCallable callable = new ImplementsCallable();            FutureTask<String> task = new FutureTask<String>(callable);            new Thread(task).start();            System.out.println(task.get());        }    }}

值得注意的是,在Thread类中的构造函数中,并没有参数为Callable的重载构造函数,基本上都是Runnable

而借助了FutureTask 这个类算是线程工作原理中比拟重要的一个类,当前可能会专门出一篇文章来学习,FutureTask 是实现了RunnableFuture 接口,而该接口又是继承了RunnableFuture

与实现Runnable接口方式最大的不同就是,Callable接口有返回值 ,这个返回值应用的场景是什么呢,比方在http调用中,须要返回某个后果,在多线程应用的状况下就会用到Callable和Future来实现。如何获取返回值呢,就是应用FutureTask中的get() 办法,让咱们来看看运行后果

这里呈现了一个有意思的问题,当我把第14行代码正文后 运行,呈现以下后果,线程是凌乱无序的,也正是期待的后果。

然而,当我保留第14行代码屡次运行 ,又会呈现以下后果,线程居然变得有序了,如果有晓得为什么的小伙伴能够留言呀

继承Thread类

继承Thread类后,Idea甚至没有揭示须要重写,须要手动去重写 run()办法

整体代码如下

public class ExtendsThread extends Thread{    @Override    public void run() {        System.out.println(Thread.currentThread().getName() + "--------继承Thread的线程工作中");    }    public static void main(String[] args) {        for (int i = 0; i < 50; i++) {            new ExtendsThread().start();        }    }}

代码比较简单,咱们来看下后果,也是和预期一样

两种形式优先选择实现接口,因为Java不反对多继承,继承了Thread类就不能继承其余类,然而能够实现多个接口。而且就性能开销方面来看,继承整个Thread类显得比拟臃肿。

线程罕用办法

线程无关的办法有比拟多种,这里着重讲下4种罕用的办法。

start

在上述例子种能够发现每次开启一个线程根本都是应用了start() 办法来开启,那它是run() 办法的区别是什么呢

public class CommonMethod {    public static void main(String[] args) throws InterruptedException {           startRunExample();    }    //start,run    public static void startRunExample() {        new MyThread().start();        new MyThread().run();    }}class MyThread extends Thread {    @Override    public void run() {        System.out.println(Thread.currentThread().getName() + " is running");    }}

新建了一个类,而后创立一个外部类继承了Thread,调用了start()run() 两种办法,在主函数外面再调用封装的办法,来看下后果如何。

能够看到一个线程名字是主线程,一个是子线程,所以start() 办法是开启了一个线程,而后这个线程执行了run() 办法的内容。然而如果间接用run() 办法呢,就是主线程单纯地执行run() 办法的内容,并没有开启新的线程。

sleep

sleep是让以后线程睡眠,让出cpu给其它线程执行。

public class CommonMethod {    public static void main(String[] args) throws InterruptedException {//        startRunExample();        sleepExample();//        yieldExample();//        waitExample();            }    //省略start,run        //sleep    public static void sleepExample() throws InterruptedException {        new MyThread().start();        Thread.sleep(3000);        System.out.println(Thread.currentThread().getName() + " is running");    }}class MyThread extends Thread {    @Override    public void run() {        System.out.println(Thread.currentThread().getName() + " is running");    }}

比方我开启了一个新的线程,然而我让主线程休眠3s再运行,后果应该先是Thread-0 is running 而后3s后输入main is running

为了做个比照,我把sleep代码给正文掉,再来看多几遍后果

能够看到两个线程的后果简直是同时进去,至于哪个前哪个后在这个例子里不是咱们能管制的。

yield

yield是指程序员倡议计算机把以后线程占用的CPU让给其它线程,然而CPU鸟不鸟咱们,又是另外一回事了,艰深地来说就是把线程从Running状态转换成Runnable状态。

再次强调是倡议计算机把以后线程挂起,执行其它线程,然而做不做是计算机的事件。

再次新建一个外部类YieldThread

public class CommonMethod {    public static void main(String[] args) throws InterruptedException {//        startRunExample();//        sleepExample();        yieldExample();//        waitExample();            }    //省略start,run        //省略sleep        //yield    public static void yieldExample() throws InterruptedException {        YieldThread yieldThread = new YieldThread();        Thread thread = new Thread(yieldThread, "thread1");        Thread thread1 = new Thread(yieldThread, "thread2");        thread.start();        thread1.start();    }}class YieldThread extends Thread {    @Override    public void run() {        for (int i = 0; i < 10; i++) {            try {                Thread.sleep(10);            } catch (InterruptedException e) {                e.printStackTrace();            }            System.out.println(Thread.currentThread().getName() + " is running " + i);            if (Thread.currentThread().getName().equals("thread1")) {                Thread.yield();            }            System.out.println(Thread.currentThread().getName() + " yield " + i);        }    }}

其实这个例子不太精确,然而可能勉强看,整个run的逻辑就是每个线程跑10遍,每遍输入一个running,一个yield。然而当咱们加了Thread.yield() 之后,预期后果是

thread1 is runningthread2 is runningthread2 yieldthread1 yield

就是thread1执行了running语句后,把cpu使用权交进去,cpu抉择了执行thread2的一套逻辑后thread1再拿到cpu工夫片来执行thread1 yield语句

接着来看下后果是否能和预期一样

能够看到只有局部可能和预期后果一样,当咱们去掉了Thread.yield() 这行代码后呢

没错,你会发现偶然也有这种状况产生,然而没有下面存在的频繁。是因为这两个线程有可能是并行的,而不是并发(交替运行的),所以两者同时执行了running语句,而后线程2接着执行了yield,线程1执行了yield。

这里说得不肯定精确,所以说是不太精确的例子,如果有更好的了解和例子能够留言呀!!!

wait

相比于后面的yield而言,接下来的例子可控性更强一点,前者是倡议,后者能够对应地说成强制。是把线程从Running状态转变成Block状态,间接挂起线程,没有外力唤醒前不会执行。

public class CommonMethod {    public static void main(String[] args) throws InterruptedException {//        startRunExample();//        sleepExample();//        yieldExample();        waitExample();            }    //省略start,run        //省略sleep        //省略yield        //wait    public static void waitExample() {        WaitThread waitThread = new WaitThread();        Thread thread1 = new Thread(waitThread, "thread1");        Thread thread2 = new Thread(waitThread, "thread2");        thread1.start();        thread2.start();    }    }class WaitThread extends Thread {    @Override    public void run() {        for (int i = 0; i < 10; i++) {            try {                Thread.sleep(10);            } catch (InterruptedException e) {                e.printStackTrace();            }            System.out.println(Thread.currentThread().getName() + " is running " + i);            if (Thread.currentThread().getName().equals("thread1")) {                try {                    wait();                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }    }}

逻辑都差不多,只不过把Thread.yield() 换成了wait() ,失常来说是线程名为thread1的线程只有执行一次就不再执行了,让咱们来看下后果

和预期后果是一样的,并且还报错java.lang.IllegalMonitorStateException

创作不易,如果对你有帮忙,欢送点赞,珍藏和分享啦!

上面是集体公众号,有趣味的能够关注一下,说不定就是你的宝藏公众号哦,根本2,3天1更技术文章!!!