面经Android

Java多线程与Android多线程

2017-12-02  本文已影响236人  安安zoe

Java 多线程

线程和进程的区别

线程和线程池

线程创建/启动的三种方法

public class FirstThread extends Thread{
    private int i;
    @Override
    public void run() {
        for(i=0;i<10;i++) 
            System.out.println(getName()); // 继承自Thread 
    }
    
    public static void main(String[] args) {    
        new FirstThread().start();
        new FirstThread().start(); // 注意启动线程需要用Start
    }
}
public class SecondThread implements Runnable{
    private int i;
    @Override
    public void run() {
        for(;i<10;i++) {
            System.out.println(Thread.currentThread().getName() + " "+ i);
        }
    }
    
    public static void main(String[] args) {
        SecondThread targetRunnable = new SecondThread();
        new Thread(targetRunnable,"线程1").start();
        new Thread(targetRunnable).start();
    }
}
public class ThridThread {
    public static void main(String[] args) {
        
        // lambda 表达式 + functionInterface 类型转换
        // Callbable: 有返回值
        FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)()->{
            int i =0;
            for(;i<100;i++) {
                System.out.println(Thread.currentThread().getName() + " "+ i);
            }
            return i;
    });
        
        new Thread(task,"有返回值的线程").start();
        
        try {
            System.out.println("子线程的返回值"+task.get());
        }catch(Exception e) {
            e.printStackTrace();
        }

 }
}

线程的生命周期

屏幕快照 2017-12-01 上午11.58.30.png 屏幕快照 2017-12-01 下午2.19.31.png

注意:抢占式策略系统:系统会给每个执行的线程一个小的时间段来处理任务,当该时间段用完之后,系统会剥夺该线程所占用的资源,让其他线程获得执行的机会。在系统调度时,还会考虑到线程的优先级问题。

线程控制

public class SleepThread {
    public static void main(String[] args) throws Exception{ // 注意异常
        for(int i =0;i<5;i++) {
            System.out.println("当前时间"+new Date());
            Thread.sleep(1000);
        }       
    }
}

线程同步

public class DrawThread extends Thread {
    private Account account;
    private double drawaccout;
    
    public DrawThread(String name,Account account,double drawaccount) {
        super(name);
        this.account = account;
        this.drawaccout= drawaccount;
    }
    
