线程创建与启动

什么是线程?

可以这么说:操作系统可以同时执行多个任务,每个任务是进程,进程可以执行多个任务,每个任务就是线程。

线程的优势

1、进程之间不能共享内存,但线程之间共享内存非常容易。
2、系统创建进程是需要为该进程 重新分配系统资源,但是创建线程代价小得多。
3、java语言内置了多线程功能支持,而不是单纯的作为底层操作系统的调度方式,从而简化了java的多线程开发。

线程的创建

1、通过继承Thread类创建线程类

(a)定义Thread子类,并重写该类的run()方法。该run()方法的方法体就代表了线程需要完成的任务。因此把run()方法称为线程执行体
(b)创建Thread子类的实例,即创建了线程对象。
(c)调用线程的start()方法来启动线程。

示例:
package czz;

public class FirstThread extends Thread {
    private int i;
    //重写run()方法,run()方法的方法体就是线程的执行体
    @Override
    public void run(){
        for (;i<300;i++){
            //当线程继承Thread类时,直接使用this获取当前线程,Thread对象的getName()方法返回当前线程的名字
            //因此可以直接调用getName()方法返回当前现成的名字
            System.out.println(getName() + " " + i );
        }
    }

    public static void main(String[] args) {
        for (int i=0;i<300;i++){
            //调用Thread的currentThread()方法获取当前线程(本例为主线程)
            System.out.println(Thread.currentThread().getName() + " " + i);
            if (i==10){
                //创建并启动第一个线程
                Thread jdwa = new FirstThread();
                //为线程设置名字
                jdwa.setName("jdwa");
                jdwa.start();
                //创建并启动第一个线程
                Thread czz = new FirstThread();
                czz.setName("czz");
                czz.start();

            }
        }
    }
}


结果:

"D:\...\jdk1.8.0_05\bin\java.exe" "-javaagent:D:\...\IntelliJ IDEA 
...  ...
Files\Java\jdk1.8.0_05\jre\lib\rt.jar;E:\...\out\production\MyTest" czz.FirstThread
main 0
... ...
main 80
czz 0
... ...
czz 126
main 81
... ...
main 174
jdwa 0
jdwa  1
main 175
... ...
main 299
czz  127
... ...
czz  210
jdwa  2
... ...
jdwa  22
czz  211
... ...
czz 299
jdwa  23
... ...
jdwa  299

Process finished with exit code 0

有上面的示例可以看出,两个线程的循环变量不连续表明他们没有共享数据,因为每次创建线程都需要创建一个Thread对象,因此:
使用Thread类的方法创建线程时,多个线程之间无法共享线程类的实例变量。

2、实现Runnable接口创建线程类

(a)定义Runnable接口的实现类,并重写改接口的run()方法,该run()方法的方法体同样是该线程的执行体。
(b)创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。代码如下所示:

//创建Runnable实现类的实例
SecondThreadst = new SecondThread();
//以Runable实现类的实例作为Thread的target来创建Thread的对象,即线程对象。
new Thread(st);

也可以在创建对象时为该Thread对象指定一个名字:

new Thread(st,"threadName");

(c)调用线程对象的start()方法来启动该线程
示例:

package czz;

public class SecondThread implements Runnable {
    private int i;
    @Override
    public void run(){
        for (;i<300;i++){
            //当线程类实现Runnable接口时,获取当前线程需要Thread.currentThread()
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }

    public static void main(String[] args) {
        for (int i=0;i<12;i++){
            //调用Thread的currentThread()方法获取当前线程(本例为主线程)
            System.out.println(Thread.currentThread().getName() + " " + i);
            if (i==10){
                SecondThread target = new SecondThread();
                //通过New Thread(target,name)方法创建新线程
                new Thread(target,"jdwa").start();
                new Thread(target,"czz").start();
            }
        }
    }
}

3、使用Callable和Future创建线程

java5提供了Callable接口,这个接口类似于Runnable接口的增强版。Callable接口提供了一个call()方法作为线程的执行体。call()方法比run()方法功能更强大。
(a)call()方法可以有返回值
(b)call()方法可以声明抛出异常

但是,Callable接口不是Runnable接口的子接口,所以其实现类的实例并不能作为Thread的target。而且call()方法作为线程执行体,如何获取返回值呢?
java5提供了一个Future接口来代表Callable接口call()方法的返回值,并为Future接口提供了一个实现类FutureTask,该实现类不仅实现了Future接口,还实现了Runnable接口,因此可以作为Thread的target。接口有几个公共的方法:
(a)boolean cancle(boolean mayInterruptIfRunning):试图取消Future里关联的Callable任务
(b)V get():call()方法返回值
(c)V get(long timeout,TimeUnit unit):指定时间内call()没有返回值就抛出Timeoutxception
(d)boolean isCanclled():任务正常完成前被取消则返回true
(e)boolean isDone():任务已完成则返回true

创建并启动有返回值线程的步骤:
(a)创建Callable接口的实现类,并实现call()方法。在创建Callable接口的实现类(可以使用lambda表达式)
(b)使用FutureTask类来包装Callable对象,该实现类封装了call()方法返回值
(c)使用FutureTask对象作为Thread的target创建并启动新线程
(d)调用FutureTask对象的get方法获取线程返回值

示例(非Lambda表达式版):

package czz;

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class ThirdThread implements Callable<Integer> {
    @Override
    public Integer call(){
        int i=0;
        for (;i<100;i++){
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
        return i;
    }


    public static void main(String[] args) {
        ThirdThread callThread = new ThirdThread();
        FutureTask<Integer> task = new FutureTask<>(callThread);
        for (int i=0;i<500;i++){
            System.out.println(Thread.currentThread().getName() + "循环变量i= " + i);
            if (i==5){
                new Thread(task,"有返回值的线程").start();
            }
        }
        try{
            System.out.println("子线程的返回值:"+task.get());
        } catch (Exception e){
            e.printStackTrace();
        }
    }
}

示例(Lambda表达式版):

package czz;

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class ThirdThread1 {
    public static void main(String[] args) {
        FutureTask<Integer> task = new FutureTask<>(()->{
            int i=0;
            for (;i<100;i++){
                System.out.println(Thread.currentThread().getName() + " " + i);
            }
            return i;
        });

        for (int i=0;i<500;i++){
            System.out.println(Thread.currentThread().getName() + "循环变量i= " + i);
            if (i==5){
                new Thread(task,"Lambda有返回值的线程").start();
            }
        }
        try{
            System.out.println("Lambda子线程的返回值:"+task.get());
        } catch (Exception e){
            e.printStackTrace();
        }
    }
}

三种方式的比较

Runnable与Callable接口实现基本一致。这两种方式优缺点:
(a)线程类还可以继承其他类
(b)多个线程可以共享一个target,非常适合多个相同的线程处理同一份资源的情况。从而可以将CPU,代码,数据分开,形成清晰的模型,较好的体现了面向对象的编程思想。
(c)缺点是编程复杂一些,如果需要访问当前线程,需要访问Thread.currentThread()

采用Thread
(a)缺点是不能继承其他父类
(b)优点是编码简单,当前线程this即可。

学无止境。大家可以关注我的微信公众号,方便利用零碎时间互相交流。共勉!

路漫漫其修远兮,吾将上下而求索。。。

猜你喜欢

转载自www.cnblogs.com/caozz/p/thread01.html
今日推荐