多线程基础 之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就会让出执行权,让其他线程得到优先执行。

更多相关文章

  1. 子线程更新UI的方法
  2. Android真机测试 INSTALL_FAILED_INSUFFICIENT_STORAGE 解决方法
  3. Android能否在子线程中更新UI呢?
  4. 安卓(android)建立项目时失败,出现Android Manifest.xml file missi
  5. Android——Listview不用notifydatasetchanged更新数据的方法
  6. Android中两种使用Animation的方法
  7. 详解Android aidl的使用方法
  8. Android中自定义标题栏样式的两种方法
  9. Genymotion Android模拟器下载和找不到模拟器解决方法

随机推荐

  1. Hello Android(安卓)- SQLite数据库操作
  2. android绘图Paint.setXfermode()和Canvas
  3. 自己写的一套应用管理系统(包含一套app系
  4. 浅谈Android之系统概述
  5. android的应用程序调用另一个应用程序的
  6. Android(安卓)设备关闭实体按键
  7. AndroidUI设计之 布局管理器 - 详细解析
  8. Android利用Fiddler进行网络数据抓包
  9. Android(安卓)NDK
  10. android的四种启动方式和各自特点