Java知识(四)

Author Avatar
子语 2017 - 12 - 23
  • 在其它设备中阅读本文章

Java多线程实现

Java可通过以下两种方式实现多线程:

· 继承Thread类;

· 实现Runnable接口(此外扩充了Callable接口)

线程与进程

Java支持多线程。
1、进程指的是一次程序的完整运行,在运行过程中内存、处理器、IO等资源都是为该进程服务。
DOS系统时代,有一种现象:假如病毒运行,那电脑就无法运行,因为所有资源都被病毒占用。但在windows时代,即使病毒在运行,电脑也可以运行。
这是因为windows系统是多进程操作系统。其资源分配方法是:在同一时间段,多进程轮流抢占资源,但在某时间点,只会有一个进程在运行
2、线程是在进程基础上进一步地划分的结果:即一个进程可以同时创建多个线程。线程是比进程更快的处理单元,而且所占的资源更小。多线程的应用也是性能最高的应用。

Thread类实现多线程

1、Thread类是一个支持多线程的功能类,只要是其子类,就可以实现多线程。
class MyThread extends Thread { // 多线程操作类}
程序的起点是main()。而每个线程也有它的起点run()。多线程类必须覆写Thread类的run()public void run(){},该方法没有返回值,表示线程一旦开始,就需要一直运行,不能返回内容。

// 线程操作主类
class MyThread extends Thread { 
    private String name;

    public MyThread(String name) {
        this.name = name;
    }

    @Override
    public void run() { // 覆写run(),作为线程的主体操作方法
        for (int x = 0; x < 200; x++) {
            System.out.println(this.name + "-->" + x);
        }
    }
}

public class Demo {
    public static void main(String[] args) {
        MyThread mA = new MyThread("线程A");
        MyThread mB = new MyThread("线程B");
        MyThread mC = new MyThread("线程C");

        mA.run();
        mB.run();
        mC.run();
    }
}

运行上述代码,输出结果是线程A、B、C依次进行循环输出。
2、线程与进程是一样的,都必须轮流去抢占资源,多线程的执行应该是多个线程彼此交替执行。但直接调用run()并不能启用多线程,多线程启用依靠的是Thread类的start():public void start()(调用此方法,执行的方法体是run()定义的)。

public class Demo {
    public static void main(String[] args) { // 主类
        MyThread mtA = new MyThread("线程A");
        MyThread mtB = new MyThread("线程B");
        MyThread mtC = new MyThread("线程C");

        mtA.start();
        mtB.start();
        mtC.start();
    }
}

上述代码结果中每个线程对象交替执行。

问题:为什么多线程启用不是调用run()而是调用start()?
打开Java的源代码,观察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()利用throw抛出IllegalThreadStateException,本应使用try…catch处理,或者start()上使用throws声明,但此处没有这样处理,这是因为该异常是RuntimeException的子类,属于选择性处理。如果某一个线程重复启动,就会抛出异常。
start()要调用start0(),而且该方法结构与抽象方法类似,唯一不同的是使用native声明。Java中有一个JNI技术(Java Native Interface),其特点是:使用Java调用本机操作系统提供的函数。其缺点:不能离开特定的操作系统。
如果线程要执行,需要操作系统进行资源分配,所以此操作是由JVM根据不同的操作系统实现的。即:使用Thread类的start()不仅仅要启动多线程的执行代码,还要根据不同的操作系统进行资源的分配。

Runnable接口实现多线程

1、虽然Thread类可以实现多线程。但存在问题:Java存在单继承限制。任何情况下,对于类的单继承都是应该尽量回避的,多线程也一样。为了解决单继承的限制,在Java中专门提供了Runnable接口,此接口定义如下:

@FunctionalInterface
public interface Runnable{
    public void run();
}

接口中都是public权限,不存在default权限。那么只需要让一个类实现Runnable接扣,并覆写run()方法即可。

class MyThread implements Runnable {
    private String name;

    public MyThread(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        for (int x = 0; x < 10; x++) {
            System.out.println(this.name + "-->" + x);
        }
    }
}

与继承Thread类相比,此时MyThread类在结构上并没有区别,但存在一个不同:继承Thread类,可以直接继承start(),但是实现Runnable接口,并没有start()。
2、要想启用多线程,一定依靠Thread类完成,在Thread类定义有如下构造方法:
public Thread(Runnable target),接收的是Runable接口对象;
范例:启动多线程

public class Demo {
    public static void main(String[] args) { // 主类
        MyThread mtA = new MyThread("线程A");
        MyThread mtB = new MyThread("线程B");
        MyThread mtC = new MyThread("线程C");
        new Thread(mtA).start();
        new Thread(mtB).start();
        new Thread(mtC).start();
    }
}

此时就避免了单继承局限,所以实际开发中使用接口是最合适的。

两种实现方式的区别

Runnable接口与Thread类相比,解决了单继承的局限,所以如果要使用,一定使用Runnable接口。
1、观察Thread类的定义
public class Thread extends Object implements Runnable
Thread类实现了Runnable接口。
无法加载
2、除了以上的联系外,还有一点:使用Runnable接口可以比Thread类更好地描述出数据共享这一概念。此时的数据共享指的是多个线程访问同一资源的操作。
范例:观察代码(每一个线程对象都必须通过start()启动)

class MyThread extends Thread {
    private int ticket = 10;

