多线程与Android线程性能优化(一)
基本概念
1、CPU核心数和线程数的关系
首先我们做开发的时候,买台式或者笔记本的时候,都会看几核几线程,例如CPU是4核8线程等等。
核心数就是一个计算机的处理器,在计算机的早期,是没有多核这个概念的,引入多核的概念是因为按照摩尔定律,计算机芯片里面晶体管的密度每18个月会翻一番,但是翻到我们现在这个地步时,CPU的晶体管的制程到了3nm,到了极限,翻不动了(这个原因涉及到量子物理里面的量子隧穿),为了继续提高转速,提出了多核心的概念,也就是说在一块物理芯片上集成多个CPU,每个CPU是单独的,也单独执行多个进程或线程。
核心数和线程数关系是1:1对应,也就是4核CPU会同时运行4个线程,但是我们同时运行数会达到8个?这是怎么做到的呢?这个就运用到Intel提供的超线程技术,把一个物理CPU通过超线程技术模拟成两个逻辑CPU,也就是说一个物理核心对应两个逻辑核心。
2、CPU时间片轮转机制
RR调度,即使我只有1个CPU,100个线程,我们感觉到100个线程在同时运行一样,其实就是我们把CPU的运行时间进行切片,比如说每一片就是5ms,把100个线程全部执行一遍要花500ms,500ms对于我们来说基本是无感知的,所以我们平时开发过程中,我们感觉CPU仅为我们的线程而生,这就是所谓的时间片轮转机制。
3、什么是进程和线程
按照标准的学术定义,进程是操作系统在运行时进行资源分配(CPU、内存、IO等)的最小单位。
线程是CPU调度的最小单位,从概念上说,进程 > 线程。
例如开发一个App,首先会启动MainThread线程(在Android中又称为UI Thread,主要负责UI的绘制),如果在UI Thread线程中进行耗时操作,会导致APP发生anr,所以我们又创建了WorkThread。
OS:Linux 1000 Windows 2000
4、澄清并行和并发
并行:假如一个高速公路是四车道,说明这条高速公路的并行能力是4辆车。其实并行简单理解就是同时执行。
并发:并发一定是和时间相关的,脱离了单位时间是毫无意义的,一般我们说并发是在单位时间内的并发量是多少。
两者区别:一个是交替执行,一个是同时执行。
高并发编程的意义、好处和注意事项
由于多核多线程的CPU的诞生,多线程、高并发的编程越来越受重视和关注。多线程可以给程序带来如下好处。
(1)充分利用CPU的资源
从上面的CPU的介绍,可以看的出来,现在市面上没有CPU的内核不使用多线程并发机制的,特别是服务器还不止一个CPU,如果还是使用单线程的技术做思路,明显就out了。因为程序的基本调度单元是线程,并且一个线程也只能在一个CPU的一个核的一个线程跑,如果你是个i3的CPU的话,最差也是双核心4线程的运算能力:如果是一个线程的程序的话,那是要浪费3/4的CPU性能:如果设计一个多线程的程序的话,那它就可以同时在多个CPU的多个核的多个线程上跑,可以充分地利用CPU,减少CPU的空闲时间,发挥它的运算能力,提高并发量。
就像我们平时坐地铁一样,很多人坐长线地铁的时候都在认真看书,而不是为了坐地铁而坐地铁,到家了再去看书,这样你的时间就相当于有了两倍。这就是为什么有些人时间很充裕,而有些人老是说没时间的一个原因,工作也是这样,有的时候可以并发地去做几件事情,充分利用我们的时间,CPU也是一样,也要充分利用。
(2)加快响应用户的时间
比如我们经常用的迅雷下载,都喜欢多开几个线程去下载,谁都不愿意用一个线程去下载,为什么呢?答案很简单,就是多个线程下载快啊。
我们在做程序开发的时候更应该如此,特别是我们做互联网项目,网页的响应时间若提升1s,如果流量大的话,就能增加不少转换量。做过高性能web前端调优的都知道,要将静态资源地址用两三个子域名去加载,为什么?因为每多一个子域名,浏览器在加载你的页面的时候就会多开几个线程去加载你的页面资源,提升网站的响应速度。多线程,高并发真的是无处不在。
(3)可以使你的代码模块化、异步化、简单化
例如我们在做 Android程序开发的时候,主线程的UI展示部分是一块主代码程序部分,但是UI上的按钮用相应事件的处理程序就可以做个单独的模块程序拿出来。这样既增加了异步的操,又使程序模块化,清晰化和简单化。
时下最流行的异步程序处理机制,正是多线程、并发程序最好的应用例子。
多线程应用开发的好处还有很多,大家在日后的代码编写过程中可以慢慢体会它的魅力。
多线程程序需要注意事项
(1)线程之间的安全性
从前面的章节中我们都知道,在同一个进程里面的多线程是资源共享的,也就是都可以访问同一个内存地址当中的一个变量。例如:若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的:若有多个线程同时执行写操作,一般都需要考虑线程同步,否则就可能影响线程安全。
(2)线程之间的死循环过程
为了解决线程之间的安全性引入了Java的锁机制,而一不小心就会产生Java线程死锁的多线程问题,因为不同的线程都在等待那些根本不可能被释放的锁,从而导致所有的工作都无法完成。假设有两个线程,分别代表两个饥饿的人,他们必须共享刀叉并轮流吃饭。他们都需要获得两个锁:共享刀和共享叉的锁。
假如线程A获得了刀,而线程B获得了叉。线程A就会进入阻塞状态来等待获得叉,而线程B则阻塞来等待线程A所拥有的刀。这只是人为设计的例子,但尽管在运行时很难探测到,这类情况却时常发生
(3)线程太多了会将服务器资源耗尽形成死机宕机
线程数太多有可能造成系统创建大量线程而导致消耗完系统内存以及CPU的“过渡切换”,造成系统的死机,那么我们该如何解决这类问题呢?
某些系统资源是有限的,如文件描述符。多线程程序可能耗尽资源,因为每个线程都可能希望有一个这样的资源。如果线程数相当大,或者某个资源的侯选线程数远远超过了可用的资源数则最好使用资源池。一个最好的示例是数据库连接池。只要线程需要使用一个数据库连接,它就从池中取出一个,使用以后再将它返回池中。资源池也称为资源库。
启动线程的三种方式
1、X extends Thread;,然后X.run
2、X implements Runnable;然后交给Thread运行
3、X implements Callable;然后交给Thread运行
public class NewThread {
/* 继承Thread类*/
private static class UserThread extends Thread {
@Override
public void run() {
super.run();
System.out.println("I am extends Thread");
}
}
/* 实现Runnable接口*/
private static class UserRun implements Runnable {
@Override
public void run() {
System.out.println("I am implements Runnable");
}
}
/* 实现Callable接口 */
private static class UserCall implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("I am implements Callable");
return "callresult";
}
}
public static void main(String[] args) {
UserThread userThread = new UserThread();
userThread.start();
UserRun userRun = new UserRun();
new Thread(userRun).start();
UserCall userCall = new UserCall();
//用FutureTask对userCall进行封装
FutureTask<String> futureTask = new FutureTask<>(userCall);
new Thread(futureTask).start();
try {
System.out.println(futureTask.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}
第1、2方式有一个缺陷就是:在执行完任务之后无法获取执行结果,从java1.5开始,提供了Callable和Future,通过他们可以在执行完任务后得到任务的执行结果。
Callable、Future和FutureTask
Runnable是一个接口,在它里面只声明了一个run()方法,由于run()方法返回值为void类型,所以在执行完任务之后无法返回任何结果。
Callable位于java.util.concurrent包下,它是一个泛型接口,只不过它只声明了一个方法叫call(),call的返回值就是传递过来的泛型V。
Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。
因为Future只是一个接口,无法用它来创建对象,于是有了它的实现类:FutureTask
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
public class FutureTask<V> implements RunnableFuture<V> {
......
}
可以看到,FutureTask类实现了RunnableFuture接口,RunnableFuture接口继承了Runnable接口和Future接口,所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
要new一个FutureTask的实例,有两种方法:
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
深入理解run()和start()方法
先说结论:run方法其实就是一个普通类的普通成员方法,可以重复执行,也可以单独调用。
start()方法是真真正正和操作系统挂起钩的方法,只有执行了start()方法后,才实现了真正意义上的启动线程。
start()方法让一个线程进入就绪队列等待分配cpu,分到cpu后才调用实现的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".
*/
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. */
group.add(this);
boolean started = false;
try {
start0();
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 */
}
}
}
让两个线程有顺序的执行可以调用join()方法。
上一篇: windows 7 技巧
下一篇: Windows解决端口占用