一.对线程的理解:
1. 线程是进程中的一个顺序执行流,在程序中表现为一个对象,Thread类;多个Thread类对象可以并发执行,线程内部的多个操作肯定是顺序执行的。
2.进程:进程可以理解为正在运行的程序,多个进程可以并发执行,一个进程可以启动多个线程。
3.线程并发执行:
(1)如果计算机中只有一个cpu,那么系统每次只会执行一个线程,获得cpu时间片的线程会执行,随后其他线程抢占cpu,获得时间片继而执行其他线程,所谓并发其实质是在某个时间节点上还是一个线程被执行,并发效果体现在cpu在多个线程之中来回切换。
(2)如果计算机中有多个cpu,那么系统会实现同时执行多个线程;
(3)在微观体现上:多个线程在顺序执行;在宏观体现上:以肉眼观看,多个进程并发执行;
4.多线程的应用场合:当我们需要处理多个任务:如下载多个文件:音乐,电影等;并发加载多张图片中可以考虑使用多线程;
5.使用多线程的优势和劣势:
①优势:提高了执行效率,用户体验更流畅;
②劣势:数据的安全不好控制,业务的调试开发难度加大;
二.线程的创建启动及运行:
1.java中线程对象是Thread类型的,线程的创建需要实例化Thread对象;Thread类的构造方法有Thread(){}和Thread(Runnable run){};构造方法中重写run()方法;
2.线程的启动需要调用Thread类的对象的start()方法;注意:启动不代表运行,而是等待分配cpu状态;
new Thread(){ @Override public void run() { } }.start(); new Thread(new Runnable() { @Override public void run() { } }){ }.start();
3.运行:当线程获得cpu时,会调用Thread对象的run()方法;注意!new Rundable(){public void run(){}}.start;中Rundable类的对象的run()方法底层调用的是所在线程的run()方法;
三.主线程和工作线程:
1.主线程:java中运行在main方法中的线程称之为主线程;而安卓中运行在onCreate()方法中的线程为主线程也称之为UI线程;
2.工作线程:在主线程之下的实例化的线程称之为工作线程;
3.主线程与工作线程之间的协作:安卓中UI操作必须运行在主线程之中,而工作线程一般执行一些耗时操作比如下载,加载图片等;
四.线程注意问题:
1.线程的运行存在不确定性,线程的运行自己不能控制而是有操作系统控制;何时分配cpu,何时执行该线程;
2.方法运行在哪个线程取决于对哪个线程进行了调用;
3.一个对象的不同方法可以运行在不同线程;
五.java中线程对象状态及相关方法:
1.线程对象的状态:
①新建状态:new Thread(){}
②就绪状态:调用线程对象的start()方法;
③启动状态:获得cpu,执行线程对象的run()方法;
④阻塞状态:sleep(), 调用对象锁的wait()方法;注意!!一般不应让主线程处于阻塞状态而是选择工作线程让其等待;是为了更好地用户体验;
⑤死亡状态:run()方法运行结束,该线程处于死亡状态;
2.线程相关方法:
(1)start():启动线程;此时线程处于就绪状态,等待获得cpu执行run()方法;
(2)run():线程运行时执行此方法,线程执行完run()方法以后就会死掉,如何让线程不死掉!!在run()方法中写一个死循环!!!!
(3)sleep():让当前线程休眠同时在该时间段内让出cpu;
(4)setName();给线程起名字,不起名字也会有个默认名字;
(5)getName();获得线程名字;
(6)isAlive();判断线程是否活着;
(7)setPriority():设置线程优先级(1,5,10)优先级越大越先执行;
(8)setDaemon();设置线程为守护线程,启动之前设置,当其他线程运行结束时,该线程也自行死亡,守护线程一般为服务线程;(垃圾回收线程是一个服务线程,当没有其他线程存在时,也就没有垃圾了,垃圾回收线程也就自行死亡);
(9)join():插入线程,调用此方法的线程会优先执行,例如:A线程正在执行run()方法,在A的run()方法中调用B线程的join()方法,此时A线程阻塞,B线程开始执行;
(10)currentThread():静态方法,获得当前线程;
3.问题:
(1)线程对象创建以后可以调用run()方法启动吗?不可以!
(2)线程对象启动以后,线程会立即执行run()方法吗?不确定,有可能被其他线程抢占了cpu,该线程就处于阻塞状态;
(3)多个线程可以并发执行吗?可以
(4)工作线程可以启动工作线程吗?可以!
(5)多个线程可以共同执行一个任务吗?可以;
六.java中线程的同步(互斥与协作):
1.线程的异步:多条线程并发执行各自的业务;
2.线程同步:是多个线程并发执行时,在共享数据集上的互斥与协作;多条线程有次序执行各自的业务;如排队上公车等;
3.互斥是为了保证数据的安全,协作是让多个线程在共享数据集上进行通讯,以确保业务的合理性;
4.java中线程的互斥:共享数据集上存在多线程并发非原子操作,在其他线程执行时,其他线程不能进入,而是排队执行。
5.以StringBuffer为例:
public synchronized StringBuffer append(Object obj) {
if (obj == null) {
appendNull();
} else {
append0(obj.toString());
}
return this;
}
synchronized关键字给方法加锁,多个线程并发访问该方法时,某个线程进入该方法后,必须在执行完该方法后,释放该方法锁,其他线程才能进入该方法;这样就保证了多个线程并发访问同一数据集的非原子方法时,不会产生数据逻辑错误;
6.为什么要排队执行的形象说明:
联系到生活中的案例,如商场的试衣间,如果不加锁,当一个人进入试衣间试衣服时,试衣服的动作不是一下子完成的,而是要经过脱衣服,换衣服的过程,在这个过程中如果还没有执行完整套动作,如刚脱完衣服,其他人就闯入了,这样就造成了不安全的问题,需要对试衣间;
多个线程,(多个顾客要试衣服)同时访问一个数据集,(同时要进试衣间试衣服),要执行非原子操作时,(试衣服的动作不是一步完成的,而是要脱衣服,换衣 服等动作)为了保证数据集安全(为了保证试衣服时不被其他人打扰);需要对数据集加锁!!(需要对试衣间加锁!!!)
7.加锁的方式:
(1)使用同步代码块;(拿出方法中的一部分代码加锁(对象锁保证是唯一的锁))
(2)使用同步方法;(对方法加锁)
Public synchronized void method01(){}对象锁默认为this
Public static synchronized void method02(){}静态方法对象锁默认为类名.class (类对象)
8.案例:
static int count;
static class CountTask01 implements Runnable{
//为什么会有重复的现象:枷锁:
@Override
public synchronized void run() {
String tname=Thread.currentThread().getName();//获得当前线程的名字;
int num=++count;
System.out.println(tname+" : "+num);
}
}
public static void main(String[] args) {
//创建任务对象:
CountTask01 task=new CountTask01();
//创建并启动多个线程:有没有数据安全问题;有么有共享数据集
for(int i=0;i<5;i++){
new Thread(task).start();
}
}
该案例中,在main方法这个主线程中又开辟了多个工作线程,这几个工作线程同时访问task这个对象调用start()方法,共享count这个数据集,并且执行的操作为非原子操作!所谓非原子操作为对该数据集的操作不是一步完成的而是多步完成!!!
问题主要体现在++count上,count=count+1;先取到count然后得到count+1的值,再把该值赋给count;这是一个非原子操作!!!
9.线程安全与不安全的一些类:
①.StringBuffer和StringBuilder类:
StringBuffer是线程安全的,在该类中的很多方法都加了锁;所以相应的执行效率要低一些;
StringBuilder是非线程安全的,执行效率高
在线程不安全的环境下:所谓线程不安全是指:多个线程并发访问同一数据集的非原子操作;这种环境下使用StringBuffer;
在能确保线程安全的环境下,可以使用StringBuilder;
②Vector,HashTable是一个线程安全的集合,但是不推荐使用了,因为执行效率太低,锁的是整个哈希表;
ConcurrentHashMap底层对哈希表进行了分段加锁,锁的是试衣间;
Collections.synchronizedList(参数);的方法转换集合为线程安全的集合;也可以考虑使用java.util.concurrent包下的一些集合;例如:ConcurrentHashMap;
10.典型案例:生产者消费者模型:
1)生产者线程对象:负责向容器放数据;
2)消费者线程对象:负责从容器取数据;
3)容器对象:存放数据;(需要考虑线程安全问题)
/**外界通过此方法向容器放数据,默认放在size(最后)位置*/
public synchronized void put(Object obj){
/**1.判断容器是否已满,满了则等待(生产者线程等待)
* 2.没满则放数据*/
if(datas.length==size){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
datas[size]=obj;
//3.有效元素个数加1;
size++;
//4.通知消费者线程取数据;
this.notify();
}//此方法存在设计缺陷;满了的情况怎么办;
/**外界通过此方法取数据*/
public synchronized Object take(){
/**1.判定容器是否为空,空的则让消费者线程等待
* 2.如果不为空则取数据(默认从第0个位置取)*/
if(size==0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Object obj=datas[0];
//3.移动元素:
System.arraycopy(datas, 1, datas, 0, size-1);
//5.有效元素个数减一;
size--;
//4.将size位置元素设置为null;
datas[size]=null;
//6.通知生产者线程可以继续放数据;
this.notify();
return obj;
}
}
注意加锁的位置:对共享的数据集加锁;
七.安卓中线程消息模型:
1.概述:
安卓中线程应用机制:
1)所有的UI操作都在主线程中进行;
2)所有的耗时操作,例如下载都在工作线程中进行;
2.消息模型中用到的对象:
安卓中的消息模型是实现安卓中线程通讯的方式。这种方式的实现依托于如下几个对象:
1)消息对象;Message负责承载线程之间需要承载的数据;
2)MessageQueue(消息队列:负责存储多个消息队列)
3)Looper(迭代消息队列的迭代器:负责迭代消息队列)
4)Handler (处理器:发送和处理消息)
3.理解:
给哪个线程发消息,取决于Handler中所关联的Looper对象;在Handler()的构造方法中,传入Looper对象,默认是关联的主线程的Looper对象;
如果将Handler看做是一个邮局,那么Message就是要运送的包裹,MessageQueue消息队列就是所有包裹的载体,如邮车,Looper对象就是用来迭代消息队列中的内容的邮递员,要将消息发送给哪个线程取决于Looper对象,也就是邮递员是哪个邮局的。
handlerMessage()方法会在收到消息后进行回调,在这方法中做一些逻辑处理;
4.工作线程给主线程发消息:
在工作线程中构建消息对象,将消息封装到该消息中,然后调用sendMessage()方法将消息发送给主线程;
在主线程中构建handler对象处理并接收工作线程中发来的消息,重写sendMessage()方法,在该方法中做出相应的逻辑处理;
5.主线程给工作线程发消息:
需要构建工作线程的Looper对象,在工作线程中构建Handler对象,重写handlerMessage()方法在方法中作出相应的业务处理;
6.HandlerThread类:
封装了Looper对象的创建和销毁的过程,可以简化对looper对象的操作;Looper.looper()方法为内置的无限循环,不断的迭代消息队列中的消息内容;只有当looper.quite()方法执行时,该循环才会停止;
7.杀死进程:
工作线程由主线程启动,主线程运行在进程之中,杀死进程的同时,所在的主线程和工作线程都会终止;
具体方法:
Progress.killProgress(Progress.myPid());//myPid()方法为当前正在执行的进程;
八.安卓中的AsyncTask类:
1.概述:
(1)AsyncTask是安卓中的一个工具类,此类实现了对异步任务处理逻辑的封装,是对消息模型应用技巧的巧妙实现;简化消息模型逻辑的编写!
(2)我们在使用该对象时,需要构建此类类型的子类对象,然后重写相关方法,这些方法有些运行在工作线程,有些运行在主线程;异步:有多个线程;
2.AsyncTask相关方法:
(1)execute:(调用此方法执行任务,需要排队执行)
(2)executeOnExecutor:(调用此方法这些任务可以并发执行)
(3)onPreExecute:(运行在主线程,在doInBackground:之前执行)
(4)doInBackground:(工作线程,用于执行耗时操作)
(5)onPostExecute:(主线程在doInBackground:之后执行)
(6)publishProgress(发布进度,一般在工作线程调用)
(7)onprogressUpdate(主线程,更新进度);
当调用异步任务的executeXXX方法,启动任务执行时,execute方法中的实际参数值会传递给doInBackground方法;
相关泛型,决定类中属性类型,方法参数类型,方法返回值类型;
Ablstract class AsyncTask<Params,Progress,Result>();
(1)Params
(2)Progress
(3)Result
/**此方法运行在主线程,在doInBackground方法之前之前,用于初始化*/
@Override
protected void onPreExecute() {}
//此方法运行在工作线程;
@Override
protected File doInBackground(Integer... params) {}
/**此方法可以运行在主线程:可以在此方法中更新进度*/
@Override
protected void onProgressUpdate(Integer... values) {}
//此方法在doInBackground()方法结束后执行;运行在主线程;
@Override
protected void onPostExecute(File result) {}
九.线程池:
1.概述:
(1)何为线程池:
①内存中的一块区域
②允许存储多个线程对象;
类似:整数池,字符串池,消息池.....
(2)线程池应用的目的:
①允许线程池中的线程可以重复使用;
②提高系统的执行效率,较少对象的创建和销毁的过程;
(3)线程池的应用场合:
1>需要反复创建线程,并发执行任务的场合;
例如:
同时下载多个文件;
同时处理多个用户请求;
(4)java中或安卓中线程池相关API?
1.Executor(线程池顶层接口):代表一个线程池对象;
1>ExecutorService(接口)
2>ScheduledExecutorService(接口)
3>ThreadPoolExecutor(实现类)
4>..........,
我们通常可以调用ExecutorServices, ScheduledExecutorService的实现类的类型对象创建线程;
2.Executors(工具类:创建一些简单的线程池对象)
1>创建一个只有一个线程的线程池;newSingle.....
2>创建一个有固定上限的线程池;newFixedThreadPool
3>创建一个没有固定上限的线程池;newCachedThreadPool
说明:也可以直接构建ThreadPoolExecutor的对象;
3.线程池对象创建以后我们可以调用线程池的execute,submit等相关方法执行任务;