博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
java高级-多线程编程
阅读量:6988 次
发布时间:2019-06-27

本文共 4970 字,大约阅读时间需要 16 分钟。

hot3.png

一、进程和线程        

        在java语言中最大的特点就是支持多线程的开发(也是为数不多支持多线程开发的语言),如果对多线程没有一个全面而且细致的了解,在以后一定存在严重的技术缺陷。

        进程:传统的dos是单进程处理,允许一个程序执行,后来到了Windows时代,出现了多线程的设计。表示在一个时间段上可以运行多个程序,并且这些程序将进行资源的轮流抢占。在同一时间点上,只有一个程序执行,后来有了多核CPU的实现。可以实现在同一时间点,多个程序同时执行。

        线程:是进程的基础之上划分的更小的程序单元,线程依赖于进程的实现,线程的启动要比进程快的多。

二、Thread实现多线程

 范例:  多线程主体类

class MyThread extends  Thread{    private String title;    public MyThread(String title){        this.title = title;    }    @Override    public void run() {        for (int i = 0; i< 10; i++) {             System.out.println(this.title+"运行,i="+i);        }    }}

    多线程要执行的功能都应该在run()方法中进行定义,run()方法不能被直接调用(如果我们直接调用子线程的run()方法,其方法还是运行在主线程中,代码在程序中是顺序执行的,所以不会有解决耗时操作的问题。所以不能直接调用线程的run()方法,只有子线程开始了,才会有异步的效果。当thread.start()方法执行了以后,子线程才会执行run()方法,这样的效果和在主线程中直接调用run()方法的效果是截然不同的)。所以如果要开启多线程,必须执行start()方法。

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

   执行结果:线程A,B,C交替执行,执行顺序不可控。

   结论:虽然调用是start()方法,但是最终执行的是run()方法。

   疑问:为什么不直接使用run()方法,而使用start()?

   start()源码:

