Java中的多线程

一、Java中线程实现

Java 中实现多线程的代码有三种方式,一种是继承 Thread 类,另一种是实现 Runnable 接口,在JDK1.5之后还有一个 Callable 接口,Runnable 接口方式利于资源共享的处理,Callable 接口的实现方式可以获取线程的返回值。

1. 方法1——继承 Thread 类

Thread 类是在 java.lang 包中定义的。一个类只要继承了 Thread 类就称为多线程操作类。在 Thread 的子类中必须明确覆写 Thread 类中的 run() 方法,此方法为线程主体。线程类定义如下:

class 类名 extends Thread {
    属性...
    方法...
    public void run() {
        线程主体
    }
}

启动线程是调用 Thread 类的 start() 方法,而不是 run() 方法。若直接调用 run() 方法就是一个普通方法调用,而不是多线程。并且 start() 方法只能调用一次,因为 start() 方法中有一个调用计数,多次调用会 throw new IllegalThreadStateException() 异常。

例子:

class MyThread extends Thread {
    private String name;
    public MyThread(String name) {
        this.name = name;
    }

    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("name: " + name + " i=" + i);
        }
    }
}

public class ThreadDemo {
    public static void main(String args[]) {
        MyThread mt1 = new MyThread("mt1");
        MyThread mt2 = new MyThread("mt2");
        //mt1.run(); //简单的方法调用
        //mt2.run();
        mt1.start();
        mt2.start();
        //mt2.start(); //触发IllegalThreadStateException异常
    }
}

 

2. 方法2——实现 Runnable 接口

Java 中也可以通过实现 Runnable 接口的方式实现多线程,此接口定义为:

public interface Runnable {
    public void run();
}

使用 Runnable 接口实现多线程的格式:

class 类名 implements Runnable {
    属性...
    方法...
    public void run() {
        线程主体
    }
}

Runnable 接口实际上还是依靠 Thread 实现多线程启动的,可以看 Thread 类的定义就知道使用方法了:

public class Thread extends Object implements Runnable {
    private Runnable target;
    
    public Thread(Runnable target, String name) {
        init(null, target, name, 0);
    }
    private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
        ...
        this.target = target;
        ...
    }
    public void run() {
        if (target != null) {
            target.run();
        }
    }
}

如果传了 Runnable 类型的参数,最终执行的就是 Runnable 参数的 run() 方法。

举例1:

class MyThread implements Runnable {
    private String name;
    public MyThread(String name) {
        this.name = name;
    }

    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("name: " + name + " i=" + i);
        }
    }
}

public class ThreadDemo {
    public static void main(String args[]) {
        MyThread mt1 = new MyThread("mt1");
        MyThread mt2 = new MyThread("mt2");
        Thread t1 = new Thread(mt1); //传复写run()方法的Runnable的子类
        Thread t2 = new Thread(mt2);
        t1.start();
        t2.start();
    }
}

通过 Runnable 接口实现多线程比起通过实现 Thread 类实现多线程的优势是便于多个同类对象资源共享时的处理。因为后者的运行的 run() 方法是来自参数对象的,因此多个线程传同一个参数对象的话其属性就只有一份资源。而前者需要定义多个对象然后调用其 run() 方法实现多线程,由于是多个对象,其属性就是多个资源了。开发过程中建议使用 Runnable 接口的实现方式实现多线程。


3. 方法3——利用 Callable 接口

通过 Runnable 接口实现的多线程会出现 run() 方法不能返回操作结果的问题,为了解决此问题,JDK1.5开始提供了一个新的接口 java.util.concurrent.Callable,定义如下:

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

call() 方法在执行完后可以返回一个具体类型的数据。但是 Thread 类中没有定义任何构造方法来接收 Callable 接口对象实现对象,这导致多线程的启动又遇到了问题,JDK1.5之后开始提供一个 java.util.concurrent.FutureTask<V> 类来解决这个问题,其定义:

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

FutureTask 实现了 RunnableFuture 接口,而后者又同时实现了 Future 和 Runnable 接口。如果想要接收线程执行的返回结果,调用 Future 接口中的 get() 方法即可。FutureTask 类常用方法如下:

public FutureTask(Callable<V> callable); //构造函数,接收 Callable 接口对象实例
public FutureTask(Runnable runnable, V result); //接收 Runnable 接口实例,并指定返回结果类型
public V get() throws InterruptedException, ExecutionException; //取得线程的执行结果,由 Future 接口定义

