• android开发系列之多线程


          今天在这篇博客里面,我只想谈谈自己对程序开发里面避无可避的一个问题-多线程的一些看法与思考。

          其实说到多线程这个名称相信只要接触过软件这个行业的人都已经耳熟能详了,但是如果被问到到底什么才是多线程呢?为什么我们会需要多线程呢?多线程又会造成什么副作用呢?其实这些都应该是值得我们深入思考的一些问题,因为在多线程的环境里面,往往会发生一些意想不到的事情。下面让我们来针对android平台来具体看看多线程应该是怎么一回事呢?其实在android里面,系统已经为我们提供了一些封装好的多线程api,比如runable接口,AsyncTask后台任务等。

          让我们先来看看一段代码,这段代码是比较常规的创建、开启一个线程的方法。

    public class TestThread extends Thread {
    
        @Override
        public void run() {
            //do your something
        }
    }
    

     上面方法只是定义好了一个线程,然后我们应该调用下面的方法运营它。

    private void runMyThread(){
         new TestThread().start();
    }
    

     这样的话,我们就相当于已经开启了一个线程。所以只要不手动中断这个线程的话,那么该线程就会在App处于运行态时一直跑。所以这个时候,如果我们有什么比较耗时的操作的话,我们就可以放在run方法里面执行了。下面就让我们来看看Thread里面的源码,看看为什么Thread调用start方法就会运行,为什么每个线程开启之后都会循环执行里面的run方法呢?有没有什么办法去中断一个线程呢?

          首先我们来看看Thread的类定义,如下:

    public class Thread implements Runnable{
    }
    

     可以看到Thread其实也是从Runable继承而来,那么什么是Runable呢?让我们稍后讲。一进到Thread类定义里面,给我们最直观的感受就是“volatile”,“ThreadLocal”,还有各种的Thread构造方法。我们可以看到在每个Thread构造方法里面都调用了create方法,相信看到这个名字大家就应该能够知道它是干什么用的吧,让我们截出create的源码来看看:

    private void create(ThreadGroup group, Runnable runnable, String threadName, long stackSize) {
            Thread currentThread = Thread.currentThread();
            if (group == null) {
                group = currentThread.getThreadGroup();
            }
    
            if (group.isDestroyed()) {
                throw new IllegalThreadStateException("Group already destroyed");
            }
    
            this.group = group;
    
            synchronized (Thread.class) {
                id = ++Thread.count;
            }
    
            if (threadName == null) {
                this.name = "Thread-" + id;
            } else {
                this.name = threadName;
            }
    
            this.target = runnable;
            this.stackSize = stackSize;
    
            this.priority = currentThread.getPriority();
    
            this.contextClassLoader = currentThread.contextClassLoader;
    
            // Transfer over InheritableThreadLocals.
            if (currentThread.inheritableValues != null) {
                inheritableValues = new ThreadLocal.Values(currentThread.inheritableValues);
            }
    
            // add ourselves to our ThreadGroup of choice
            this.group.addThread(this);
        }
    

    从上面的源码我们可以很清楚的看到create方法里面就是设置线程所属的ThreadGroup,ThreadName,StackSize,但是有一点需要特别注意就是Runable对象,它是用来干什么的呢?我们通过代码查找,发现它主要是用在run方法里面,代码如下:

    public void run() {
            if (target != null) {
                target.run();
            }
        }
    

     我们可以看到Thread里面的run方法,其实是调用了Runable里面的方法。也就是说你可以在Thread实例化的时候,传进来一个Runable对象,这个时候就能执行到里面的run方法了。但是别急,如果你以为这样就能够真正创建一个线程的话,那么就错了。我们可以再看看start方法,也许你就能够明白了。

    public synchronized void start() {
            checkNotStarted();
    
            hasBeenStarted = true;
    
            nativeCreate(this, stackSize, daemon);
        }
    
    private native static void nativeCreate(Thread t, long stackSize, boolean daemon);
    

     看到了吗?其实在nativeCreate方法里面才会利用create方法里面设置的参数进行真正进行创建过程。现在让我们回到原来那个问题上,为什么线程创建之后,会一直执行run方法呢?具体怎么循环调用的,我在源码里面没有找到,也许是在c层调的吧。然后我们还可以看到常用的sleep方法,源码如下:

    public static void sleep(long millis, int nanos) throws InterruptedException {
            if (millis < 0) {
                throw new IllegalArgumentException("millis < 0: " + millis);
            }
            if (nanos < 0) {
                throw new IllegalArgumentException("nanos < 0: " + nanos);
            }
            if (nanos > 999999) {
                throw new IllegalArgumentException("nanos > 999999: " + nanos);
            }
    
            // The JLS 3rd edition, section 17.9 says: "...sleep for zero
            // time...need not have observable effects."
            if (millis == 0 && nanos == 0) {
                // ...but we still have to handle being interrupted.
                if (Thread.interrupted()) {
                  throw new InterruptedException();
                }
                return;
            }
    
            long start = System.nanoTime();
            long duration = (millis * NANOS_PER_MILLI) + nanos;
    
            Object lock = currentThread().lock;
    
            // Wait may return early, so loop until sleep duration passes.
            synchronized (lock) {
                while (true) {
                    sleep(lock, millis, nanos);
    
                    long now = System.nanoTime();
                    long elapsed = now - start;
    
                    if (elapsed >= duration) {
                        break;
                    }
    
                    duration -= elapsed;
                    start = now;
                    millis = duration / NANOS_PER_MILLI;
                    nanos = (int) (duration % NANOS_PER_MILLI);
                }
            }
        }
    

     我们可以看到在java这层只是做了一些时间上面的预设判断,真正的sleep实现是调用c层代码:

    private static native void sleep(Object lock, long millis, int nanos);
    

     最后让我们再来看看线程中断的方法:

    public void interrupt() {
            // Interrupt this thread before running actions so that other
            // threads that observe the interrupt as a result of an action
            // will see that this thread is in the interrupted state.
            nativeInterrupt();
    
            synchronized (interruptActions) {
                for (int i = interruptActions.size() - 1; i >= 0; i--) {
                    interruptActions.get(i).run();
                }
            }
        }
    
     private native void nativeInterrupt();
    

     看到上面的代码其实有一点不解的就是,是不是意味着我中断当前的一个线程,系统就会默认启动后一个线程呢?

           好了,接下来我们可以再看看Runable,其实可以发现它就是一个接口,里面提供了一个run方法,代码如下:

    public interface Runnable {
    
        /**
         * Starts executing the active part of the class' code. This method is
         * called when a thread is started that has been created with a class which
         * implements {@code Runnable}.
         */
        public void run();
    }

          最后我们来看看AsyncTask方法,它的特点是什么?我们应该怎么来使用它呢?首先我们来看看在android代码里面通常的使用方法是:

    public class TestAsyncTask extends AsyncTask<String,Object,String> {
        @Override
        protected String doInBackground(String... params) {
            return null;
        }
    
        @Override
        protected void onPostExecute(String s) {
            super.onPostExecute(s);
        }
    
        @Override
        protected void onProgressUpdate(Object... values) {
            super.onProgressUpdate(values);
        }
    }
    

     定义一个类,然后继承AsyncTask复写里面的三个方法。下面就让我们来具体看看AsyncTask的类定义:

    public abstract class AsyncTask<Params, Progress, Result> {
    }
    

     我们可以看到AsyncTask是一个抽象类,同时利用泛型的方式定义三个参数,分别代表传入的参数、执行过程的进度、执行之后的结果。那么在AsyncTask里面又是怎样调用doInBackground方法的呢?这就要回想一下,当我们定义好一个AsyncTask的时候,是怎样运行它的呢?代码如下:

    private void testMyAsyncTask(){
            new TestAsyncTask().execute(url);
    }
    

     所以我们只要看看execute方法里面是不是有调用就知道了。

    @MainThread
        public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
                Params... params) {
            if (mStatus != Status.PENDING) {
                switch (mStatus) {
                    case RUNNING:
                        throw new IllegalStateException("Cannot execute task:"
                                + " the task is already running.");
                    case FINISHED:
                        throw new IllegalStateException("Cannot execute task:"
                                + " the task has already been executed "
                                + "(a task can be executed only once)");
                }
            }
    
            mStatus = Status.RUNNING;
    
            onPreExecute();
    
            mWorker.mParams = params;
            exec.execute(mFuture);
    
            return this;
        }
    

     最终它会调用到上面的方法实现我们复写的三个方法实现。

           好了,这篇博客就到这里。理解不够深入、不到的地方,欢迎拍砖!

  • 相关阅读:
    LINUX VNC配置[转]
    win7下,两台笔记本内置的无线局域网卡共享上网
    修改Linux和aix系统为北京时区
    ubuntu 9.10 下安装ORACLE 出错SP20750: You may need to set ORACLE_HOME to your Oracle software directory
    安装vim7.1
    select 语句的处理顺序
    批量从数据库是提取数据,并显示出来。
    输入四个字符串然后按大到后输出。
    shell 批量修改指定的文件。
    unix自动登录Telnet,实现查看多台服务器硬盘及数据表空间使用情况
  • 原文地址:https://www.cnblogs.com/xiaocai20091687/p/android-xiaocai-thread.html
Copyright © 2020-2023  润新知