Android-多线程

线程

线程是进程中可独立执行的最小单位,也是 CPU 资源(时间片)分配的基本单位,同一个进程中的线程可以共享进程中的资源,如内存空间和文件句柄。线程有一些基本的属性,如id、name、以及priority。
id:线程 id 用于标识不同的线程,编号可能被后续创建的线程使用,编号是只读属性,不能修改。
name:线程的名称,默认值是 Thread-(id)
daemon:分为守护线程和用户线程,我们可以通过 setDaemon(true) 把线程设置为守护线程。守护线程通常用于执行不重要的任务,比如监控其他线程的运行情况,GC 线程就是一个守护线程。
setDaemon() 要在线程启动前设置,否则 JVM 会抛出非法线程状态异常,可被继承。
priority:线程调度器会根据这个值来决定优先运行哪个线程(不保证),优先级的取值范围为
1~10,默认值是 5,可被继承。Thread 中定义了下面三个优先级常量:
最低优先级:MIN_PRIORITY = 1
默认优先级:NORM_PRIORITY = 5
最高优先级:MAX_PRIORITY = 10

线程池的原理

ThreadPoolExecutor线程池的执行方法中,我发现一个出现频率很高的 API addWorker , 线程池拿到任务之后丢给了 Worker 类处理, 这个类还创建了 Thread 对象,就是创建线程的时候,传入的并不是我们给线程池的那个 Runnable 对象,而是 Worker 对象本身,也就是说线程 start 的时候,Worker 类的 run 方法会被执行,然后开启了一个 while 循环,循环的意思是:只要 task 对象不为空,那么就会一直调用 task = getTask(),直到获取到的 task 对象为空了才会停止循环,getTask 其实就是往阻塞队列中取出 Runnable 对象。
线程池复用线程的原理,创建 Thread 对象的时候传入的不是我们的 Runnable 对象,而是通过线程池自定义的 Runnable 类,这个类主要的作用不仅是执行我们的 Runnable 对象,当我们传入的任务被某个线程执行完毕之后,它还会遍历阻塞队列中其他未执行的任务,这样就能达到一个线程执行多个 Runnable 对象的效果,这个就是线程池复用线程的原理。
往线程池添加一个新的任务时,如果核心线程处于空闲状态,任务会直接交由核心线程处理,否则任务会存放到阻塞队列中,当阻塞队列中的任务数量超过设定的最大值时,才会开启非核心线程去执行,如果当前任务总量 > 阻塞队列的最大容量 + 最大线程数时,线程池则会拒绝执行该任务。
在这里插入图片描述
https://www.jianshu.com/p/a6c3df1f12c4

java的内存模型

Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。

原子性
在Java中,为了保证原子性,提供了两个高级的字节码指令monitorenter和monitorexit。在synchronized的实现原理文章中,介绍过,这两个字节码,在Java中对应的关键字就是synchronized。因此,在Java中可以使用synchronized来保证方法和代码块内的操作是原子性的。
可见性
Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值的这种依赖主内存作为传递媒介的方式来实现的。Java中的volatile关键字提供了一个功能,那就是被其修饰的变量在被修改后可以立即同步到主内存,被其修饰的变量在每次是用之前都从主内存刷新。因此,可以使用volatile来保证多线程操作时变量的可见性。除了volatile,Java中的synchronized和final两个关键字也可以实现可见性。只不过实现方式不同,这里不再展开了。
有序性
在Java中,可以使用synchronized和volatile来保证多线程之间操作的有序性。实现方式有所区别:volatile关键字会禁止指令重排。synchronized关键字保证同一时刻只允许一条线程操作。好了,这里简单的介绍完了Java并发编程中解决原子性、可见性以及有序性可以使用的关键字。读者可能发现了,好像synchronized关键字是万能的,他可以同时满足以上三种特性,这其实也是很多人滥用synchronized的原因。但是synchronized是比较影响性能的,虽然编译器提供了很多锁优化技术,但是也不建议过度使用。

sleep 和 wait 的区别?

1、sleep 方法是 Thread 类中的静态方法,wait 是 Object 类中的方法。
2、sleep 并不会释放同步锁,而 wait 会释放同步锁。
3、sleep 可以在任何地方使用,而 wait 只能在同步方法或者同步代码块中使用。
4、sleep 中必须传入时间,而 wait 可以传,也可以不传,不传时间的话只有 notify 或者 notifyAll 才能唤醒,传时间的话在时间之后会自动唤醒。

