Java多线程-基础篇

2018-10-17  本文已影响32人  erki_stwee
qaq

学习消息机制后,有几个关于线程的问题。于是有了这篇文章。本文绝大多数是对于 Java多线程编程核心技术的总结

什么是线程

执行程序中的一个线程,Java虚拟机允许应用程序同时运行多个执行线程。每个线程都有优先权。具有较高优先级的线程优先于具有较低优先级的线程执行。每个线程可能会被标记为守护进程。当在某个线程中运行的代码创建一个新Thread对象时,新线程的优先级最初设置为等于创建线程的优先级,并且当且仅当创建线程是守护进程时才是守护进程线程。

线程和进程的关系

一个进程中可以包含多个线程

Thread的状态(有待补充)

Thread状态

线程的启动

如何实现Thread

* public class Thread implements Runnable
* public interface Runnable
继承Thread或者实现Runnable接口
public interface Runnable {
    public abstract void run();
}

我们来看一下Runnable实现类Thread中的run方法。

@Override
public void run() {
    if (target != null) {//target是如何初始化?
        target.run();
    }
}

//线程初始化
private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
    //currentThread():返回当前正在执行线程的引用
    Thread parent = currentThread();
    if (g == null) {
        g = parent.getThreadGroup();//线程终止返回null,否则返回ThreadGroup对象
    }
    
    g.addUnstarted();//记录未启动的线程
    this.group = g;

    this.target = target;
    //线程的优先级具有继承关系
    this.priority = parent.getPriority();
    //是否是守护线程(后面会提到)
    this.daemon = parent.isDaemon();
    setName(name);
    
    init2(parent);

    /* Stash the specified stack size in case the VM cares */
    this.stackSize = stackSize;
    tid = nextThreadID();
}

我们看一下Thread的初始化。

public Thread() {
    //target为null,run()方法怎么执行?
    init(null, null, "Thread-" + nextThreadNum(), 0);
}

通过上面的分析我们知道了Thread初始化执行了那些操作。但是run方法是怎么被执行的?
我们看一下start()方法源码。

public synchronized void start() {
    /**
     * This method is not invoked for the main method thread or "system"
     * group threads created/set up by the VM. Any new functionality added
     * to this method in the future may have to also be added to the VM.
     *
     * A zero status value corresponds to state "NEW".
     */
    //不能多次调用start()方法,否则抛出IllegalThreadStateException
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    /* Notify the group that this thread is about to be started
     * so that it can be added to the group's list of threads
     * and the group's unstarted count can be decremented. */
    //添加线程到ThreadGroup中
    group.add(this);

    started = false;
    try {
        //调用run方法
        nativeCreate(this, stackSize, daemon);
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then
              it will be passed up the call stack */
        }
    }
}

我们看到最终都会去调用c++层的nativeCreate()。

static void Thread_nativeCreate(JNIEnv* env, jclass, jobject java_thread, jlong stack_size,
                                jboolean daemon) {
    .
    .
    .
        Thread::CreateNativeThread(env, java_thread, stack_size, daemon == JNI_TRUE);
}

CreateNativeThread(JNIEnv*env, jobject java_peer, size_t stack_size, bool is_daemon) {
    .
    .
    .
    if (child_jni_env_ext.get() != nullptr) {
        pthread_t new_pthread;
        pthread_attr_t attr;
        child_thread -> tlsPtr_.tmp_jni_env = child_jni_env_ext.get();
        CHECK_PTHREAD_CALL(pthread_attr_init, ( & attr), "new thread");
        CHECK_PTHREAD_CALL(pthread_attr_setdetachstate, ( & attr, PTHREAD_CREATE_DETACHED),
                "PTHREAD_CREATE_DETACHED");
        CHECK_PTHREAD_CALL(pthread_attr_setstacksize, ( & attr, stack_size), stack_size);
        pthread_create_result = pthread_create( & new_pthread,
        &attr,
                Thread::CreateCallback,
                child_thread);
        CHECK_PTHREAD_CALL(pthread_attr_destroy, ( & attr), "new thread");

        if (pthread_create_result == 0) {
            // pthread_create started the new thread. The child is now responsible for managing the
            // JNIEnvExt we created.
            // Note: we can't check for tmp_jni_env == nullptr, as that would require synchronization
            //       between the threads.
            child_jni_env_ext.release();
            return;
        }
    }

    // Either JNIEnvExt::Create or pthread_create(3) failed, so clean up.
    {
        MutexLock mu (self,*Locks::runtime_shutdown_lock_);
        runtime -> EndThreadBirth();
    }
    .
    .
    .
}
CreateCallback(void*arg) {

    .
    .
    .
    // Invoke the 'run' method of our java.lang.Thread.
ObjPtr<mirror::Object > receiver = self -> tlsPtr_.opeer;
//调用Thread的run方法(为什么不直接调用run()?)
    jmethodID mid = WellKnownClasses::java_lang_Thread_run;
    ScopedLocalRef<jobject> ref (soa.Env(), soa.AddLocalReference < jobject > (receiver));
    InvokeVirtualOrInterfaceWithJValues(soa, ref.get(), mid, nullptr);
    // Detach and delete self.
    Runtime::Current () -> GetThreadList()->Unregister(self);

    return nullptr;
}

run方法被那个调用?


为什么不直接调用run方法?(start()与run()的区别)
Java中start与run
1)Thread状态来说
Start():新建状态---就绪状态
Run():新建状态--运行状态
2)能否新建线程来说
Start()可以新建线程。run()则不行
3)能否进行线程切换
start中的run代码可以进行线程切换。
run方法必须等待其代码全部执行完才能继续执行。

线程安全性问题

public class NotSharingThread extends Thread{

    private int count = 5;

    public NotSharingThread(String threadName) {
        super();
        this.setName(threadName);
    }

    @Override
    public void run() {
        super.run();
        while (count > 0){
            count --;
            System.out.println("由" + this.currentThread().getName()
                    + "计算,count = " + count);
        }
    }
}

NotSharingThread a = new NotSharingThread("A");
NotSharingThread b = new NotSharingThread("B");
NotSharingThread c = new NotSharingThread("C");
a.start();
b.start();
c.start();

I/System.out: 由A计算,count = 4
I/System.out: 由A计算,count = 3
I/System.out: 由A计算,count = 2
I/System.out: 由A计算,count = 1
I/System.out: 由A计算,count = 0
I/System.out: 由B计算,count = 4
I/System.out: 由B计算,count = 3
I/System.out: 由B计算,count = 2
I/System.out: 由B计算,count = 1
I/System.out: 由B计算,count = 0
I/System.out: 由C计算,count = 4
I/System.out: 由C计算,count = 3
I/System.out: 由C计算,count = 2
I/System.out: 由C计算,count = 1
I/System.out: 由C计算,count = 0
public class LoginServlet {

    private static String userNameRef;
    private static String passwordRef;

    public static void doPost(String userName,String password){
        try{
            userNameRef = userName;
            if(userName.equals("a")) {
                Thread.sleep(5000);
            }
            passwordRef = password;
            System.out.println("userName = " + userNameRef +
                    "passWord = " + passwordRef);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

}
public class ALoginThread  extends Thread{

    @Override
    public void run() {
        LoginServlet.doPost("a","aa");
    }
}
public class BLoginThread extends Thread{
    @Override
    public void run() {
        LoginServlet.doPost("b","bb");
    }
}

ALoginThread aLoginThread = new ALoginThread();
aLoginThread.start();
BLoginThread bLoginThread = new BLoginThread();
bLoginThread.start();
I/System.out: userName = b passWord = bb
I/System.out: userName = b passWord = aa

通过上述代码我们发现,同一时间多个线程对同一数据进行操作。引发了“非线程安全”。
通过synchronized方法可以避免“非线程安全”。
synchronized简介

线程停止

Java中有三种方式退出正在运行的线程:
1)使用退出标志,使线程正常退出,当run方法完成后线程终止。
2)使用stop方法强行终止线程(不推荐)。
3)使用interrupt方法中断线程(推荐)。
Thread.interrupt()
并不会终止一个正在运行的线程,还需要加入一个判断才可以完成线程的停止。(interrupt()方法仅仅是在当前线程中打了一个停止的标记)

判断线程是否是停止状态
this.interrupted():测试当前线程是否已经是中断状态。执行后具有将状态标志清除为false的功能(前提是中断状态)。
This.isInterrupted():测试线程Thread对象是否已经是中断状态,但不清除状态标志。

如何终止线程举例(推荐使用try-catch)

public class InterruptedThread extends Thread{
    
    @Override
    public void run() {
        super.run();
        try{
            for (int i = 0 ; i < 500000 ; i ++){
                if(this.isInterrupted()) {//判断是否是中断标志
                    System.out.println("已经是停止状态了!我要退出了");
                    throw new InterruptedException();
                }
                System.out.println("i = " + (i + 1));
            }
            System.out.println("我还在执行");
        }catch (InterruptedException e){
            System.out.println("进入try-catch方法");
            e.printStackTrace();
        }

    }

}
try{
    InterruptedThread interruptedThread = new InterruptedThread();
    interruptedThread.start();
    Thread.sleep(5000);
    //设置中断的标记位
    interruptedThread.interrupt();
}catch (InterruptedException exception){
    System.out.println("主方法try-catch");
    exception.printStackTrace();
}
输出结果
I/System.out: i = 41998
I/System.out: 已经是停止状态了!我要退出了
I/System.out: 进入try-catch方法

线程暂停(恢复)

暂停线程意味着此线程还可以恢复运行。suspend()暂停线程,resume()恢复线程。

线程优先级(1-10)

