Android中的线程之线程基础(synchronized,wait,sleep,yield,notify )
多线程基础 之synchronized,wait,sleep,yield,notify
Android中的多线程实际上就是jAVA SE中的多线程,只是为了方便使用,Android封装了一些类,AsyncTask,HandlerThread等等。
首先,我们先看看Thread和Runnable;
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方法:
@Overridepublic void run() { if (target != null) { target.run(); }}
在上面分析中,我们知道target是Runnable类型的实例,所以我们可以知道当启动一个线程时,如果Thread的target不为空,则会在子线程中执行这个target的run方法,否则虚拟机会执行该线程自身的run方法。
线程的synchronized
关键字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;}@Overridepublic void run() { super.run(); object.methodA(); }}public class ThreadB extends Thread {private MyObject obeject;public ThreadB(MyObject obeject) { super(); this.obeject = obeject;}@Overridepublic 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 = Abegin methodA threadName = Bendend
两个线程可以一同进入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 = Aendbegin methodA threadName = Bend
我们可以看到,这两个线程是排队进入方法的
那么为什么会出现排队的现象呢?这是因为锁对象的原因,我们再看看一个实例:
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;}@Overridepublic void run() { super.run(); obeject.methodB(); }}
然后运行Run.java:
得出的结果:
begin methodA threadName = Abegin methodB threadName = B begin time = 1529831185440endend 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 = Aend endTime = 1529831671458begin methodB threadName = B begin time = 1529831671458end
我们可以看到,当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 = Aend endTime = 1529833499688begin methodA threadName = Bend endTime = 1529833504689
synchronized可以将任意对象作为对象监视器
这里说的任意对象,大多数是实例变量及方法的参数,使用格式为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,sleep,join和yield
wait(): 当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去(释放)了对象的机锁,使得其他线程可以访问。用户可以使用notify,notifyAll或者指定睡眠时间来唤醒当前等待池中的线程。
注意:wait方法,notify方法,notifyAll方法必须放在synchronized block中,否则会抛出异常。sleep():该函数是Thread的静态函数,作用是使调用线程进入睡眠状态。因为sleep()是Thread类的Static方法,因为它不能改变对象的机制。所以,当在一个Synchronized块中调用sleep方法时,线程虽然休眠了,但是对象的机制并没有被释放,其他线程无法访问这个对象(即使睡着也持有对象锁)
join():等待目标线程执行完成之后再继续执行
yield():线程礼让。目标线程由运行状态转换为就绪状态,也就是让出执行权限,让其他线程得以优先执行,但其他线程能否优先执行是未知的。
首先看看wait和notify,notifyAll的运用:
//用于等待,唤醒的对象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
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()); } }
结果:
启动线程1work in work-1work finish work-1启动线程2work in work-2work 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方法,会让线程进入等待状态,直到它被唤醒。
yield方法
它的官方解释为“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]------->0thread-1[5]------->1thread-1[5]------->2thread-2[5]------->0thread-2[5]------->1thread-2[5]------->2thread-1[5]------->3thread-1[5]------->4thread-2[5]------->3thread-2[5]------->4`
我们可以看到只要当i == 2时,该thread就会让出执行权,让其他线程得到优先执行。
更多相关文章
- 子线程更新UI的方法
- Android真机测试 INSTALL_FAILED_INSUFFICIENT_STORAGE 解决方法
- Android能否在子线程中更新UI呢?
- 安卓(android)建立项目时失败,出现Android Manifest.xml file missi
- Android——Listview不用notifydatasetchanged更新数据的方法
- Android中两种使用Animation的方法
- 详解Android aidl的使用方法
- Android中自定义标题栏样式的两种方法
- Genymotion Android模拟器下载和找不到模拟器解决方法