FutureTask 是 Runnable 接口的子类,并且其构造函数可以接收 Callable 实例,因此依然可以利用 Thread 类来实现多线程的启动。若想获取线程执行结果,则利用 Future 接口中的 get() 方法。

例子:

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

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

    //@override
    public String call() throws Exception {
        for (int i = 0; i < 10; i++) {
            if (ticket > 0) {
                System.out.println("ticket left: " + ticket--);
            }
        }
        return "sold out";
    }
}

public class ThreadDemo {
    public static void main(String args[]) {
        MyThread mt1 = new MyThread();
        MyThread mt2 = new MyThread();

        FutureTask<String> task1 = new FutureTask<String>(mt1);
        FutureTask<String> task2 = new FutureTask<String>(mt2);

        new Thread(task1).start();
        new Thread(task2).start();

        try {
            System.out.println("task1 return: " + task1.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        try {
            System.out.println("task2 return: " + task2.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

 Runnable 接口是 Java 最早提供也是使用最广泛的,平时建议通过使用 Runnable 接口的方式实现多线程

 

二、线程操作相关方法

1. Thread 类中的主要方法

在 Java 实现多线程的程序中,虽然 Thread 类实现了 Runnable 接口,但是操作线程的主要方法并不在 Runnable 接口中,而是在 Thread 类中,下面列出 Thread 类中的主要方法:

public Thread(Runnable target) //构造方法,通过 Runnable 接口子类对象实例化 Thread 对象
public Thread(Runnable target, String name) //构造方法,通过 Runnable 接口子类对象实例化 Thread 对象,并设置子线程名称
public Thread(String name) //构造方法,实例化对象并设置子线程名称
public static Thread currentThread() //返回目前正在执行的线程,静态方法,可以直接 Thread.currentThread()进行调用。
public final String getName() //返回线程名称
public final void setName(String name) //设定线程名称
public final int getPriority() //返回线程优先级
public final void setPriority(int newPriority) //设置线程优先级
public boolean isInterrupted() //判断目前线程是否被中断,如果是返回true,否则返回false
public final boolean isAlive() //判断线程是否在活动,如果是返回true,否则返回false
public final void join() throws InterruptedException //等待线程死亡
public final synchronized void join(long millis) throws InterruptedException //等待 millis ms后,线程死亡  ######
public void run() //线程函数主体
public static void sleep(long millis) throws InterruptedException //使目前正在执行的线程休眠 millis ms
public void start() //开始执行新线程
public String toString() //返回代表线程的字符串
public static void yield() //将目前正在执行的线程暂停,允许其他线程执行
public final void setDaemon(boolean on) //将一个线程设置为后台运行

2. Thread 类中的方法使用

(1) getName/setName

线程名称一般是启动前设置,但是也允许为已经运行的线程设置名字,允许两个 Thread 对象有相同的名字。如果没有设置线程的名字,系统会自动为其分配,格式为 Thread-X,X是数字,从0开始。

class MyThread implements Runnable {
    public void run() {
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName() + " running " + i); //为啥直接使用getName()和Thread.getName()都报错
        }
        while(true);
    }
}

public class ThreadDemo {
    public static void main(String args[]) {
        MyThread mt1 = new MyThread();
        new Thread(mt1).start();

        MyThread mt2 = new MyThread();
        new Thread(mt2).start();

        MyThread mt3 = new MyThread();
        new Thread(mt3, "mt3").start();

        mt1.run(); //直接调用run()也打印出了main,说明main也是一个线程
    }
}

/*
# java ThreadDemo 
Thread-0 running 0
Thread-0 running 1
Thread-0 running 2
mt3 running 0
mt3 running 1
mt3 running 2
main running 0
main running 1
main running 2
Thread-1 running 0
Thread-1 running 1
Thread-1 running 2
*/

而直接继承 Thread 类是可以直接调用的,Runnable 接口继承 Thread 类,这里 MyThread 类实现 Runnable 接口,与直接继承 Thread 类有何区别?

class MyThread extends Thread {
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(1);
            } catch(Exception e) {}
            System.out.println("name: " + getName());
            if (i > 50) {
                while(true);
            }
        }
    }
}

public class ThreadDemo {
    public static void main(String args[]) {
        MyThread mt1 = new MyThread();
        mt1.start();
        try {
            Thread.sleep(10);
        } catch(Exception e) {}
        mt1.setName("Hello");
    }
}

这些线程的名字只是 Java 层的,cat /proc/<pid>/task/<tid>/comm 全部显示为java,此例中/proc/<pid>/task/下有17个线程,名字全为java。就算是调用了 setName() 也不会改变 cat 出来的名字。注:Android art中应该做了处理,proc节点可以cat出来。

(2) isAlive()判断线程是否启动

class MyThread implements Runnable {
    public void run() {
        System.out.println(Thread.currentThread().getName() + " running");
        while(true);
    }
}

public class ThreadDemo {
    public static void main(String args[]) {
        MyThread mt1 = new MyThread();
        Thread t1 = new Thread(mt1);
        System.out.println("isAlive: " + t1.isAlive());
        t1.start();
        System.out.println("isAlive: " + t1.isAlive());
    }
}

/*
# java ThreadDemo 
isAlive: false
isAlive: true
Thread-0 running
*/

注意,主线程先执行完,但是其它线程不会受到任何影响,也不会随着主线程的结束而结束。和C不同!

(3) sleep() 线程的休眠

class MyThread implements Runnable {
    public void run() {
        while(true) {
            System.out.println(Thread.currentThread().getName() + " running");
        }
    }
}

public class ThreadDemo {
    public static void main(String args[]) {
        MyThread mt1 = new MyThread();
        Thread t1 = new Thread(mt1);
        t1.setDaemon(true);
        t1.start();
    }
}

但是没有设置成功,程序执行后直接退出。

(3) setPriority()/getPriority() 线程的优先级

class MyThread implements Runnable {
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " running " + i);
        }
        while(true);
    }
}

public class ThreadDemo {
    public static void main(String args[]) {
        MyThread mt1 = new MyThread();
        Thread t1 = new Thread(mt1, "MIN_P");
        t1.setPriority(Thread.MIN_PRIORITY);
        t1.start();
        System.out.println(t1.getName() + " priority: " + t1.getPriority());

        MyThread mt2 = new MyThread();
        Thread t2 = new Thread(mt2,"MAX_P");
        t2.setPriority(Thread.MAX_PRIORITY);
        t2.start();
        System.out.println(t2.getName() + " priority: " + t2.getPriority());

        MyThread mt3 = new MyThread();
        Thread t3 = new Thread(mt3, "NOR_P");
        t3.setPriority(Thread.NORM_PRIORITY);
        t3.start();
        System.out.println(t3.getName() + " priority: " + t3.getPriority());
    }
}

# java ThreadDemo 
MIN_P priority: 1
...
MAX_P priority: 10
...
NOR_P priority: 5
...

top看CPU占用率为300%,的确是三个核被占满了,但是cat /proc/<pid>/task/<tid>/sched,所有线程的优先级还是120,看来又是Java 虚拟机自己封装了优先级,对操作系统是不可见的(这只是Ubuntu上测试的状态。

在Android上,相同代码,运行起来状态如下:

8295:/proc/6529/task # cat 6543/comm
MIN_P
8295:/proc/6529/task # cat 6544/comm
MAX_P
8295:/proc/6529/task # cat 6545/comm
NOR_P
8295:/proc/6529/task # cat 6543/sched| grep prio
prio                                         :                  139
8295:/proc/6529/task # cat 6544/sched| grep prio
prio                                         :                  112
8295:/proc/6529/task # cat 6545/sched| grep prio
prio                                         :                  120

8295:/proc/6529/task # cat 6543/cgroup
4:memory:/
3:cpuset:/
2:cpu:/system-background //139优先级的线程会自动被设置到后台
1:blkio:/background
0::/uid_0/pid_6228
8295:/proc/6529/task # cat 6544/cgroup
4:memory:/
3:cpuset:/
2:cpu:/
1:blkio:/
0::/uid_0/pid_6228
8295:/proc/6529/task # cat 6545/cgroup
4:memory:/
3:cpuset:/
2:cpu:/
1:blkio:/
0::/uid_0/pid_6228

 

(4) yield()线程礼让

class MyThread extends Thread {
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + " running " + i);
            if (i == 50) {
                System.out.println(getName() + " yield");
                yield();
            }
        }
    }
}

public class ThreadDemo {
    public static void main(String args[]) {
        MyThread mt1 = new MyThread();
        mt1.start();
        MyThread mt2 = new MyThread();
        mt2.start();
    }
}

yield() 对应内核的实现机制是将此任务设置为 ignore buddy,只是选中它运行时只跳过一次,若是下次任务切换再次选中,就继续运行了,所以上面测试用例yield()后下次选可能还是选自己。