线程优先级具有继承性。优先级高的线程得到的CPU资源较多。但并不能保证优先级高德线程先于优先级低的线程执行完任务。通过setPriority()可以设置优先级。

常见方法

currentThread()

/**
 * Returns a reference to the currently executing thread object.
 *
 * @return  the currently executing thread.
 */
//得当当前正在执行的线程
public static native Thread currentThread();

public class RunOrStartThread extends Thread {

    public RunOrStartThread() {
        System.out.println("构造方法打印:" + Thread.currentThread().getName());
    }

    @Override
    public void run() {
        System.out.println("run方法打印: "  + Thread.currentThread().getName());
    }
}
//  测试
        RunOrStartThread runOrStartThread = new RunOrStartThread();
//        runOrStartThread.start();
//        I/System.out: 构造方法打印:main
//        I/System.out: run方法打印: Thread-142

//        runOrStartThread.run();
//        I/System.out: 构造方法打印:main
//        I/System.out: run方法打印: main

isAlive()
判断当前线程是否处于活动状态

sleep()
指定毫秒数内,暂停当前正在执行的线程。

getId()
获取线程的唯一标识。

yield方法
Yield()方法的作用是放弃当前CPU的资源,让给其他线程。

Java中有两种线程:
用户线程和守护线程(具有陪护功能,典型的守护线程垃圾回收器)。当进程中不存在非守护线程了,守护线程自动销毁。通过setDaemon()设置守护线程。

线程间通信

wait/notify

wait/notify:必须出现在同步方法或者同步代码块中;变量在特殊时刻需要特殊处理,避免CPU浪费。

wait:使当前线程进入预执行队列,直到接收到通知或者线程被中断为止。*具有释放锁的操作。

notify:随机恢复拥有同一对象锁的wait线程。notify并不马上释放锁直到synchronized代码执行完后才释放。

使用管道流实现线程通信

public class WriteData {

    public void writeMethod(PipedOutputStream out){
        try{
            Log.e("TAG", "write:");
            for (int i = 0 ; i < 300 ; i++){
                String outData = (i+1) + "";
                out.write(outData.getBytes());
                Log.e("TAG", "" + outData);
            }
            out.close();
        }catch (IOException e){
            e.printStackTrace();
        }
    }

}
public class ReadData {

    public void readMethod(PipedInputStream input) {
        try{
            Log.e("TAG", "read:");
            byte[] bytes = new byte[1024];
            int read = input.read(bytes);
            while(read != -1) {
                String s = new String(bytes, 0, read);
                Log.e("TAG", "" + s);
                read = input.read(bytes);
            }
            input.close();

        }catch (IOException e){
            e.printStackTrace();
        }
    }

}
public class WriteThread extends Thread {

    private WriteData data;
    private PipedOutputStream outputStream;

    public WriteThread(WriteData data, PipedOutputStream outputStream) {
        this.data = data;
        this.outputStream = outputStream;
    }

    @Override
    public void run() {
        super.run();
        data.writeMethod(outputStream);
    }
}
public class ReadThread extends Thread{

    private ReadData readData;
    private PipedInputStream inputStream;

    public ReadThread(ReadData readData, PipedInputStream inputStream) {
        this.readData = readData;
        this.inputStream = inputStream;
    }

    @Override
    public void run() {
        super.run();
        readData.readMethod(inputStream);
    }
}
//熟悉流
try{
    WriteData writeData = new WriteData();
    ReadData readData = new ReadData();
    PipedOutputStream pipedOutputStream = new PipedOutputStream();
    PipedInputStream pipedInputStream = new PipedInputStream();
    //使两个Stream之间产生通信连接
    pipedOutputStream.connect(pipedInputStream);

    ReadThread readThread = new ReadThread(readData, pipedInputStream);
    WriteThread writeThread = new WriteThread(writeData, pipedOutputStream);

    writeThread.start();
    Thread.sleep(500);
    readThread.start();
}catch (IOException e){
    e.printStackTrace();
} catch (InterruptedException e) {
    e.printStackTrace();
}

join方法

等待当前线程销毁。内部通过wait方法实现(线程终止时调用notifyAll方法)

public class MyThread extends Thread {

    public volatile static int count;

    private static void addCount(){
        for (int i = 0 ; i < 100 ; i ++){
            count ++;
        }
        Log.e("TAG", "count = " + count);
    }

    @Override
    public void run() {
        addCount();
    }
}
MyThread myThread = new MyThread();
myThread.start();

Log.e("TAG", "1111111111111111111");
Log.e("TAG", "222222222222222222");
Log.e("TAG", "3333333333333333333333");

//TAG: 1111111111111111111
//TAG: 222222222222222222
//TAG: 3333333333333333333333
//TAG: count = 100

//加入join后(join具有释放锁的作用)
//TAG: count = 100
//TAG: 1111111111111111111
//TAG: 222222222222222222
//TAG: 3333333333333333333333
上一篇下一篇

猜你喜欢

热点阅读