join 的用法

join 方法通常是保证线程间顺序调度的一个方法,它是 Thread 类中的方法。比方说在线程 A 中执行线程 B.join(),这时线程 A 会进入等待状态,直到线程 B 执行完毕之后才会唤醒,继续执行A线程中的后续方法。join 方法可以传时间参数,也可以不传参数,不传参数实际上调用的是 join(0)。它的原理其实是使用了 wait 方法,join 的原理如下:

 public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
 

volatile

一般提到 volatile,就不得不提到内存模型相关的概念。我们都知道,在程序运行中,每条指令都是由 CPU 执行的,而指令的执行过程中,势必涉及到数据的读取和写入。程序运行中的数据都存放在主存中,这样会有一个问题,由于 CPU 的执行速度是要远高于主存的读写速度,所以直接从主存中读写数据会降低 CPU 的效率。为了解决这个问题,就有了高速缓存的概念,在每个 CPU 中都有高速缓存,它会事先从主存中读取数据,在 CPU 运算之后在合适的时候刷新到主存中。
这样的运行模式在单线程中是没有任何问题的,但在多线程中,会导致缓存一致性的问题。举个简单的例子:i=i+1 ,在两个线程中执行这句代码,假设i的初始值为0。我们期望两个线程运行后得到2,那么有这样的一种情况,两个线程都从主存中读取i到各自的高速缓存中,这时候两个线程中的i都为0。在线程1执行完毕得到i=1,将之刷新到主存后,线程2开始执行,由于线程2中的i是高速缓存中的0,所以在执行完线程2之后刷新到主存的i仍旧是1。
所以这就导致了对共享变量的缓存一致性的问题,那么为了解决这个问题,提出了缓存一致性协议:当 CPU 在写数据时,如果发现操作的是共享变量,它会通知其他 CPU 将它们内部的这个共享变量置为无效状态,当其他 CPU 读取缓存中的共享变量时,发现这个变量是无效的,它会从新从主存中读取最新的值。
把一个变量声明为volatile,其实就是保证了可见性和有序性。 可见性我上面已经说过了,在多线程开发中是很有必要的。这个有序性还是得说一下,为了执行的效率,有时候会发生指令重排,这在单线程中指令重排之后的输出与我们的代码逻辑输出还是一致的。但在多线程中就可能发生问题,volatile在一定程度上可以避免指令重排。volatile的原理是在生成的汇编代码中多了一个lock前缀指令,这个前缀指令相当于一个内存屏障,这个内存屏障有3个作用:确保指令重排的时候不会把屏障后的指令排在屏障前,确保不会把屏障前的指令排在屏障后。修改缓存中的共享变量后立即刷新到主存中。当执行写操作时会导致其他CPU中的缓存无效。

在Java的多线程开发中,有三个重要概念:原子性、可见性、有序性
原子性:一个或多个操作要么都不执行,要么都执行。
可见性: 一个线程中对共享变量(类中的成员变量或静态变量)的修改,在其他线程立即可见。
有序性: 程序执行的顺序按照代码的顺序执行。

volatile和synchronize的区别?

volatile它所修饰的变量不保留拷贝,直接访问主内存中的。
在Java内存模型中,有main memory,每个线程也有自己的memory (例如寄存器)。为了性能,一个线程会在自己的memory中保持要访问的变量的副本。这样就会出现同一个变量在某个瞬间,在一个线程的memory中的值可能与另外一个线程memory中的值,或者main memory中的值不一致的情况。 一个变量声明为volatile,就意味着这个变量是随时会被其他线程修改的,因此不能将它cache在线程memory中。
使用场景:
您只能在有限的一些情形下使用 volatile 变量替代锁。要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:1)对变量的写操作不依赖于当前值。2)该变量没有包含在具有其他变量的不变式中。volatile最适用一个线程写,多个线程读的场合。如果有多个线程并发写操作,仍然需要使用锁或者线程安全的容器或者原子变量来代替

synchronized
当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。
区别:
1、volatile是变量修饰符,而synchronized则作用于一段代码或方法。
2、volatile只是在线程内存和“主”内存间同步某个变量的值;而synchronized通过锁定和解锁某个监视器同步所有变量的值, 显然synchronized要比volatile消耗更多资源。
3、volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
4、volatile保证数据的可见性,但不能保证原子性;而synchronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存中和公共内存中的数据做同步。
5、volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。线程安全包含原子性和可见性两个方面,Java的同步机制都是围绕这两个方面来确保线程安全的。关键字volatile主要使用的场合是在多个线程中可以感知实例变量被修改,并且可以获得最新的值使用,也就是多线程读取共享变量时可以获得最新值使用。关键字volatile提示线程每次从共享内存中读取变量,而不是私有内存中读取,这样就保证了同步数据的可见性。

https://juejin.cn/post/6844904136937324552

volatile和

ThreadLocal的作用

ThreadLocal的作用是提供线程内的局部变量,说白了,就是在各线程内部创建一个变量的副本,相比于使用各种锁机制访问变量,ThreadLocal的思想就是用空间换时间,使各线程都能访问属于自己这一份的变量副本,变量值不互相干扰,减少同一个线程内的多个函数或者组件之间一些公共变量传递的复杂度。get函数用来获取与当前线程关联的ThreadLocal的值,如果当前线程没有该ThreadLocal的值,则调用initialValue函数获取初始值返回,initialValue是protected类型的,所以一般我们使用时需要继承该函数,给出初始值。而set函数是用来设置当前线程的该ThreadLocal的值,remove函数用来删除ThreadLocal绑定的值,在某些情况下需要手动调用,防止内存泄露。

死锁

虽然进程在运行过程中,可能发生死锁,但死锁的发生也必须具备一定的条件,死锁的发生必须具备以下四个必要条件。
1)互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
2)请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
3)不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
4)环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。

Java中生产者与消费者模式

生产者消费者模式要保证的是当缓冲区满的时候生产者不再生产对象,当缓冲区空时,消费者不再消费对象。实现机制就是当缓冲区满时让生产者处于等待状态,当缓冲区为空时让消费者处于等待状态。当生产者生产了一个对象后会唤醒消费者,当消费者消费一个对象后会唤醒生产者。

三种种实现方式:wait 和 notify、await 和 signal、BlockingQueue。

wait 和 notify:

//wait和notify
import java.util.LinkedList;
public class StorageWithWaitAndNotify {
    private final int                MAX_SIZE = 10;
    private       LinkedList<Object> list     = new LinkedList<Object>();
    public void produce() {
        synchronized (list) {
            while (list.size() == MAX_SIZE) {
                System.out.println("仓库已满:生产暂停");
                try {
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            list.add(new Object());
            System.out.println("生产了一个新产品,现库存为:" + list.size());
            list.notifyAll();
        }
    }
    public void consume() {
        synchronized (list) {
            while (list.size() == 0) {
                System.out.println("库存为0:消费暂停");
                try {
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            list.remove();
            System.out.println("消费了一个产品,现库存为:" + list.size());
            list.notifyAll();
        }
    }
}

await 和 signal

import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
class StorageWithAwaitAndSignal {
    private final int                MAX_SIZE = 10;
    private       ReentrantLock      mLock    = new ReentrantLock();
    private       Condition          mEmpty   = mLock.newCondition();
    private       Condition          mFull    = mLock.newCondition();
    private       LinkedList<Object> mList    = new LinkedList<Object>();
    public void produce() {
        mLock.lock();
        while (mList.size() == MAX_SIZE) {
            System.out.println("缓冲区满,暂停生产");
            try {
                mFull.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        mList.add(new Object());
        System.out.println("生产了一个新产品,现容量为:" + mList.size());
        mEmpty.signalAll();
        mLock.unlock();
    }
    public void consume() {
        mLock.lock();
        while (mList.size() == 0) {
            System.out.println("缓冲区为空,暂停消费");
            try {
                mEmpty.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        mList.remove();
        System.out.println("消费了一个产品,现容量为:" + mList.size());
        mFull.signalAll();
        mLock.unlock();
    }
}

BlockingQueue

import java.util.concurrent.LinkedBlockingQueue;
public class StorageWithBlockingQueue {
    private final int                         MAX_SIZE = 10;
    private       LinkedBlockingQueue<Object> list     = new LinkedBlockingQueue<Object>(MAX_SIZE);
    public void produce() {
        if (list.size() == MAX_SIZE) {
            System.out.println("缓冲区已满,暂停生产");
        }
        try {
            list.put(new Object());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("生产了一个产品,现容量为:" + list.size());
    }
    public void consume() {
        if (list.size() == 0) {
            System.out.println("缓冲区为空,暂停消费");
        }
        try {
            list.take();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("消费了一个产品,现容量为:" + list.size());
    }
}

线程的几个常见方法的比较

1、Thread.sleep(long millis),一定是当前线程调用此方法,当前线程进入TIMED_WAITING状态,但不释放对象锁,millis后线程自动苏醒进入就绪状态。作用:给其它线程执行机会的最佳方式。
2、Thread.yield(),一定是当前线程调用此方法,当前线程放弃获取的CPU时间片,但不释放锁资源,由运行状态变为就绪状态,让OS再次选择线程。作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。该方法与sleep()类似,只是不能由用户指定暂停多长时间。
3、thread.join()/thread.join(long millis),当前线程里调用其它线程thread的join方法,当前线程进入WAITING/TIMED_WAITING状态,当前线程不会释放已经持有的对象锁。线程thread执行完毕或者millis时间到,当前线程进入就绪状态。
4、thread.interrupt(),当前线程里调用其它线程thread的interrupt()方法,中断指定的线程。
如果指定线程调用了wait()方法组或者join方法组在阻塞状态,那么指定线程会抛出InterruptedException。
5、Thread.interrupted,一定是当前线程调用此方法,检查当前线程是否被设置了中断,该方法会重置当前线程的中断标志,返回当前线程是否被设置了中断。
6、thread.isInterrupted(),当前线程里调用其它线程thread的isInterrupted()方法,返回指定线程是否被中断。
7、object.wait(),当前线程调用对象的wait()方法,当前线程释放对象锁,进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout) timeout时间到自动唤醒。
8、object.notify()唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程。

Object.wait() / Object.notify() Object.notifyAll()

任意一个Java对象,都拥有一组监视器方法(定义在java.lang.Object上),主要包括wait()、wait(long timeout)、notify()以及notifyAll()方法,这些方法与synchronized同步关键字配合,可以实现等待/通知模式。

1、使用的前置条件
当我们想要使用Object的监视器方法时,需要或者该Object的锁,代码如下所示:
synchronized(obj){ … //1 obj.wait();//2 obj.wait(long millis);//2 …//3 }
一个线程获得obj的锁,做了一些时候事情之后,发现需要等待某些条件的发生,调用obj.wait(),该线程会释放obj的锁,并阻塞在上述的代码2处
obj.wait()和obj.wait(long millis)的区别在于:
obj.wait()是无限等待,直到obj.notify()或者obj.notifyAll()调用并唤醒该线程,该线程获取锁之后继续执行代码3obj.wait(long millis)是超时等待,我只等待long millis 后,该线程会自己醒来,醒来之后去获取锁,获取锁之后继续执行代码3obj.notify()是叫醒任意一个等待在该对象上的线程,该线程获取锁,线程状态从BLOCKED进入RUNNABLEobj.notifyAll()是叫醒所有等待在该对象上的线程,这些线程会去竞争锁,得到锁的线程状态从BLOCKED进入RUNNABLE,其他线程依然是BLOCKED,得到锁的线程执行代码3完毕后释放锁,其他线程继续竞争锁,如此反复直到所有线程执行完毕。

synchronized(obj){ … //1 obj.notify();//2 obj.notifyAll();//2 }一个线程获得obj的锁,做了一些时候事情之后,某些条件已经满足,调用obj.notify()或者obj.notifyAll(),该线程会释放obj的锁,并叫醒在obj上等待的线程,obj.notify()和obj.notifyAll()的区别:在于obj.notify()叫醒在obj上等待的任意一个线程(由JVM决定)obj.notifyAll()叫醒在obj上等待的全部线程。

2、使用范式
synchronized(obj){ //判断条件,这里使用while,而不使用ifwhile(obj满足/不满足 某个条件){ obj.wait() } }放在while里面,是防止处于WAITING状态下线程监测的对象被别的原因调用了唤醒(notify或者notifyAll)方法,但是while里面的条件并没有满足(也可能当时满足了,但是由于别的线程操作后,又不满足了),就需要再次调用wait将其挂起。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值