(5) interrupt()中断线程

class MyThread extends Thread {
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + " running " + i);
            try {
                sleep(100);
            } catch(Exception e) {
                System.out.println(getName() + " get exception and return");
                return;
            }
        }
    }
}

public class ThreadDemo {
    public static void main(String args[]) {
        MyThread mt1 = new MyThread();
        mt1.start();
        try {
            Thread.sleep(2000);
        } catch(Exception e) { }
        mt1.interrupt();
    }
}

/*
# java ThreadDemo 
Thread-0 running 0
...
Thread-0 running 19
Thread-0 get exception and return
*/

可以看出 interrupt() 就是使自己受到一个异常。若 run() 中没有调用 sleep 并进行 catch 异常,线程是不会响应 interrupt() 调用的,正常执行完毕。


三、线程同步互斥问题

1. synchronized 关键字

临界区可以通过 同步代码块同步方法 两种方式完成。代码块就是使用 {} 括起来的一段代码,根据其位置和声明的不同,又分为普通代码块构造块静态块 3种。若代码块上加 synchronized 关键字就称为同步代码块。
同步代码块:

//同步代码块:
synchronized(同步对象) {
    ...
}
//同步方法:
synchronized 返回值类型 方法名(参数列表) {
    ...
}

Java 中定义方法的完整格式:

访问权限{public/default/protected/private}[final][static][synchronized]
返回值类型 方法名称(参数列表)[throws Exception1, Exception2] {
    函数体
}

 

2. 等待与唤醒

Object类是所有类的父类,此类中有以下方法是对多线程进行支持的,notify()只唤醒一个,notifyAll()唤醒所有等待线程。

public final void wait() throws InterruptedException //线程等待
public final void wait(long timeout) throws InterruptedException //线程等待,可指定最长等待时间,单位ms
public final void wait(long timeout, int nanos) throws InterruptedException //线程等待,可指定最长等待多少ms和ns
public final void notify() //唤醒一个等待线程
public final void notifyAll() //唤醒全部等待线程

 

3. 一个生产者和消费者的例子

class Info {
    private static boolean flag = true; //true can produce
    private String content;

    public synchronized String get() {
        if (flag) {
            try {
                super.wait(); //Object's, the same as wait()
            } catch(Exception e) {}
        } else {
            flag = true;
            super.notify(); //Object's, the same as notify()
        }
        return this.content;
    }
    public synchronized void set(String content) {
        if (flag) {
            flag = false;
            this.content = content;
            System.out.println("set: " + this.content);
            super.notify();
        } else {
            try {
                super.wait();
            } catch(Exception e) {}
        }
    }
}

class MyThread implements Runnable {
    private boolean role;
    private Info info;
    public MyThread() {
        this.info = new Info();
    }
    public void run() {
        if ("Provider".equals(Thread.currentThread().getName())) {
            role = true;
        } else {
            role = false;
        }
        if (role) {
            for (int i = 0; i < 100; i++) {
                System.out.println("get: " + info.get());
            }
        } else {
            for (int i = 0; i < 100; i++) {
                info.set("I am " + i);
            }
        }
    }
}

public class ThreadDemo {
    public static void main(String args[]) {
        MyThread mt = new MyThread();
        new Thread(mt, "Consumer").start();
        new Thread(mt, "Provider").start();
    }
}

/*
...
set: I am 94
get: I am 94
set: I am 96
get: I am 96
set: I am 98
get: I am 98 //生产者和消费者各加一次,都是偶数了
*/

首先要保证两个线程共享 Info 实例对象才行,这样使用实现 Runnable 方式来实现线程好一些。

 

四、线程的生命周期

1. 线程中的 suspend() resume() stop() 方法已经被标记为 @Deprecated 注释,不建议使用。

2. 可以通过自己实现一个 stop() 然后在 run() 调用来实现 stop 线程。

 

五、补充

1. Thread的函数调用路径

//art/runtime/thread.h 
pid_t GetTid() const {    return tls32_.tid;}

//art/runtime/thread.cc tls32_.tid 的赋值位置:
void Thread::InitTid() {
    tls32_.tid = ::art::GetTid();
}

bool Thread::Init(ThreadList* thread_list, JavaVMExt* java_vm, JNIEnvExt* jni_env_ext) { ...  InitTid(); ...}

void* Thread::CreateCallback(void* arg) { ... self->Init(runtime->GetThreadList(), runtime->GetJavaVM(), self->tlsPtr_.tmp_jni_env); ...}