public synchronized void start() {    if (threadStatus != 0)        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()方法源代码:抛出“IllegalThreadStateException”异常,没有throws也没有try..catch,表明此异常一定是RuntimeException的子类。一个线程只允许启动一次,否则就会抛出此异常。

    列如:

public static void main(String[] args) {    Thread t = new MyThread("线程A");    t.start();    t.start();}

414add1ea7ec806ca7b6733af86637c56f4.jpg

    native含义:

    在java执行过程中考虑到对于不同层次的开发者的需求,所以java支持有本地的操作系统函数调用,而这项技术成为JNI技术,但是Java开发过程中并不推荐这样使用,这项技术可以使用一些底层函数进行一些特殊的处理,而在start()方法里面的start0()表示需要将此方法依赖于不同的操作系统实现。

三、Runnable接口实现多线程

范例:Runnable实现

class MyThread implements  Runnable{    private String title;    public MyThread(String title){        this.title = title;    }    @Override    public void run() {        for (int i = 0; i< 10; i++) {            System.out.println(this.title+"运行,i="+i);        }    }}

    此方式避免了单继承的局限,也能更好的进行功能扩展。以后的开发中,优先考虑Runnable来实现,但是启动多线程永远都是通过Thread对象来启动多线程。

    lambda表达式写法:

public static void main(String[] args) {    for (int i =0; i <3 ; i++){        String title="线程对象_"+i;        Runnable run=()->{            for (int j = 0; j< 10; j++){                System.out.println(title+"运行,j="+j);            }        };        new Thread(run).start();    }}

更简洁:

public static void main(String[] args) {    for (int i =0; i <3 ; i++){        String title="线程对象_"+i;        new Thread(()->{            for (int j = 0; j< 10; j++){                System.out.println(title+"运行,j="+j);            }        }).start();    }}

四、Thread和Runnable的关系

    Thread也是Runnable接口的子类

    关系图:

2d7e0894488eebd2a85f866e79476946103.jpg

    多线程的设计过程中,使用了代理模式,用户自定义的MyThread的类只负责项目核心功能的实现吗,而所有的辅助实现全部由Thread类来处理。

    调用start()方法时候,通过Thread的构造函数传递了一个Runnable的接口对象,此对象将会被target保存,最终调用的将是Runnable子类覆盖了Runnable接口的run()方法。

多线程开发示意图:

b490f650e56dd640e75ff05d494d5f22812.jpg

范例:通过卖票程序来实现多个线程的资源并发访问

class MyThread implements  Runnable{    private int  ticket = 5;    @Override    public void run() {        for (int i = 0; i< 100; i++) {            if(this.ticket> 0) {                System.out.println("卖票,ticket=" + ticket--);            }        }    }}public class Main {    public static void main(String[] args) {        MyThread mt = new MyThread();        new Thread(mt).start();        new Thread(mt).start();        new Thread(mt).start();    }}

执行结果:

15930bc620313bb234687e5a2d4183a5bb9.jpg

内存分析:

5497cc838eef89308d8b803209c94a499b3.jpg

五、Callable实现多线程

    如果要实现多线程,肯定依靠的是Runnable,但是Runnable中的run()没有返回值。所以JDK1.5后出现java.util.concurrent.Callable来实现多线程。

    接口的定义:

@FunctionalInterfacepublic interface Callable
{ V call() throws Exception;}

从定义可以发现Callable定义了一个泛型,此泛型的类型就是返回数据的类型,这样的好处是避免向下转型的安全隐患。

Callable的相关关系图:

59413d55dabec4a06b64648c2240e5aae45.jpg

实例:Callable的线程实现

import java.util.concurrent.Callable;import java.util.concurrent.FutureTask;class MyThread implements Callable
{ @Override public String call() throws Exception { for(int i = 0; i< 10 ; i++){ System.out.println("线程执行,i="+i); } return "程序执行完毕"; }}public class Main { public static void main(String[] args) { try { FutureTask
task = new FutureTask<>(new MyThread()); new Thread(task).start(); System.out.println("【线程返回数据】"+task.get()); }catch(Exception e){ e.printStackTrace(); } }}

执行结果:

9e2b1ae0555af56377a26f5a607333c1291.jpg

注:Runnable和Callable的区别?

1.Runnable是在JDK1.0的时候提出的多线程的实现接口,而Callable是在JDK1.5以后提出的。

2.java.util.Runnable接口只有一个run()方法,没有返回值,java.util.concurrent.Callable接口里面只有一个call()方法,可以有返回值。

六、线程的运行状态

对于多线程的开发而言,总是先定义线程主题类,通过Thread进行线程的启动,当你调用start()方法并不意味着线程已经启动。

线程的运行状态:

7e599a8774b7603907bdb6b29bdec87fc2d.jpg

1.任何一个线程对象都应该使用Thread来进行封,并且通过start()进行启动,启动的时候,线程就会进入一种就绪状态,并没有执行。

2.进入到就绪状态后,就需要等待资源调度,当某一个线程调度成功后进入运行状态(run()),但是所有的线程不可能一直持续的执行下去,中间需要产生一些暂停的状态。例如:某个线程执行一段时间后让出资源,而后,这个线程就将进入阻塞状态。随后重新回到就绪状态。

3.当run()方法执行完毕后,线程的主要任务也就完成了,此时就可以进入停止状态。

转载于:https://my.oschina.net/chenzhou/blog/2049677

你可能感兴趣的文章
java返回一个简单的日历
查看>>
java中包的命令行(cmd)操作详解
查看>>
git中fatal: Authentication failed的问题
查看>>
数学建模排版中加页码与首页不加页码问题
查看>>
matlab-1
查看>>
C# 用POST提交json数据
查看>>
cpu亲和性绑定
查看>>
变量 、缓冲值 、编码
查看>>
20160408javaweb之JDBC ---PreparedStatement
查看>>
2018.11.03-dtoj-2092-交通 (traffic)
查看>>
内置模块(二)
查看>>
【HNOI2016】树
查看>>
Java Web整合开发(附录1) - 安装配置环境
查看>>
javascript对象创建
查看>>
linux解压war包的命令
查看>>
MyQThread new
查看>>
js的继承
查看>>
SOCKET用法详解
查看>>
struts2中的OGNL详解
查看>>
C++的虚函数
查看>>