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方法:

 @Override
public 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;
}

@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可以将任意对象作为对象监视器

这里说的任意对象,大多数是实例变量及方法的参数,使用格式为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());
        }
    }

结果:

启动线程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方法,会让线程进入等待状态,直到它被唤醒。

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

你可能感兴趣的:(android)