/* Thread::CreateCallback 就是线程的执行函数体,它先做一些初始化后再调用的用户的回调函数 */
void Thread::CreateNativeThread(JNIEnv* env, jobject java_peer, size_t stack_size, bool is_daemon) { ... pthread_create(&new_pthread, &attr, Thread::CreateCallback, child_thread); ...}

/* art/openjdkjvm/OpenjdkJvm.cc */
JNIEXPORT void JVM_StartThread(JNIEnv* env, jobject jthread, jlong stack_size, jboolean daemon) { ... art::Thread::CreateNativeThread(env, jthread, stack_size, daemon == JNI_TRUE); }

/* external/oj-libjdwp/src/share/javavm/export/jvm.h  java.lang.Thread 类的 */
JNIEXPORT void JNICALL JVM_StartThread(JNIEnv *env, jobject thread);

2. 任务优先级

t.setPriority() 最终会调用到 PaletteSchedSetPriority:

/* 若Java中设置 Thread t = new Thread(); t.setPriority(3); 就是设置nice=13 */
palette_status_t PaletteSchedSetPriority(int32_t tid, int32_t managed_priority) {
    /* Thread::setPriority(X),这层的取值范围是 [1, 10] */
    if (managed_priority < art::palette::kMinManagedThreadPriority ||
        managed_priority > art::palette::kMaxManagedThreadPriority) {
        return PALETTE_STATUS_INVALID_ARGUMENT;
    }

    /* 若传3,就是3-1,对应 ANDROID_PRIORITY_BACKGROUND + 3,nice=13 */
    int new_nice = kNiceValues[managed_priority - art::palette::kMinManagedThreadPriority];
    int curr_nice = getpriority(PRIO_PROCESS, tid);

    if (curr_nice == new_nice) {
        return PALETTE_STATUS_OK;
    }

    /* 还与cgroup挂钩,切cgroup */
    if (new_nice >= ANDROID_PRIORITY_BACKGROUND) { //10
        /* libprocessgroup 中的函数,其会设置任务的 timerslack */
        SetTaskProfiles(tid, {"SCHED_SP_SYSTEM"}, true);
    } else if (curr_nice >= ANDROID_PRIORITY_BACKGROUND) {
        SetTaskProfiles(tid, {"SCHED_SP_FOREGROUND"}, true);
    }

    /* 设当前进程nice值,new_nice取值[-20, 19] */
    if (setpriority(PRIO_PROCESS, tid, new_nice) != 0) {
        return PALETTE_STATUS_CHECK_ERRNO;
    }

    return PALETTE_STATUS_OK;
}

java优先级与nice值的转换:

system/libartpalette/palette_system.h
// Managed thread definitions
static constexpr int32_t kNormalManagedThreadPriority = 5;
static constexpr int32_t kMinManagedThreadPriority = 1;
static constexpr int32_t kMaxManagedThreadPriority = 10;
static constexpr int32_t kNumManagedThreadPriorities = kMaxManagedThreadPriority - kMinManagedThreadPriority + 1; //10

//作用:将参数转换为nice值
//system/libartpalette/palette_android.cc。这些宏在 system/core/libsystem/include/system/thread_defs.h 中定义
static const int kNiceValues[art::palette::kNumManagedThreadPriorities] = {
    ANDROID_PRIORITY_LOWEST,  // 1 (MIN_PRIORITY)      //19
    ANDROID_PRIORITY_BACKGROUND + 6,                   //10+6=16
    ANDROID_PRIORITY_BACKGROUND + 3,                   //10+3=13
    ANDROID_PRIORITY_BACKGROUND,                       //10
    ANDROID_PRIORITY_NORMAL,  //5(NORM_PRIORITY)       //0
    ANDROID_PRIORITY_NORMAL - 2,                       //-2
    ANDROID_PRIORITY_NORMAL - 4,                       //-4
    ANDROID_PRIORITY_URGENT_DISPLAY + 3,               //-8+3=-5
    ANDROID_PRIORITY_URGENT_DISPLAY + 2,               //-8+2=-6
    ANDROID_PRIORITY_URGENT_DISPLAY //10(MAX_PRIORITY) //-8
};

system/core/libsystem/include/system/graphics.h
#define HAL_PRIORITY_URGENT_DISPLAY    -8

 

posted on 2021-10-09 01:57  Hello-World3  阅读(79)  评论(0编辑  收藏  举报

导航