乐趣区

关于线程:多线程线程初体验

上节讲了下线程和过程的基础知识,然而对于 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 running
thread2 is running
thread2 yield
thread1 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 更技术文章!!!

退出移动版