线程以及多线程开发

进程和线程

在学习线程以前,首先要理解什么是进程。打开你的任务管理器,导航栏第一个清清楚楚的写着进程,点进去会发现是许许多多的你在运行的程序,这就是一个进程。html

like this:java

现代操做系统均可以同时执行多个程序,这就是多任务。线程时创建在进程的基础上的,好比QQ音乐这个进程能够同时在执行播放、下载、传输等动做。这就叫多线程,每一个线程在执行不一样的功能。
在单核CPU系统中,也能够同时运行多个程序,程序运行是抢占式的,QQ运行0.001S,chrome运行0.01s,这个时间人是感知不出来的,咱们就会以为在同时执行。因此为了提升效率,如今的手机、电脑都是很是多核的。算法

进程和线程的关系就是:一个进程能够包含一个或多个线程,但至少会有一个线程。chrome

操做系统调度的最小任务单位其实不是进程,而是线程。api

进程 VS 线程

进程和线程是包含关系,可是多任务既能够由多进程实现,也能够由线程实现,还能够混合多进程+多线程。安全

和多线程相比,多进程的缺点是:多线程

  • 建立进程比建立线程开销大不少,尤为是在Windows上
  • 进程间通讯比线程要慢,由于线程见通讯就是读写同一个变量,速度很快

多进程的优势:ide

  • 多进程稳定性比多线程高,由于在多进程状况下,一个进程的崩溃不会影响其余进程,任何一个线程崩溃会致使整个进程崩溃。

建立线程

1. Thread

例:函数

public class MyThread extends Thread {  // 线程的主体类
    @Override
    public void run(){  
       System.out.println("Thread is starting");
    }
}

上面的MyThread类继承Thread,覆写了run方法。一个类只要继承了此类,就表示这个类为线程的主体类。run()是线程的主方法,多线程要执行的方法都在这写。
可是run()方法是不能被直接调用的,这牵涉到系统的资源调度问题,想要启动多线程,必须用start()完成。学习

调用线程
public class ThreadDemo {
    public static void main(String[] args) {
     new MyThread().start();
        // 启动新线程
}

java语言内置了多线程支持。当Java程序启动的时候实际上是启动了一个JVM进程。JVM启动主线程来执行main()方法,在main()方法中能够启动其余线程。

start() 只能由 Thread类型实例调用,表示启动一个线程。

执行结果
"C:\Program Files\Java\jdk1.8.0_221\bin\java.exe"

Thread is starting

因而可知,线程建立成功

那么建立一个多线程呢?

建立多线程
// 多线程主体类
public class MyThread extends Thread {
    private String title;
    public MyThread(){
    }
    MyThread(String title){
        this.title = title;
    }
    @Override
    public void run(){
        for (int i = 0; i<10;i++){
            System.out.println(this.title +  "is starting");
            System.out.println(Thread.currentThread().getName());
        }
    }
}



 public static void main(String[] args) {
        new Thread(new MyThread("A"),"线程1").start();
        new Thread(new MyThread("C"),"线程2").start();
        new Thread(new MyThread("B")).start();
    }

执行结果:

这个结果中有几个关注点:

  1. 多线程的执行是无序的,不可控的
  2. 调用的是start()方法,但执行的是run()方法

咱们来看一下源码,分析一下

public synchronized void start() {
      
        if (threadStatus != 0)  // 判断线程状态
        
        // 每个线程的类的对象只容许启动一次,重复启动就会抛出这个异常,由run()抛出
            throw new IllegalThreadStateException();
            
        group.add(this);

        boolean started = false;
        try {
        // 调用此方法
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
            
            }
        }
    }

    private native void start0();
   
   // 注释部分被我删掉了,太长了

咱们发现start()方法调用的是start0(),而start0()并无实现,还被native修饰,那么native是啥呢?

在Java程序执行的过程当中考虑到对于不一样层次的开发者需求,支持了本地的操做系统函数调用。这项技术被称为JNI(Java Native Interface),但在Java开发过程当中并不推荐这样使用。利用这项技术,能够利用操做系统提供的的底层函数,操做一些特殊的处理。

