美文网首页
Java多线程-基础篇

Java多线程-基础篇

作者: erki_stwee | 来源:发表于2018-10-17 20:27 被阅读32次
    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
    

    相关文章

      网友评论

          本文标题:Java多线程-基础篇

          本文链接:https://www.haomeiwen.com/subject/uoaczftx.html