    @Override
    public void run() {
        for (int x = 0; x < 100; x++) {
            if (this.ticket > 0) {
                System.out.println("卖票,ticket = " + this.ticket--);
            }
        }
    }
}

public class Demo {
    public static void main(String[] args) { // 主类
        MyThread mtA = new MyThread();
        MyThread mtB = new MyThread();
        MyThread mtC = new MyThread();
        mtA.start();
        mtB.start();
        mtC.start();
    }
}

上述代码声明了三个MyThread对象,并且分别调用start()方法,启动线程,发现每个线程都在卖各自的十张票,此时的内存关系如下:
无法加载
此时并不存在数据共享。
范例:利用Runnable实现

class MyThread implements Runnable {
    private int ticket = 10;

    @Override
    public void run() {
        for (int x = 0; x < 100; x++) {
            if (this.ticket > 0) {
                System.out.println("卖票,ticket = " + this.ticket--);
            }
        }
    }
}

public class Demo {
    public static void main(String[] args) { // 主类
        MyThread mt = new MyThread();
        new Thread(mt).start();
        new Thread(mt).start();
        new Thread(mt).start();
    }
}

上述代码的内存分析如下:
无法加载
此时也属于三个线程对象,唯一的区别是,这三个线程对象都直接占用了同一个MyThread类对象引用,即这三个线程对象都直接访问同一个数据资源。

请解释Thread与Runnable实现多线程的区别?(请解释多线程两种实现方式的区别?)
1、Thread类是Runnable接口的子类,使用Runnable接口多线程可以避免单继承局限;
2、Runnable接口实现的多线程可以比Thread类实现的多线程更加清楚地描述数据共享的概念;

Callable接口实现多继承

1、使用Runnable实现多线程可以避免单继承局限,但是Runnable中的run()不能返回操作结果。为了解该问题,Java提供了一个新的接口java.util.concurrent.Callable

@FunctionalInterface
public interface Callable<V>{
    public V call() throws Exception;
}

call()执行完线程的主体功能之后可以返回一个结果,而返回类型由Callable的泛型决定。
范例:定义一个线程主体类

class MyThread implements Callable<String> {
    private int ticket = 10;

    @Override
    public String call() throws Exception {
        for (int x = 0; x < 100; x++) {
            if (this.ticket > 0) {
                System.out.println("卖票,ticket = " + this.ticket--);
            }
        }
        return "票已经卖光";
    }
}

此时发现Thread类中没有接收Callable对象的应用。但从JDK1.5开始增加java.util.concurrent.FutureTask<V>类,这个类主要负责Callable接口对象的操作。这个类的结构:
public class FutureTask<V> extends Object implements RunnableFuture<V>
而上述的RunnableFuture结构如下:
public interface RunnableFuture<V> extends Runnable,Future<V>
在FutureTask类中定义有构造方法:public FutureTask(Callable<V> callable),接收的是call()的返回值。
范例:启动多线程

public class Demo {
    public static void main(String[] args) throws Exception { 
    
        MyThread mtA = new MyThread();
        MyThread mtB = new MyThread();
        FutureTask<String> taskA = new FutureTask<String>(mtA);
        FutureTask<String> taskB = new FutureTask<String>(mtB);
        // 目的是取得call()的返回值
        // FutureTask是Runnable接口的子类,所以可以使用Thread的构造接收
        new Thread(taskA).start(); // 启动多线程
        new Thread(taskB).start();
        // 多线程执行完毕后,可以通过FutureTask的父接口Future中的get()方法取得返回内容
        System.out.println("A线程的返回结果:" + taskA.get());
        System.out.println("B线程的返回结果:" + taskB.get());
    }
}

上述代码最麻烦的地方在于需要接收返回值,并且又要与原始的多线程实现靠拢(向Thread类靠拢)。

多线程常用操作方法

多线程有许多方法,但大部分方法都定义在Thread类中,本章只介绍几种开发常用方法。

线程命名和获取线程

1、线程的每次运行结果都不同,因为其会根据实际情况进行资源抢占。因此要区分每个线程,必须依靠线程名。线程名一般是在其启动前定义。不建议更改已经启动的线程名或为不同线程设置相同的名字。

2.、对线程进行命名,可以利用Thread类的如下方法:

  1. 构造方法:public Thread(Runnable target, String name);

2)设置名字:public final void setName(String name);

3)取得名字:public final String getName();

Runable子类没有继承Thread类,要想获取线程名即获取当前执行方法的线程名需要利用Thread类中提供的取得当前线程对象的方法:public static Thread currentThread()

**范例:**不设置线程名

class MyThread implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

public class Demo {
    public static void main(String[] args) throws Exception {
        MyThread mt = new MyThread();
        new Thread(mt).start(); // Thread-0
        new Thread(mt).start(); // Thread-1
        new Thread(mt).start(); // Thread-2
    }
}

实例化Thread类对象时,如果没有为其设置名字,会自动进行编号命名Thread-x,来保证线程名字不重复。

**范例:**设置线程名

class MyThread implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

public class Demo {
    public static void main(String[] args) throws Exception {
        MyThread mt = new MyThread();
        new Thread(mt, "线程A").start(); // 线程A
        new Thread(mt).start(); // Thread-0
        new Thread(mt, "线程B").start(); // 线程B
    }
}

3、观察下述代码

public class Demo {
    public static void main(String[] args) throws Exception {
        MyThread mt = new MyThread();
        new Thread(mt, "线程A").start(); // 线程A
        mt.run(); // 直接调用run()方法,结果为main
    }
}

上述代码说明主方法就是一个线程main线程,所有在主方法上创建的线程实际上都可以将其视为子线程。上述代码也说明线程一直都存在(主方法就是主线程),每当使用java命令去解释一个程序类时,对于操作系统而言,都相当于启动了一个进程,而main只是这进程上的一个子线程而已。

**问题:**一个JVM进程启动时至少启动几个线程?

**答:**至少启用了2个线程

1.main线程:程序的主要执行,以及启动子线程;

2.gc线程:负责垃圾收集


休眠

1、线程休眠指的是让线程的执行暂时停顿,其方法:public static void sleep(longmillis) throws InterruptedException

**范例:**观察休眠特点

class MyThread implements Runnable {
    @Override
    public void run() {
        for (int x = 0; x < 10000; x++) {
            try {
                Thread.sleep(1000); // 让其休眠1秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ",x = " + x);
        }
	}
}

public class Demo {
    public static void main(String[] args) throws Exception {
        MyThread mt = new MyThread();
        new Thread(mt, "线程A").start();
    }
}

由于每次执行run()都要休眠1秒,所以执行的速度变慢。一般情况下,休眠时设置多个线程对象将导致所有线程对象一起进入run()(所谓一起进入实际上是因为先后进入的顺序时间间隔短,肉眼忽略,但实际上不是同时进入)。

线程优先级

1、 线程优先级越高,越有可能先被执行。在Thread类中提供有以下两个方法设置和获取优先级:

​ · 设置优先级:public final void setPriority(int new Priority)

​ · 取得优先级:public final int getPriority()

设置和取得优先级都是使用int型数据类型,对于此内容有三种取值:

​ · 最高优先级:public static final int MAX_PRIORITY;// 值为10

​ · 默认优先级:public static final int NORM_PRIORITY;// 值为5

​ · 最低优先级:public static final int MIN_PRIORITY。 // 值为1

**范例:**优先级

class MyThread implements Runnable {
    @Override
    public void run() {
        for (int x = 0; x < 20; x++) {
            System.out.println(Thread.currentThread().getName() + ",x = " + x);
        }
    }
}

public class Demo {
    public static void main(String[] args) throws Exception {
        MyThread mt = new MyThread();
        Thread tA = new Thread(mt, "线程A");
        Thread tB = new Thread(mt, "线程B");
        Thread tC = new Thread(mt, "线程C");
        tA.setPriority(Thread.MAX_PRIORITY);
        tA.start();
        tB.start();
        tC.start();
    }
}

**范例:**主线程优先级

public class Demo {
    public static void main(String[] args) throws Exception {
      // 获取主线程的优先级
      System.out.println(Thread.currentThread().getPriority());     // 5
    }
}

总结:

Thread.currentThread()可以取得当前线程类对象;

Thread.sleep()用于线程休眠,看起来是一起休眠,实际存在时间间隔

优先级越高的线程越有可能先执行。

This blog is under a CC BY-NC-SA 3.0 Unported License
本文链接:http://yov.oschina.io/article/Java/Java/Java知识(四)/