Android面试题线程篇,由本人整理汇总,后续将推出系列篇,如果喜欢请持续关注和推荐。
开启线程的三种方式?
java有三种创建线程的方式,分别是继承Thread类、实现Runable接口和使用线程池
线程池:
Android中常见的线程池有四种,FixedThreadPool、CachedThreadPool、ScheduledThreadPool、SingleThreadExecutor。
- FixedThreadPool线程池是通过Executors的new FixedThreadPool方法来创建。它的特点是该线程池中的线程数量是固定的。即使线程处于闲置的状态,它们也不会被回收,除非线程池被关闭。当所有的线程都处于活跃状态的时候,新任务就处于队列中等待线程来处理。注意,FixedThreadPool只有核心线程,没有非核心线程。
- CachedThreadPool线程池是通过Executors的newCachedThreadPool进行创建的。它是一种线程数目不固定的线程池,它没有核心线程,只有非核心线程,当线程池中的线程都处于活跃状态,就会创建新的线程来处理新的任务。否则就会利用闲置的线程来处理新的任务。线程池中的线程都有超时机制,这个超时机制时长是60s,超过这个时间,闲置的线程就会被回收。这种线程池适合处理大量并且耗时较少的任务。这里得说一下,CachedThreadPool的任务队列,基本都是空的。
- ScheduledThreadPool线程池是通过Executors的newScheduledThreadPool进行创建的,它的核心线程是固定的,但是非核心线程数是不固定的,并且当非核心线程一处于空闲状态,就立即被回收。这种线程适合执行定时任务和具有固定周期的重复任务。
- SingleThreadExecutor线程池是通过Executors的newSingleThreadExecutor方法来创建的,这类线程池中只有一个核心线程,也没有非核心线程,这就确保了所有任务能够在同一个线程并且按照顺序来执行,这样就不需要考虑线程同步的问题。
sleep(), wait()的区别
- sleep不释放同步锁,自动唤醒,需要try catch, wait释放同步锁,需要notify来唤醒
- sleep是线程的方法 wait是Object的方法
谈谈wait/notify关键字的理解
等待对象的同步锁,需要获得该对象的同步锁才可以调用这个方法,否则编译可以通过,但运行时会收到一个异常:IllegalMonitorStateException。
调用任意对象的 wait() 方法导致该线程阻塞,该线程不可继续执行,并且该对象上的锁被释放。
唤醒在等待该对象同步锁的线程(只唤醒一个,如果有多个在等待),注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。
调用任意对象的notify()方法则导致因调用该对象的 wait()方法而阻塞的线程中随机选择的一个解除阻塞(但要等到获得锁后才真正可执行)。
Thread中的start()和run()方法有什么区别
start()方法是用来启动新创建的线程,而start()内部调用了run()方法,这和直接调用run()方法是不一样的,如果直接调用run()方法,
如何控制某个方法允许并发访问线程的个数?
- semaphore.acquire() 请求一个信号量,这时候的信号量个数-1(一旦没有可使用的信号量,也即信号量个数变为负数时,再次请求的时候就会阻塞,直到其他线程释放了信号量)
- semaphore.release() 释放一个信号量,此时信号量个数+1
什么导致线程阻塞?线程如何关闭?
阻塞式方法是指程序会一直等待该方法完成期间不做其他事情,ServerSocket的accept()方法就是一直等待客户端连接。这里的阻塞是指调用结果返回之前,当前线程会被挂起,直到得到结果之后才会返回。此外,还有异步和非阻塞式方法在任务完成前就返回。
一种是调用它里面的stop()方法
另一种就是你自己设置一个停止线程的标记 (推荐这种)
如何保证线程安全?
- 1.synchronized;
- 2.Object方法中的wait,notify;
- 3.ThreadLocal机制 来实现的。
如何实现线程同步?
- 1、synchronized关键字修改的方法。
- 2、synchronized关键字修饰的语句块
- 3、使用特殊域变量(volatile)实现线程同步
线程间操作List
List list = Collections.synchronizedList(new ArrayList());
谈谈对Synchronized关键字,类锁,方法锁,重入锁的理解
java的对象锁和类锁:java的对象锁和类锁在锁的概念上基本上和内置锁是一致的,但是,两个锁实际是有很大的区别的,对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态方法或者一个类的class对象上的。我们知道,类的对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定实例方法和静态方法的区别的
synchronized 和volatile 关键字的区别
- 1.volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
- 2.volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的
- 3.volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性
- 4.volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
- 5.volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化
ReentrantLock 、synchronized和volatile比较
java在过去很长一段时间只能通过synchronized关键字来实现互斥,它有一些缺点。比如你不能扩展锁之外的方法或者块边界,尝试获取锁时不能中途取消等。Java 5 通过Lock接口提供了更复杂的控制来解决这些问题。 ReentrantLock 类实现了 Lock,它拥有与 synchronized 相同的并发性和内存语义且它还具有可扩展性。
线程同步的问题,常用的线程同步
- 1.sycn:保证了原子性、可见性、有序性
- 2.锁:保证了原子性、可见性、有序性
- 1.自旋锁:可以使线程在没有取得锁的时候,不被挂起,而转去执行一个空循环。
- 1.优点:线程被挂起的几率减少,线程执行的连贯性加强。用于对于锁竞争不是很激烈,锁占用时间很短的并发线程。
- 2.缺点:过多浪费CPU时间,有一个线程连续两次试图获得自旋锁引起死锁
- 2.阻塞锁:没得到锁的线程等待或者挂起,Sycn、Lock
- 3.可重入锁:一个线程可多次获取该锁,Sycn、Lock
- 4.悲观锁:每次去拿数据的时候都认为别人会修改,所以会阻塞全部其他线程 Sycn、Lock
- 5.乐观锁:每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。cas
- 6.显示锁和内置锁:显示锁用Lock来定义、内置锁用synchronized。
- 7.读-写锁:为了提高性能,Java提供了读
- 3.volatile
- 1.只能保证可见性,不能保证原子性
- 2.自增操作有三步,此时多线程写会出现问题
- 4.cas
- 1.操作:内存值V、旧的预期值A、要修改的值B,当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做并返回false。
- 2.解释:本地副本为A,共享内存为V,线程A要把V修改成B。某个时刻线程A要把V修改成B,如果A和V不同那么就表示有其他线程在修改V,此时就表示修改失败,否则表示没有其他线程修改,那么把V改成B。
- 3.局限:如果V被修改成V1然后又被改成V,此时cas识别不出变化,还是认为没有其他线程在修改V,此时就会有问题
- 4.局限解决:将V带上版本。
- 5.线程不安全到底是怎么回事:
- 1.一个线程写,多个线程读的时候,会造成写了一半就去读
- 2.多线程写,会造成脏数据
有三个线程T1,T2,T3,怎么确保它们按顺序执行?
在多线程中有多种方法让线程按特定顺序执行,你可以用线程类的join()方法在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。为了确保三个线程的顺序你应该先启动最后一个(T3调用T2,T2调用T1),这样T1就会先完成而T3最后完成。
Android的多线程模型有哪几种?
Android提供了四种常用的操作多线程的方式,分别是:
- Handler+Thread
- AsyncTask
- ThreadPoolExecutor
- IntentService
Android多线程的实现方式有哪些?
- Thread & AsyncTask
- Thread 可以与Loop 和 Handler 共用建立消息处理队列
- AsyncTask 可以作为线程池并行处理多任务
多线程的优劣
使用多进程显而易见的好处就是分担主进程的内存压力。我们的应用越做越大,内存越来越多,将一些独立的组件放到不同的进程,它就不占用主进程的内存空间了。当然还有其他好处,有心人会发现Android后台进程里有很多应用是多个进程的,因为它们要常驻后台,特别是即时通讯或者社交应用,不过现在多进程已经被用烂了。典型用法是在启动一个不可见的轻量级私有进程,在后台收发消息,或者做一些耗时的事情,或者开机启动这个进程,然后做监听等。还有就是防止主进程被杀守护进程,守护进程和主进程之间相互监视,有一方被杀就重新启动它。 坏处的话,多占用了系统的空间,大家都这么用的话系统内存很容易占满而导致卡顿。消耗用户的电量。应用程序架构会变复杂,应为要处理多进程之间的通信。这里又是另外一个问题了。
AsyncTask的工作原理
AsyncTask是Android本身提供的一种轻量级的异步任务类。它可以在线程池中执行后台任务,然后把执行的进度和最终的结果传递给主线程更新UI。实际上,AsyncTask内部是封装了Thread和Handler。虽然AsyncTask很方便的执行后台任务,以及在主线程上更新UI,但是,AsyncTask并不合适进行特别耗时的后台操作,对于特别耗时的任务,个人还是建议使用线程池。
HandlerThread是什么
MessageQueue + Looper + Handler
IntentService是什么
含有HandlerThread的Service,可以多次startService()来多次在子线程中进行 onHandlerIntent()的调用。
线程间如何通信
我们知道线程是CPU调度的最小单位。在Android中主线程是不能够做耗时操作的,子线程是不能够更新UI的。而线程间通信的方式有很多,比如广播,Eventbus,接口回掉,在Android中主要是使用handler。handler通过调用sendmessage方法,将保存消息的Message发送到Messagequeue中,而looper对象不断的调用loop方法,从messageueue中取出message,交给handler处理,从而完成线程间通信。
如果喜欢本文章,请关注Android高级编程微信公众号,有更多精彩内容提供: