多线程与Android线程性能优化(二)

线程中止

线程自然终止

要么是 run 执行完成了,要么是抛出了一个未处理的异常导致线程提前结束。

stop

​ 暂停、恢复和停止操作对应在线程 Thread 的 API 就是 suspend()、resume() 和 stop()。但是这些API 是过期的,也就是不建议使用的。不建议使用的原因主要有:以 suspend() 方法为例,在调用后,线程不会释放已经占有的资源(比如锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题。同样,stop() 方法在终结一个线程时不会保证线程的资源正常释放,通常是没有给予线程完成资源释放工作的机会,因此会导致程序可能工作在不确定状态下。正因为 suspend()、 resume()和 stop()方法带来的副作用,这些方法才被标注为不建议使用的过期方法。

中断

​ 安全的中止则是其他线程通过调用某个线程 A 的 interrupt()方法对其进行中 断操作, 中断好比其他线程对该线程打了个招呼,“A,你要中断了”,不代表线程 A 会立即停止自己的工作,同样的 A 线程完全可以不理会这种中断请求。 因为 java 里的线程是协作式的,不是抢占式的。线程通过检查自身的中断标志位是否被置为 true 来进行响应。

(1)区别interrupt()、isInterrupted()和Thread.interrupted()这三个方法

​ 调用interrupt()不会强制中断线程,而是设置中断标识位,最终结果由线程本身决定,这也就说明线程之间是协作式的,不是抢占式。线程是否执行中断,通过isInterrupted()来判断,如果线程执行了中断,isInterrupted()置为true。

​ Thread.interrupted() 这个静态方法同样可以检测到中断标志位,但它会把true改成false。

我们通过代码来看下他们之间的区别:

首先我们用isInterrupted()来判断:

public class EndThread {

    private static class UserThread extends Thread {

        public UserThread(String name) {
            super(name);
        }

        @Override
        public void run() {

            String threadName = Thread.currentThread().getName();

            System.out.println(threadName + " start interrupt flag = " + isInterrupted());
            while (!isInterrupted()) {
            //while (!Thread.interrupted()) {
                System.out.println(threadName + " is running flag " + isInterrupted());
            }
            System.out.println(threadName + " end interrupt flag = " + isInterrupted());
        }
    }
    public static void main (String[] args) throws InterruptedException {

        UserThread endThread = new UserThread("endThread");
        endThread.start();
        Thread.sleep(1);

        endThread.interrupt(); //中断线程,其实设置了中断标志位
    }
}

执行结果:

endThread start interrupt flag = false
endThread is running flag false
endThread is running flag false
endThread is running flag false
endThread is running flag false
....
endThread end interrupt flag = true

Process finished with exit code 0

通过结果,我们看到,启动线程时,interrupt标志位为false,当我们执行interrupt()进行中断时,interrupt标志位置为true,程序被中断结束。

再看第二种,用静态方法Thread.interrupted()做判断条件:

直接看结果:

endThread start interrupt flag = false
endThread is running flag false
endThread is running flag false
endThread is running flag false
.....
endThread end interrupt flag = false

Process finished with exit code 0

Thread.interrupted()同样可以检测到中断标志位,但它会把true改成false。

​ 调用sleep()等阻塞类方法,当抛出InterruptedException被捕获到时,会把isInterrupted()标志位由true变为false,所以sleep()方法并不会中断线程,如果想要中断,需要在sleep()的catch语句块中,手动调用interrupt()

注意:处于死锁状态的线程无法被中断

public class SleepThread {

    private static class UserThread extends Thread {

        public UserThread(String name) {
            super(name);
        }

        @Override
        public void run() {

            String threadName = Thread.currentThread().getName();

            System.out.println(threadName + " start interrupt flag = " + isInterrupted());
            while (!isInterrupted()) {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    //资源释放
                    interrupt();
                    e.printStackTrace();
                }
                System.out.println(threadName + " is running flag " + isInterrupted());
            }
            System.out.println(threadName + " end interrupt flag = " + isInterrupted());
        }
    }
    public static void main (String[] args) throws InterruptedException {

        Thread endThread = new UserThread("endThread");
        endThread.start();
        Thread.sleep(5);

        endThread.interrupt(); //中断线程,其实设置了中断标志位
    }
}

执行结果:

endThread start interrupt flag = false
endThread is running flag true
endThread end interrupt flag = true
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.company.Android3.SleepThread$UserThread.run(SleepThread.java:19)

Process finished with exit code 0

守护线程不一定会执行,看CPU时间片分配到它没。所以当设置当前线程为守护线程时,finally语句块的代码不一定会执行。所以在finally里执行逻辑业务时,要区分开守护线程和用户线程。

public class DaemonThread {

    private static class UserThread extends Thread {

        public UserThread(String name) {
            super(name);
        }

        @Override
        public void run() {

            String threadName = Thread.currentThread().getName();

            System.out.println(threadName + " interrupt flag = " + isInterrupted());
            try {
                while (!isInterrupted()) {
                    System.out.println(threadName + " is running flag " + isInterrupted());
                }
                System.out.println(threadName + " interrupt flag = " + isInterrupted());
            } finally {
                //守护线程中finally不一定起作用
                System.out.println("............finally");
            }
        }
    }
    public static void main (String[] args) throws InterruptedException {

        UserThread userThread = new UserThread("endThread");
        userThread.setDaemon(true);
        userThread.start();
        Thread.sleep(1);
        //userThread.interrupt(); //中断线程,其实设置了中断标志位
    }
}

代码里使用userThread.setDaemon(true)来设置当前线程为守护线程,当主线程结束后,finally语句块代码不一定执行。

线程间的共享

线程同步 加锁

synchronized关键字,可以对方法加锁,也可以对代码块加锁,不管是对方法加锁还是对代码块加锁,锁的都是某一个对象,也就是对象锁。
类锁,静态方法加锁。每个类在加载时候都会有一个class对象,所以类锁锁的是class对象,本质上还是对象锁。

volatile保证的是可见性 old value --> new value

ThreadLocal辨析

ThreadLocal 为每个线程都提供了变量的副本,使得每个线程在某一时间访问到的并非同一个对象,这样就隔离了多个线程对数据的数据共享,保证了线程的安全性。

ThreadLocal 类接口很简单,只有 4 个方法,我们先来了解一下:

  • void set(Object value)

设置当前线程的线程局部变量的值。

  • public Object get()

该方法返回当前线程所对应的线程局部变量。

  • public void remove()

将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是 JDK 5.0 新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动 被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它 可以加快内存回收的速度。

  • protected Object initialValue()

返回该线程局部变量的初始值,该方法是一个 protected 的方法,显然是为 了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第 1 次调用 get() 或 set(Object)时才执行,并且仅执行 1 次。ThreadLocal 中的缺省实现直接返回一 个 null。

  • public final static ThreadLocal RESOURCE = new ThreadLocal();

RESOURCE代表一个能够存放String类型的ThreadLocal对象。 此时不论什么一个线程能够并发访问这个变量,对它进行写入、读取操作,都是线程安全的。

测试代码:

/**
 * 演示ThreadLocal的使用
 */
public class UseThreadLocal {

    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){
        @Override
        protected Integer initialValue() {
            return 1;
        }
    };

    /**
     * 运行3个线程
     */
    public void runThreadArray() {
        Thread[] threads = new Thread[3];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(new TestThread(i));
        }

        for (int i = 0; i < threads.length; i++) {
            threads[i].start();
        }
    }

    /**
     * 测试线程,线程的工作是将ThreadLocal的值发生变化,并写回,看看线程之间是否会互相影响
     */
    public static class TestThread implements Runnable {

        int id;

        public TestThread(int id) {
            this.id = id;
        }

        @Override
        public void run() {
            Integer s = threadLocal.get();
            s = s + id;
            threadLocal.set(s);
            System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
        }
    }

    public static void main(String[] args) {

        UseThreadLocal useThreadLocal = new UseThreadLocal();
        useThreadLocal.runThreadArray();
    }
}

执行结果:

Thread-1:2
Thread-2:3
Thread-0:1

从结果中看到,使用了ThreadLocal后,每个线程都单独拥有一个变量的副本,所以得到的结果是:id在这三个线程中是互不影响的,每次执行+1操作后,id的值会自加一。

ThreadLocal实现源码解析

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
在这里插入图片描述
上面先取到当前线程,然后调用getMap方法获取对应的ThreadLocalMap,ThreadLocalMap是ThreadLocal的静态内部类,然后Thread类中有一个这样的类型成员,所以getMap直接返回Thread的成员。

再看下ThreadLocal的内部类ThreadLocalMap的源码:
在这里插入图片描述
可以看到有个Entry静态内部类,它继承了WeakReference,总之它记录了两个信息,一个是ThreadLocal<?>类型,一个是Object类型的值。

在这里插入图片描述
在这里插入图片描述
getEntry方法则是获取某个ThreadLocal对应的值,set方法就是更新或赋值相应的ThreadLocal对应的值。

回顾我们的get方法,其实就是拿到每个线程独有的ThreadLocalMap,然后再用ThreadLocal的当前实例,拿到Map中相应的Entry,然后就可以拿到相应的值返回出去。当然,如果Map为空,还会先进行map的创建、初始化工作。

线程间的协作

等待/通知机制

​ 是指一个线程 A 调用了对象 O 的 wait()方法进入等待状态,而另一个线程 B 调用了对象 O 的 notify()或者 notifyAll()方法,线程 A 收到通知后从对象 O 的 wait() 方法返回,进而执行后续操作。上述两个线程通过对象 O 来完成交互,而对象 上的 wait()和 notify/notifyAll()的关系就如同开关信号一样,用来完成等待方和通 知方之间的交互工作。

  • notify()

通知一个在对象上等待的线程,使其从 wait 方法返回,而返回的前提是该线程 获取到了对象的锁,没有获得锁的线程重新进入 WAITING 状态。

  • notifyAll()

通知所有等待在该对象上的线程 。

  • wait()

调用该方法的线程进入 WAITING 状态,只有等待另外线程的通知或被中断才会返回。需要注意:调用 wait()方法后,会释放对象的锁。

  • wait(long)

超时等待一段时间,这里的参数时间是毫秒,也就是等待长达n 毫秒,如果没有通知就超时返回 。

  • wait (long,int)

对于超时时间更细粒度的控制,可以达到纳秒

等待和通知的标准范式

等待:

synchronized (obj) {
	while(条件不满足) {
		obj.wait();
	}
	//业务逻辑
}

通知:

synchronized (obj) {
	//业务逻辑,改变条件
	obj.notify()/notifyAll();
	//do sth;
}

在调用 wait()、notify()系列方法之前,线程必须要获得该对象的对象级 别锁,即只能在同步方法或同步块中调用 wait()方法、notify()系列方法,进 入 wait() 方法后,当前线程释放锁,在从 wait() 返回前,线程与其他线程竞争重新获得锁,执行 notify() 系列方法的线程退出调用了 notifyAll 的 synchronized 代码块的时候后,他们就会去竞争。如果其中一个线程获得了该对象锁,它就会继续往下执行,在它退出 synchronized 代码块,释放锁后,其他的已经被唤醒的线程将会继续竞争获取该锁,一直进行下去,直到所有被唤醒的线程都执行完毕。

notify 和 notifyAll 应该用谁

尽可能用 notifyAll(),谨慎使用 notify(),因为 notify()只会唤醒一个线程,我 们无法确保被唤醒的这个线程一定就是我们需要唤醒的线程。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值