Android中的多线程实际上就是jAVA SE中的多线程,只是为了方便使用,Android封装了一些类,AsyncTask,HandlerThread等等。
首先,我们先看看Thread和Runnable;
通常我们使用如下的代码启动一个线程:
private void startnewThread() {
new Thread() {
@Override
public void run() {
//耗时操作
}
}.start();
}
private void startnewThread() {
new Thread() {
@Override
public void run() {
super.run();
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
//耗时操作
}
}).start();
}
示例1是覆写了Thread类中的run函数执行耗时操作,示例2则是向Thread的构造函数中传递了一个Runnable对象,而在Runnable对象中执行了耗时操作。那么Thread和Runnable又是什么关系呢?
实际上Thread也是一个Runnable,它实现了Runnable接口,在Thread类中有一个Runnable类型的target字段,代表要被执行在这个线程中的任务:
public class Thread implements Runnable {
/* Make sure registerNatives is the first thing does. */
/**
* The synchronization object responsible for this thread's join/sleep/park operations.
*/
private final Object lock = new Object();
private volatile long nativePeer;
boolean started = false;
private volatile String name;
private int priority;
private Thread threadQ;
private long eetop;
/* Whether or not to single_step this thread. */
private boolean single_step;
/* Whether or not the thread is a daemon thread. */
private boolean daemon = false;
/* JVM state */
private boolean stillborn = false;
/* What will be run. */
private Runnable target;
/* The group of this thread */
private ThreadGroup group;
。。。 。。。
}
那么我们先看看Thread的构造方法,先看传入Runnable的:
*
* @param target
* the object whose {@code run} method is invoked when this thread
* is started. If {@code null}, this classes {@code run} method does
* nothing.
*/
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
/**
* Initializes a Thread.
*
* @param g the Thread group
* @param target the object whose run() method gets called
* @param name the name of the new Thread
* @param stackSize the desired stack size for the new thread, or
* zero to indicate that this parameter is to be ignored.
*/
private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
Thread parent = currentThread();
//group参数为null时,则获取当前线程的线程组
if (g == null) {
g = parent.getThreadGroup();
}
g.addUnstarted();
this.group = g;
this.target = target;
this.priority = parent.getPriority();
this.daemon = parent.isDaemon();
setName(name);
init2(parent);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
tid = nextThreadID();
}
/**
* Returns a reference to the currently executing thread object.
*
* @return the currently executing thread.
*/
public static native Thread currentThread();
在我们构造Thread的时候,无论是否传入Runnable,最终都会调用init()这个方法。
接下来我们看看start()方法:
* @exception IllegalThreadStateException if the thread was already
* started.
* @see #run()
* @see #stop()
*/
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".
*/
// Android-changed: throw if 'started' is true
if (threadStatus != 0 || started)
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);
started = false;
try {
//调用native函数启动新的线程
nativeCreate(this, stackSize, daemon);
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 */
}
}
}
我们知道,当我们调用start方法后,它一定会调用run方法,所以我们看看run方法:
@Override
public void run() {
if (target != null) {
target.run();
}
}
在上面分析中,我们知道target是Runnable类型的实例,所以我们可以知道当启动一个线程时,如果Thread的target不为空,则会在子线程中执行这个target的run方法,否则虚拟机会执行该线程自身的run方法。
关键字synchronized取得锁都是对象锁,如果多个线程访问多个对象则JVM会创建多个锁。
先看看synchronized与锁对象:
首先有一个类:
public class MyObject {
public void methodA(){
try {
System.out.println("begin methodA threadName = "+Thread.currentThread().getName());
Thread.sleep(5000);
System.out.println("end");
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public class ThreadA extends Thread {
private MyObject object;
public ThreadA(MyObject object) {
super();
this.object = object;
}
@Override
public void run() {
super.run();
object.methodA();
}
}
public class ThreadB extends Thread {
private MyObject obeject;
public ThreadB(MyObject obeject) {
super();
this.obeject = obeject;
}
@Override
public void run() {
super.run();
obeject.methodA();
}
}
然后我们运行Run.java:
public class Run {
public static void main(String[] args){
MyObject object = new MyObject();
ThreadA a = new ThreadA(object);
a.setName("A");
ThreadB b = new ThreadB(object);
b.setName("B");
a.start();
b.start();
}
}
得到的结果是:
begin methodA threadName = A
begin methodA threadName = B
end
end
两个线程可以一同进入methodA这个方法
然后我们再methodA方法前加上synchronized:
public class MyObject {
synchronized public void methodA(){
try {
System.out.println("begin methodA threadName = "+Thread.currentThread().getName());
Thread.sleep(5000);
System.out.println("end");
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
结果为:
begin methodA threadName = A
end
begin methodA threadName = B
end
我们可以看到,这两个线程是排队进入方法的
那么为什么会出现排队的现象呢?这是因为锁对象的原因,我们再看看一个实例:
public class MyObject {
synchronized public void methodA(){
try {
System.out.println("begin methodA threadName = "+Thread.currentThread().getName());
Thread.sleep(5000);
System.out.println("end endTime = "+System.currentTimeMillis());
}catch (InterruptedException e){
e.printStackTrace();
}
}
public void methodB(){
try {
System.out.println("begin methodB threadName = "
+Thread.currentThread().getName()
+ " begin time = "+System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("end");
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
改动一下ThreadB中的代码:
public class ThreadB extends Thread {
private MyObject obeject;
public ThreadB(MyObject obeject) {
super();
this.obeject = obeject;
}
@Override
public void run() {
super.run();
obeject.methodB();
}
}
然后运行Run.java:
得出的结果:
begin methodA threadName = A
begin methodB threadName = B begin time = 1529831185440
end
end endTime = 1529831190441
如果我们在methodB方法前加上synchronized关键字:
public class MyObject {
synchronized public void methodA(){
try {
System.out.println("begin methodA threadName = "+Thread.currentThread().getName());
Thread.sleep(5000);
System.out.println("end endTime = "+System.currentTimeMillis());
}catch (InterruptedException e){
e.printStackTrace();
}
}
synchronized public void methodB(){
try {
System.out.println(“begin methodB threadName = ”
+Thread.currentThread().getName()
+ ” begin time = “+System.currentTimeMillis());
Thread.sleep(5000);
System.out.println(“end”);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
运行Run.java:
begin methodA threadName = A
end endTime = 1529831671458
begin methodB threadName = B begin time = 1529831671458
end
我们可以看到,当methodB方法没有加上synchronized关键字时,两个线程基本是同时进入各自调用的方法的,这个时候只有ThreadA拥有object对象的Lock锁。
当methodB方法加上synchronized关键字时,A线程先持有object对象的Lock锁,B线程如果这个时候调用Object对象中synchronized类型的方法时,需要等待,等到A线程执行完,释放了锁,它才可以得到锁。
所以:synchronized 加到非静态方法前面是给对象上锁,也就是对象锁。
我们经常会用synchronized同步代码块,当两个并发线程访问同一个对象object中的synchronized(this)同步代码块时,一段时间内只能有一个线程被执行,另一个线程必须等到当前线程执行完这个代码块以后才能执行:
我们改一下MyObject:
public void methodA(){
try {
synchronized (this){
System.out.println("begin methodA threadName = "+Thread.currentThread().getName());
Thread.sleep(5000);
System.out.println("end endTime = "+System.currentTimeMillis());
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
然后让线程A和线程B都访问这个methodA方法:
得到的结果:
begin methodA threadName = A
end endTime = 1529833499688
begin methodA threadName = B
end endTime = 1529833504689
这里说的任意对象,大多数是实例变量及方法的参数,使用格式为synchronized(非this对象)
public class Service {
private String username;
private String password;
private String anyString = new String();
public void setUsernamePassword(String username,String password){
try {
synchronized (anyString){
System.out.println("线程名: "+Thread.currentThread().getName()+
"在 "+System.currentTimeMillis() +"进入同步块");
this.username = username;
Thread.sleep(3000);
this.password = password;
System.out.println("线程名: "+Thread.currentThread().getName()+
"在 "+System.currentTimeMillis() +"离开同步块");
}
}catch (InterruptedException e){
}
}
}
public class Run {
public static void main(String[] args){
Service service = new Service();
ThreadA a = new ThreadA(service);
a.setName("A");
ThreadB b = new ThreadB(service);
b.setName("B");
a.start();
b.start();
}
}
更改ThradA,threadB的代码,运行Run.java,得到的结果:
线程名: A在 1529834252077进入同步块
线程名: A在 1529834255077离开同步块
线程名: B在 1529834255077进入同步块
线程名: B在 1529834258077离开同步块
锁非this对象具有一定的优点:如果在一个类中有很多个synchronized方法,这时虽然能实现同步,但会受到阻塞,所以影响运行效率;但如果使用同步代码块锁非this对象,则synchronized(非this)代码块中的程序与同步方法是异步的,不予其他锁this同步方法争抢this锁,则可以大大提高运行效率。
现在我们修改一下Service:
public void setUsernamePassword(String username,String password){
try {
anyString = new String();
synchronized (anyString){
System.out.println("线程名: "+Thread.currentThread().getName()+
"在 "+System.currentTimeMillis() +"进入同步块");
this.username = username;
Thread.sleep(3000);
this.password = password;
System.out.println("线程名: "+Thread.currentThread().getName()+
"在 "+System.currentTimeMillis() +"离开同步块");
}
}catch (InterruptedException e){
}
}
得到的结果:
线程名: A在 1529834617297进入同步块
线程名: B在 1529834617297进入同步块
线程名: B在 1529834620297离开同步块
线程名: A在 1529834620297离开同步块
由此可见,使用synchronized(非this对象)同步块时,对象监视器必须是同一个对象。如果不是同一个对象,运行的结果就是异步调用了,就会交叉运行。
wait(): 当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去(释放)了对象的机锁,使得其他线程可以访问。用户可以使用notify,notifyAll或者指定睡眠时间来唤醒当前等待池中的线程。
注意:wait方法,notify方法,notifyAll方法必须放在synchronized block中,否则会抛出异常。
sleep():该函数是Thread的静态函数,作用是使调用线程进入睡眠状态。因为sleep()是Thread类的Static方法,因为它不能改变对象的机制。所以,当在一个Synchronized块中调用sleep方法时,线程虽然休眠了,但是对象的机制并没有被释放,其他线程无法访问这个对象(即使睡着也持有对象锁)
join():等待目标线程执行完成之后再继续执行
yield():线程礼让。目标线程由运行状态转换为就绪状态,也就是让出执行权限,让其他线程得以优先执行,但其他线程能否优先执行是未知的。
//用于等待,唤醒的对象
private static Object sLockObject = new Object();
public static void waitAndNotifyAll(){
System.out.println("主线程运行");
Thread thread = new WaitThread();
thread.start();
long startTime = System.currentTimeMillis();
try {
synchronized (sLockObject){
System.out.println("主线程等待");
sLockObject.wait();
}
}catch (InterruptedException e){
}
long timesMs = System.currentTimeMillis()-startTime;
System.out.println("主线程继续 ->等待耗时:"+timesMs +" ms");
}
static class WaitThread extends Thread{
@Override
public void run() {
try {
synchronized (sLockObject){
Thread.sleep(3000);
sLockObject.notifyAll();
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
结果:
主线程运行
主线程等待
主线程继续 ->等待耗时:3001 ms
在waitAndNotifyAll方法中,会启动一个WaitThread线程,在该线程中会调用sleep函数睡眠3秒钟。线程启动之后,会调用sLoackObject的wait函数,使主线程进入等待状态,此时将不会继续执行。等WaitThread在run方法沉睡了3秒后会调用sLockObject的notifyAll方法,此时就会重新唤醒正在等待中的主线程。
wait,notify机制通常用于等待机制的实现,当条件为满足时,调用wait进入等待状态,一旦条件满足,调用notify或notifyAll唤醒等待的线程继续执行。
然后我们补充一下Object的wiat,notify,notifyAll:
使用Object的wait方法和notify等方法,常常使用多线程中的等待机制的实现。
Object类的wait方法,用来将当前线程置入“预执行队列”中,并且在wait()所在的代码出停止执行,直到接到通知或被中断为止。在调用wait方法之前,线程必须获得该对象的对象级别锁,即只能在同步方法或同步快中调用wait方法。在执行wait方法后,当前线程释放锁。在从wait()返回前,线程与其他线程竞争重新获得锁。如果调用wait方法时没有持有适当的锁,则会抛出异常 IllegalMonitorStateException。
Object类的notify方法,也要在同步方法或同步块中调用,即在调用前,线程也必须获得该对象的对象级别锁。如果调用notify()时,没有持有适当的锁,也会抛出IllegalMonitorStateException。该方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程规划方法随机挑出一个呈wait状态的线程,对其发出notify通知,并使它等待获取该对象的对象锁。
需要注意的是,在执行notify()方法后,当前线程不会马上释放该对象锁,呈wait状态的线程也并不能马上获取该对象锁,要等到执行notify方法的线程将程序执行完,也就是出synchronized代码块后,当前线程才会释放锁。而呈wait状态所在的线程才可以获取该对象锁。
Object类的notifyAll,和notify差不多,只是它会唤醒所有呈wait状态的线程。
在上面代码中我们有用到sleep,我们来看看它的源码:
public static void sleep(long millis) throws InterruptedException {
Thread.sleep(millis, 0);
}
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);
}
}
}
我们可以看到,在sleep方法中,先是得到当前对象的lock,然后进入一个死循环,每次判断当前的时间和开始时间的差,当大于的需要等待的时间的时候,就退出循环,这样当前的Thread就可以继续执行了。
join方法的原始解释为“Blocks the current Thread(Thread.currentThread())until the receiver finishes its execution and ides”,意思就是阻塞当前调用join函数所在的线程,直到接收线程执行完毕之后再继续。
在很多情况下,主线程创建并启动子线程,如果子线程中要进行大量的耗时运算,主线程往往将早于子线程结束之前结束。。这时,如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据的值,就要用到join方法了。join方法的作用就是等待线程对象销毁。
static void joinDemo(){
Worker worker1 = new Worker("work-1");
Worker worker2 = new Worker("work-2");
worker1.start();
System.out.println("启动线程1");
try {
worker1.join();
System.out.println("启动线程2");
worker2.start();
worker2.join();
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("主线程继续执行");
}
static class Worker extends Thread{
public Worker(String name) {
super(name);
}
@Override
public void run() {
try {
System.out.println("work in "+ getName());
Thread.sleep(2000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("work finish "+ getName());
}
}
结果:
启动线程1
work in work-1
work finish work-1
启动线程2
work in work-2
work finish work-2
主线程继续执行
在joinDemo中,首先创建了两个子线程,然后启动了work1,下一步调用work1的join方法,此时,主线程进入阻塞状态,一直到work1执行完毕后才继续执行。所以上面的逻辑为:启动线程1,等待线程1执行完成,启动线程2,等待线程2执行完成,继续执行主线程代码。
我们来看看join的源码:
public final void join() throws InterruptedException {
join(0);
}
public final void join(long millis) throws InterruptedException {
synchronized(lock) {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
lock.wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
lock.wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
}
我们可以看到,它主要是使用local的wait方法,我们前面有知道,object的wait方法,会让线程进入等待状态,直到它被唤醒。
它的官方解释为“Causes the calling Thread to yield execution time to another Thread that isready to run” 意思为调用该函数的线程让出执行时间给其他已就绪状态的线程。我们知道,线程的执行是有时间片的,每个线程轮流占用CPU固定的时间,执行周期到了之后就让出执行权给其他线程。而yield的功能就是主动让出线程的执行权给其他线程,其他线程能否得到优先执行就看各个线程的状态了。
static class YieldThread extends Thread{
public YieldThread(String name) {
super(name);
}
@Override
public void run() {
for(int i = 0;i<5;i++){
System.out.printf("%s[%d]------->%d\n",this.getName(),this.getPriority(),i);
if(i == 2){
Thread.yield();
}
}
}
}
static void yieldDemo(){
YieldThread t1 = new YieldThread("thread-1");
YieldThread t2 = new YieldThread("thread-2");
t1.start();
t2.start();
}
结果:
`thread-1[5]------->0
thread-1[5]------->1
thread-1[5]------->2
thread-2[5]------->0
thread-2[5]------->1
thread-2[5]------->2
thread-1[5]------->3
thread-1[5]------->4
thread-2[5]------->3
thread-2[5]------->4`
我们可以看到只要当i == 2时,该thread就会让出执行权,让其他线程得到优先执行。