    public void run() {
        synchronized(account) {
            if(account.getBlance()>=drawaccount) {
                System.out.println(getName()+"取钱成功");
                try {
                    Thread.sleep(1);
                }catch(InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
    public synchronized void draw(double amount) {
        ……
    }
class X{
        private final ReentrantLock lock = new ReentrantLock();
        
        //需要定义线程安全的方法
        public void foo() {
            lock.lock();//加锁
            try {
                // 需要保证线程安全的代码
            }
            finally {
                lock.unlock();//使用finally块保证释放锁
            }
        }
    }

线程安全是以牺牲程序运行效率为代价的,因此在注意线程安全的同时,也要注意不要滥用锁和同步方法,尽量只对那些会改变竞争资源的方法进行同步。同时还要根据单线程和多线程运行环境来提供线程不安全和线程安全两种版本,JDK提供的StringBuilder,StringBuffer就是一个例子。

线程通信

private final Lock lock = new ReentrantLock();
    // Condition实例绑定在一个Lock对象上
    private final Condition cond = lock.newCondition();
    
    public void Draw(double drawamount) {
        lock.lock();
        try {
            if(!flag)
                cond.await();//导致当前线程等待
            else {
                // ...
                cond.signalAll();// 唤醒其他线程
            }
        }catch(InterruptedException e) {
            e.printStackTrace();
        }
        finally {
            lock.unlock();
        }
    }

线程池

public class Testjava{
    public static void main(String[] args)
    throws Exception{
        ExecutorService pool = Executors.newFixedThreadPool(6);
        Runnable target = ()->{
            for(int i=0;i<100;i++) {
            System.out.println(Thread.currentThread().getName()
                        + "的i值为:"+ i);
            }
        };      
        // 向线程池中提交两个线程
        pool.submit(target);
        pool.submit(target);        
        pool.shutdown();
    }
}

【Java8源码分析】线程池-Executor与ExecutorService的全面剖析

线程相关类

    class Accout{
        private ThreadLocal<String> name = new ThreadLocal<>();
        public Accout(String str) {
            this.name.set(str);
        }
        public String getname() {
            return name.get();
        }
        public void setname(String str) {
            this.name.set(str);
        }
    }

注意:ThreadLocal与其他同步机制都是为了解决访问同一资源冲突问题而出现的,但是侧重的领域不同,同步机制为实现多个线程对相同资源访问的并发安全性,ThreadLocal则是隔离多个线程之间的数据共享,从而避免竞争。


Android多线程

Android中的多线程本质上也是Java的多线程,同时添加了一些不同的特性和使用的场景。其中,最主要的一个区别就是Android中主线程和子线程中的区分,Android中的主线程是UI线程,负责运行四大组件并与用户实现交互,需要保持较高的反应速度,所以主线程不允许进行耗时的操作(比如说网络请求和访问),否则容易出现ANR现象,子线程则负责处理 一些耗时的任务,而如果子线程中想要实现对UI的操作,则需要通过Android的handler消息机制。

<u>为什么子线程中不允许对UI进行操作呢</u>
因为Android的UI控件并不是线程安全,多线程的并发访问会带来UI控件的不可预期的状态,且考虑到加锁机制会带来性能上的问题,因此Android在设计初期就禁止子线程处理UI。UI操作时ViewRootImpl会对操作者所在的线程进行checkThread,如果非主线程,会抛出CalledFromWrongThreadException。

那么Android除了java原生的Thread/Runnable等线程形态,还有哪些包装过了的有特点的线程形式?

AsyncTask

很棒的参考:你真的了解AsyncTask

// 三个泛型参数 不需要传递参数时,可以用void代替
public abstract class AsyncTask<Params,Progress,Result>
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
    //在线程池中执行 该方法必须返回计算结果给onPostExecute()
    protected Long doInBackground(URL... urls) {
        int count = urls.length;
        long totalSize = 0;
        for (int i = 0; i < count; i++) {
            totalSize += Downloader.downloadFile(urls[i]);
            // 可以使用该方法返回任务的进度,该方法会调用onProgressUpdate()
            publishProgress((int) ((i / (float) count) * 100));
            // Escape early if cancel() is called
            if (isCancelled()) break;
        }
        return totalSize;
    }
    // 被主线程调用执行 因此这里可以有UI操作
    protected void onProgressUpdate(Integer... progress) {
        setProgressPercent(progress[0]);
    }
    // 在主线程中调用执行 任务执行结束后,会调用该方法,因此这里可以有UI操作
    protected void onPostExecute(Long result) {
        showDialog("Downloaded " + result + " bytes");
    }
}

// 主线程调用execute方法,执行任务前,会调用[1] onPreExecute()完成一些准备工作
// onPreExecute()是在主线程中执行
new DownloadFilesTask().execute(url1, url2, url3);
// 也可以调用cancel来取消任务的执行
public abstract class AsyncTask<Params, Progress, Result> {
    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();//CPU数
    private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
    private static final int KEEP_ALIVE = 1;

    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);

        public Thread newThread(Runnable r) {
            return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
        }
    };

    private static final BlockingQueue<Runnable> sPoolWorkQueue =
            new LinkedBlockingQueue<Runnable>(128);

    public static final Executor THREAD_POOL_EXECUTOR
            = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                    TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
}
// 核心线程数 = CPU数+1
// 最大线程数 = CPU数*2 + 1
// 非核心线程的超时时间为1秒
// 任务队列的容量为128

Android 开发手册写明:AsyncTasks should ideally be used for short operations (a few seconds at the most.) If you need to keep threads running for long periods of time, it is highly recommended you use the various APIs provided by thejava.util.concurrentpackage such as Executor,ThreadPoolExecutorand FutureTask

HandlerThread

public void run() {
    mTid = Process.myTid();
    Looper.prepare();
    synchronized(this) {
        mLooper = Looper.myLooper();
        notifyAll();
    }
    Process.setThreadPriority(mPriority);
    onLooperPrepared();
    Looper.loop();
    mTid =-1;
    
}

IntentService

ThreadPoolExecutor

public ThreadPoolExecutor(int corePoolSize,
                      int maximumPoolSize,
                      long keepAliveTime,
                      TimeUnit unit,
                      BlockingQueue<Runnable> workQueue,
                      ThreadFactory threadFactory,
                      RejectedExecutionHandler handler) {
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}
  • corePoolSize: 线程池的核心线程数,默认情况下, 核心线程会在线程池中一直存活, 即使处于闲置状态. 但如果将allowCoreThreadTimeOut设置为true的话, 那么核心线程也会有超时机制, 在keepAliveTime设置的时间过后, 核心线程也会被终止.
  • maximumPoolSize: 最大的线程数, 包括核心线程, 也包括非核心线程, 在线程数达到这个值后,新来的任务将会被阻塞.
  • keepAliveTime: 超时的时间, 闲置的非核心线程超过这个时长,讲会被销毁回收, 当allowCoreThreadTimeOut为true时,这个值也作用于核心线程.
  • unit:超时时间的时间单位.
  • workQueue:线程池的任务队列, 通过execute方法提交的runnable对象会存储在这个队列中.
  • threadFactory: 线程工厂, 为线程池提供创建新线程的功能.
  • handler: 任务无法执行时,回调handler的rejectedExecution方法来通知调用者.
  1. 如果线程池中线程的数目少于corePoolSize,就算线程池中有其他的没事做的核心线程,线程池还是会重新创建一个核心线程;直到核心线程数目到达corePoolSize(常驻线程就位)

  2. 如果线程池中线程的数目大于或者等于corePoolSize,但是工作队列workQueue没有满,那么新的任务会放在队列workQueue中,按照FIFO的原则依次等待执行;(当有核心线程处理完任务空闲出来后,会检查这个工作队列然后取出任务默默执行去)

  3. 如果线程池中线程数目大于等于corePoolSize,并且工作队列workQueue满了,但是总线程数目小于maximumPoolSize,那么直接创建一个线程处理被添加的任务。

  4. 如果工作队列满了,并且线程池中线程的数目到达了最大数目maximumPoolSize,那么就会用最后一个构造参数handler处理;**默认的处理方式是直接丢掉任务,然后抛出一个异常。

上一篇 下一篇

猜你喜欢

热点阅读