Java中多线程与创建方式

现在的操作系统是多任务操作系统。多线程是实现多任务的一种方式。

进程与线程

进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。
线程是指进程中的一个执行流程,一个进程中可以运行多个线程。比如Java.exe进程中可以运行很多线程。线程总是属于某个进程,进程中的多个线程共享进程的内存。
线程分为两类:用户线程和守候线程。
当所有用户线程执行完毕后,JVM自动关闭。但是守候线程却不独立与JVM,守候线程一般是有操作系统或用户自己创建的。

Java中线程生命周期

image.png
Java线程具有五中基本状态

新建状态(New):新建线程对象就进入了新建状态,就像new一个Thread线程对象
就绪状态(Runnable):当调用线程对象的start()方法,线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行。
运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。就绪状态是进入到运行状态的唯一入口,线程想进入运行状态执行,首先必须处于就绪状态中
阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态。
根据阻塞产生的原因不同,阻塞状态又可以分为三种:
1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
2.同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态
3.其他阻塞 -- 通过调用线程的sleep()join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

Java编写程序都运行在在Java虚拟机(JVM)中,在JVM的内部,程序的多任务是通过线程来实现的。每用Java命令启动一个Java应用程序,就会启动一个JVM进程。在同一个JVM进程中,有且只有一个进程,就是它自己。在这个JVM环境中,所有程序代码的运行都是以线程来运行。

在Java中创建线程

在创建实例前先创建类Singleton(线程不安全)

public class Singleton {
    private Singleton() {}
    private static Singleton single=null;
    //静态工厂方法
    public static Singleton getInstance() {
        if (single == null) {
            single = new Singleton();
        }
        return single;
    }
}

1.继承Thread类创建线程类

public class Single extends Thread{
    @Override
    public void run() {
        Singleton singleton=Singleton.getInstance();
        System.out.println(singleton);
    }
}

创建一个类继承扩展Thread类,在这子类中重写run(),在run()内写线程任务代码

在main方法中测试

public class DoTest {
    public static void main(String[] args) {
        Single single1 = new Single();
        Single single2 = new Single();
        Thread t1 = new Thread(single1);
        Thread t2 = new Thread(single2);
        t1.start();
        t2.start();
    }
}

创建该子类实例,即是创建了一个线程实例并调用该实例的start方法来启动该线程

结果:

image.png

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

public class Single implements Runnable{
    @Override
    public void run() {
        Singleton singleton=Singleton.getInstance();
        System.out.println(singleton);
    }
}

实现Runnable接口构造线程的方法是: 要在一个扩展了Runnable接口的类中实现接口中的抽象方法run()

在main方法中测试

public class DoTest {
    public static void main(String[] args) {
        Single single=new Single();
        new Thread(single).start();
        new Thread(single).start();
    }
}

Thread类的构造方法要求将一个Runable接口类型的对象作为参数,这个就将这个接口的run()与所声明的Thread对象绑定在一起,也就可以用这个对象的start()sleep()来控制这个线程。

结果:

image.png

注:对于线程的启动而言,都是调用线程对象的start()方法,不能对同一线程对象两次调用start()方法。第二次调用必然会抛出IllegalThreadStateException,这是一种运行时异常,多次调用start()被认为是编程错误。

关于继承Thread类与实现Runnable接口的区别

1.使用继承Thread类实现多线程是多个线程分别完成自己的任务,实现Runnable接口实现多线程是多个线程共同完成一个任务。其实在实现一个任务用多个线程来做也可以用继承Thread类来实现只是比较麻烦,一般我们用实现Runnable接口来实现,简洁明了。
2.资源共享:继承Thread不适合资源共享。实现Runable接口的话很容易的实现资源共享。通过继承Thread类,每个线程都有一个相关联的唯一对象,而实现Runnable接口,多线程可以共享同一个Runnable实例。
3.Java单继承性,使用继承Thread有点局限,而实现Runable接口克服了单继承的限制,用接口的方式将你的代码和线程实现分离,更加清晰。

实现Runnable接口比继承Thread类所具有的优势:

1.适合多个相同的程序代码的线程去处理同一个资源
2.可以避免Java中的单继承的限制
3.增加程序的健壮性,代码可以被多个线程共享,代码和数据独立

不过这两种方式都有一个缺陷就是:在执行完任务之后无法获取执行结果。如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,这样使用起来就比较麻烦。

3.实现Callable接口创建线程类

Callable接口类似于 RunnableCallable接口的任务线程能返回执行结果并且其call()允许抛出异常,而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛

Callable接口源码

@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

Callable接口位于java.util.concurrent包下,在里面声明了一个call()
这是一个泛型接口,call()函数返回的类型就是传递进来的V类型。

Future表示异步计算的结果,就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get()获取执行结果,该方法会阻塞直到任务返回结果。

使用Callable+Future获取执行结果

public class Single implements Callable<String> {
    @Override
    public String call() throws Exception {
        Singleton singleton=Singleton.getInstance();
        System.out.println(singleton);
        return "Callable+Future获取执行结果";
    }
}

这里我简单返回一句话

在main方法中测试

public class DoTest {
    public static void main(String[] args)  {
        ExecutorService executor = Executors.newCachedThreadPool();
        Single single = new Single();
        Future<String> single1 = executor.submit(single);
        Future<String> single2 = executor.submit(single);
        try {
            System.out.println(single1.get());
            System.out.println(single2.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }finally {
            executor.shutdown();
        }
    }
}

Callable一般要配合线程池ExecutorService来使用,ExecutorServiceExecutor直接的扩展接口,也是最常用的线程池接口,Executor的实现提供的一些方法可以返回一个Future对象, 通过它我们可以跟踪到异步任务的执行和停止。

测试结果

image.png

线程结果正常返回出来了

使用Callable+FutureTask获取执行结果
只需要修改main方法

public class DoTest {
    public static void main(String[] args)  {
        ExecutorService executor = Executors.newCachedThreadPool();
        Single single = new Single();
        FutureTask<String> single1 = new FutureTask<String>(single);
        FutureTask<String> single2 = new FutureTask<String>(single);
        executor.submit(single1);
        executor.submit(single2);
        try {
            System.out.println(single1.get());
            System.out.println(single2.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }finally {
            executor.shutdown();
        }
    }
}

测试结果

image.png

关于Future和FutureTask

FutureTaskFuture接口的一个唯一实现类。
FutureTask实现了Runnable,因此它既可以通过Thread包装来直接执行,也可以提交给ExecuteService来执行。
FutureTask实现了Futrue可以直接通过get()函数获取执行结果,该函数会阻塞,直到结果返回。
如果需要使用 Future 但又不提供可用的结果,则可以声明 Future<?> 形式类型、并返回 null

总结:

Java中所有的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到CPU的资源。在Java中,每次程序运行至少启动两个线程。一个是main线程,一个是垃圾收集线程。因为每当使用Java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM实际上就是在操作系统中启动了一个进程。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,406评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,976评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,302评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,366评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,372评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,457评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,872评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,521评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,717评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,523评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,590评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,299评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,859评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,883评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,127评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,760评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,290评论 2 342