不一样的系统在进行资源调度的时候由本身的一套算法,要想调用start()方法启动线程,就要实现start0(),这时候JVM就会根据不一样的操做系统来具体实现start0(),总结就是一切的一切都是跨平台带来的。

这也规定了,启动多线程只有一种方案,调用Thread类中的start()方法.

  1. Thread 构造函数能够接收一个实例对象和线程的名字参数。

Thread.currentThread().getName() 就表明了获取当前线程的名字。

在返回值中还出现了"Thread-3",这是因为Thread会自动给没有命名的线程分配一个不会重复的名字。

这种方式启动多线程当然没错,但存在单继承的隐患。下面就给出另外一种模式。

2. Runnable

首先分别来看一下Thread类的实现

public
class Thread implements Runnable {}

原来Thread是继承了Runnable

再看一下Runnable接口

@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

再次惊讶,原来这个run方法也是从这里继承的。

那就清楚了,来试一下吧。

// 只须要实现 Runnable,重写run()便可,其余丝毫未变
public class MyThread implements Runnable {
    private String title;
    public MyThread(){
    }
    MyThread(String title){
        this.title = title;
    }
    @Override
    public void run(){
        for (int i = 0; i<10;i++){
            System.out.println(this.title +  "is starting");
            System.out.println(Thread.currentThread().getName());
        }
    }
}




public class ThreadDemo {
    public static void main(String[] args) {
        new Thread(new MyThread("A线程"),"线程1").start();
        new Thread(new MyThread("C线程"),"线程2").start();
        new Thread(new MyThread("B线程")).start();
       // lambda 语法实现
//        new Thread(() -> {
//           System.out.println("启动新的线程");
//       }).start();


    }
}

结果:

彻底一致。

在之后的多线程设计实现,优先使用Runnable接口实现。

还没完,咱们依靠Runnable接口实现的时候,会发现有一个缺陷,就是没有返回值,那有没有带返回值的实现方式呢?有!继续看

3. Callable

Java1.5以后为了解决run()方法没有返回值的问题,引入了新的线程实现java.util.concurrent.Callable接口.

咱们看一下Oracle的api文档:

能够看到Callable定义的时候利用了一个泛型,表明了返回数据的类型,避免了向下转型带来的安全隐患

了解向下转型能够看个人另外一篇文章:http://www.javashuo.com/article/p-vdtvwwnj-ce.html

可是问题又来了,咱们上面已经说过了,要想启动多线程,必须使用Thread类提供的
start() 方法调用Runnable接口的 run() 方法,但是如今 Callable中并无run() 方法,那怎么办呢?

再来找到一个FutureTask类:

public class FutureTask<V>
extends Object
implements RunnableFuture<V>

构造方法:

它的构造方法能够接收一个Callable类型参数

它又继承了RunnableFuture<V>,那就继续往上找

public interface RunnableFuture<V>
extends Runnable, Future<V>

出现了,它出现了,Runnable咱们知道了,是没有返回值的,如今看看Future<V>是个啥

它有一个get()方法能够获得一个泛型返回值。

OK,如今咱们就能够梳理一下找到的这些东西怎么个关系:

具体实现

public class CallableThread implements Callable<String> {  // 继承实现Callable<V>
    // 覆写call()方法
    @Override
    public String call() throws Exception{
        for (int i = 0;i<10;i++){
            System.out.println("*********线程执行、i="+ i);
        }
        return "线程执行完毕";
    }
}



// 调用
        FutureTask<String> task = new FutureTask<>(new CallableThread());
        new Thread(task).start();
        System.out.println("【线程返回数据】" + task.get());

结果:

为了获得一个返回值可真不容易,核心思想仍是实例化一个Thread对象,可经过其构造方法接收一个Rannable类型参数,调用start()启动线程.

总结:基原本说就这三种建立多线程模式,根据场景使用。

**纯属我的理解,但愿指正错误,共同交流。

相关文章
相关标签/搜索