Android 线程的控制及原理 并发
前言:上次讲解讲解了下线程的Android的线程基础,后来觉得其实还有很多值得补充的地方,今天算是把线程这块完结,请大家批评指正。
本文主要包含关键字volitate,Semaphore,AtomicInteger,通过对他们的分析,让你了解到线程同步,并发等情况。
好了,闲话少说,我问先来回顾下线程。
一、原理
线程的原理大家也都很清楚,就是在一个进程中,执行单独的一段代码块,采用多线程的方式,可以实现我们程序的执行效率,节约我们的时间,同时也消耗相对较多的内存以及性能上的资源。
二、实际应用
实际中为什么会用到多线程,管理线程呢,有很多种场景,
1.在Android由于主线程不予许做耗时操作,所以需要选择子线程执行
2.当执行过大数据的传输以及读写时,多线程可以帮助我们缩短时间完成例如上传、下载等工作
3.在压力测试时,会用多线程看看线程例如运行、阻塞情况。
4.服务器编程、分压和监听器,也会用到多线程。
等等
所以说,多线程在实际操作中应用还是比较多的,掌握了多线程,也是对自己在开发设计上的提高。
三、主要内容
1.volitate java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排他锁单独获得这个变量。Java语言提供了volatile,在某些情况下比锁更加方便。如果一个字段被声明成volatile,java线程内存模型确保所有线程看到这个变量的值是一致的。volatile关键字对一个实例的域的同步访问提供了一个免锁(lock-free)机制。如果把域声明为volatile,那么编译器和虚拟机就知道该域可能会被另一个线程并发更新。对象内需要同步的域值少,使用锁显得浪费和繁琐场景,这时使用volatile。一些并发容器(ConcurrentHashMap,etc)的实现内使用了volatile。利用jvm对volatile承诺的happen-before原则。
说了这么多有点懵圈,简单几点:
1)volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;
2)volatile仅能使用在变量级别;
3)volatile仅能实现变量的修改可见性,有序性,不能保证原子性(这个我们下面讲到);
4)volatile不会造成线程的阻塞;
5)volatile标记的变量不会被编译器优化(下面讲到)。
针对3、和5,我们细说
·可见性:在java中每个线程都有自己的线程栈,当一个线程执行需要数据时,会到主存中将需要的数据复制到自己的线程栈中,然后对线程栈中的副本进行操作,再操作完成后再将数据写回到主存中,volitate修饰的变量一旦改变,会立即写入主存,被其他的线程全部更新可以。
·有序性:即程序执行的顺序按照代码的先后顺序执行。我们写代码会有一个先后的顺序,但是那仅仅是我们看到的顺序,但是当编译器编译时会进行指令重排,于是代码的执行顺序有可能和我们想的不一样。例如:
int i = 0; boolean flag = false; //语句3 i = 1; //语句1 flag = true; //语句2
语句1和语句2的执行顺序改变一下对程序的结果并没有什么影响,所以这时可能会改变这两条指令的顺序。那么语句2会不会在语句3之前执行呢,答案是不会呢,因为语句2用到了语句3声明的变量,这时编译器会限制语句的执行顺序来保证程序的正确性。
在单线程中,改变指令的顺序可能不会产生不良后果,但是在多线程中就不一定了。例如:
//线程1: context = loadContext(); // 语句1 inited = true; // 语句2 //线程2:while(!inited ){ sleep() } doSomethingwithconfig(context);由于语句1和语句2没有数据依赖性,所以编译器可能会将两条指令重新排序,如果先执行语句2,这时线程1被阻塞,然后线程2的while循环条件不满足,接着往下执行,但是由于context没有赋值,于是会产生错误,这里如果用volatile关键字对inited变量进行修饰,就不会出现这种问题了,因为当执行到语句2时,必定能保证context已经初始化完毕。
·编译器优化:
volatile int i=10; int j = i; ... int k = i;volatile 告诉编译器i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的可执行码会重新从i的地址读取数据放在k中。
而优化做法是,由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在k中。而不是重新从i里面读。这样以来,如果i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问,不会出错。
·着重讲下原子性:
//记录当前的产品数量 private static volatile int count =0;
public static void main(String []args){ //生产线线程 new Thread(new Producer()).start(); //消费者线程 new Thread(new Consumer()).start(); } //生产者类 static class Producer implements Runnable{ @Override public void run() { while (true){ try { empty.acquire();//等待空位 mutex.acquire();//等待读写锁 count++; mutex.release();//释放读写锁 full.release();//放置产品 System.out.println("消费者=======还剩: "+count+"个产品, 操作线程还剩: "+mutex.availablePermits()+", 产品还剩: "+full.availablePermits()+", 空余位置还剩: "+empty.availablePermits()); //随机休息一段时间,让生产者线程有机会抢占读写锁 Thread.sleep(((int)Math.random())%10); } catch (InterruptedException e) { e.printStackTrace(); } } } } //消费者类 static class Consumer implements Runnable{ @Override public void run() { while (true){ try { full.acquire();//等待产品 mutex.acquire();//等待读写锁 count--; mutex.release();//释放读写锁 empty.release();//释放空位 System.out.println("消费者=======还剩: "+count+"个产品, 操作线程还剩: "+mutex.availablePermits()+", 产品还剩: "+full.availablePermits()+", 空余位置还剩: "+empty.availablePermits()); //随机休息一段时间,让消费者线程有机会抢占读写锁 Thread.sleep(((int)Math.random())%10); } catch (InterruptedException e) { e.printStackTrace(); } } } }
日志:
消费者=======还剩: 3个产品, 操作线程还剩: 3, 产品还剩: 2, 空余位置还剩: 8 消费者=======还剩: 2个产品, 操作线程还剩: 3, 产品还剩: 1, 空余位置还剩: 9 消费者=======还剩: 1个产品, 操作线程还剩: 3, 产品还剩: 0, 空余位置还剩: 10 消费者=======还剩: 1个产品, 操作线程还剩: 3, 产品还剩: 0, 空余位置还剩: 10 消费者=======还剩: 2个产品, 操作线程还剩: 3, 产品还剩: 1, 空余位置还剩: 9 消费者=======还剩: 2个产品, 操作线程还剩: 3, 产品还剩: 1, 空余位置还剩: 9 消费者=======还剩: 2个产品, 操作线程还剩: 3, 产品还剩: 1, 空余位置还剩: 9 消费者=======还剩: 3个产品, 操作线程还剩: 3, 产品还剩: 2, 空余位置还剩: 8执行一段时间的日志:
消费者=======还剩: -3个产品, 操作线程还剩: 3, 产品还剩: 2, 空余位置还剩: 8 消费者=======还剩: -2个产品, 操作线程还剩: 3, 产品还剩: 3, 空余位置还剩: 7 消费者=======还剩: -1个产品, 操作线程还剩: 3, 产品还剩: 4, 空余位置还剩: 6 消费者=======还剩: 0个产品, 操作线程还剩: 3, 产品还剩: 5, 空余位置还剩: 5 消费者=======还剩: -5个产品, 操作线程还剩: 3, 产品还剩: 0, 空余位置还剩: 10 消费者=======还剩: 0个产品, 操作线程还剩: 3, 产品还剩: 5, 空余位置还剩: 5 消费者=======还剩: -1个产品, 操作线程还剩: 3, 产品还剩: 4, 空余位置还剩: 6 消费者=======还剩: -2个产品, 操作线程还剩: 3, 产品还剩: 3, 空余位置还剩: 7 消费者=======还剩: 1个产品, 操作线程还剩: 3, 产品还剩: 6, 空余位置还剩: 4 消费者=======还剩: -2个产品, 操作线程还剩: 3, 产品还剩: 3, 空余位置还剩: 7呀,怎么产品还是负数了????
这也就是volitate的问题所在,上面这段代码看似没有什么问题呀,怎么回事,要知道java中什么是原子性,其实简单理解就是不能再细分的操作,例如 int i =0;这句话是一个赋值语句,而count++呢?其实他并不是一个原子级操作,其实有这么几步骤
1> 从主存中读取当前的count值,
2>将count+1;
3>将步骤2的结果再写入给count;
好,接着往下说:
如果producer在有两个在并发生产的时候(a,b线程),当a执行到count++语句时,从主存中获取了count的当前值(N),而这是a线程受阻,b线程执行了producer并且完成了count++(N+1),而这时候由于a线程已经读取过了count的值,还是之前的N,所以当a,b都执行完成之后,只是对count加了一次,因此,数据就没法得到保证了。
有人会问就会奇怪:volitate变量不是刷新主存吗?是的没错,但是a已经读取了count,所以a线程还是没有增加的旧值。
注意:volitate在读取上面保持同步作用,但在写上面不保持同步。
好,既然volitate无法满足原子性的需求,怎么办呢,怎么实现高并发的操作并保持变量符合逻辑呢?
2.AtomicInteger类
private static AtomicInteger count = new AtomicInteger(0) ;
public static void main(String []args){ //生产线线程 new Thread(new Producer()).start(); //消费者线程 new Thread(new Consumer()).start(); } //生产者类 static class Producer implements Runnable{ @Override public void run() { while (true){ try { empty.acquire();//等待空位 mutex.acquire();//等待读写锁 count.getAndIncrement(); mutex.release();//释放读写锁 full.release();//放置产品 System.out.println("消费者=======还剩: "+count+"个产品, 操作线程还剩: "+mutex.availablePermits()+", 产品还剩: "+full.availablePermits()+", 空余位置还剩: "+empty.availablePermits()); //随机休息一段时间,让生产者线程有机会抢占读写锁 Thread.sleep(((int)Math.random())%10); } catch (InterruptedException e) { e.printStackTrace(); } } } } //消费者类 static class Consumer implements Runnable{ @Override public void run() { while (true){ try { full.acquire();//等待产品 mutex.acquire();//等待读写锁 count.decrementAndGet(); mutex.release();//释放读写锁 empty.release();//释放空位 System.out.println("消费者=======还剩: "+count+"个产品, 操作线程还剩: "+mutex.availablePermits()+", 产品还剩: "+full.availablePermits()+", 空余位置还剩: "+empty.availablePermits()); //随机休息一段时间,让消费者线程有机会抢占读写锁 Thread.sleep(((int)Math.random())%10); } catch (InterruptedException e) { e.printStackTrace(); } } } }日志:
消费者=======还剩: 0个产品, 操作线程还剩: 3, 产品还剩: 0, 空余位置还剩: 10 消费者=======还剩: 0个产品, 操作线程还剩: 3, 产品还剩: 0, 空余位置还剩: 10 消费者=======还剩: 1个产品, 操作线程还剩: 3, 产品还剩: 1, 空余位置还剩: 9 消费者=======还剩: 1个产品, 操作线程还剩: 3, 产品还剩: 1, 空余位置还剩: 9 消费者=======还剩: 0个产品, 操作线程还剩: 3, 产品还剩: 0, 空余位置还剩: 9 消费者=======还剩: 0个产品, 操作线程还剩: 3, 产品还剩: 0, 空余位置还剩: 10 消费者=======还剩: 1个产品, 操作线程还剩: 3, 产品还剩: 1, 空余位置还剩: 9运行1小时日志:
消费者=======还剩: 7个产品, 操作线程还剩: 3, 产品还剩: 7, 空余位置还剩: 3 消费者=======还剩: 8个产品, 操作线程还剩: 3, 产品还剩: 8, 空余位置还剩: 2 消费者=======还剩: 7个产品, 操作线程还剩: 3, 产品还剩: 7, 空余位置还剩: 3 消费者=======还剩: 7个产品, 操作线程还剩: 3, 产品还剩: 7, 空余位置还剩: 3 消费者=======还剩: 6个产品, 操作线程还剩: 3, 产品还剩: 6, 空余位置还剩: 4 消费者=======还剩: 5个产品, 操作线程还剩: 3, 产品还剩: 5, 空余位置还剩: 5 消费者=======还剩: 7个产品, 操作线程还剩: 3, 产品还剩: 7, 空余位置还剩: 3 消费者=======还剩: 4个产品, 操作线程还剩: 3, 产品还剩: 4, 空余位置还剩: 6 消费者=======还剩: 5个产品, 操作线程还剩: 3, 产品还剩: 5, 空余位置还剩: 5 消费者=======还剩: 4个产品, 操作线程还剩: 3, 产品还剩: 4, 空余位置还剩: 6 消费者=======还剩: 5个产品, 操作线程还剩: 3, 产品还剩: 5, 空余位置还剩: 5 消费者=======还剩: 4个产品, 操作线程还剩: 3, 产品还剩: 4, 空余位置还剩: 6搞定。
总结:
1)AtomicInteger是在使用非阻塞算法实现并发控制,在一些高并发程序中非常适合,但并不能每一种场景都适合,不同场景要使用使用不同的数值类。
2)AtomicInteger是一个提供原子操作的Integer类,通过线程安全的方式操作加减。
好,接着我们说说Semaphore。
3.Semaphore类
Semaphore是一种基于计数的信号量。它可以设定一个阈值,基于此,多个线程竞争获取许可信号,做完自己的申请后归还,超过阈值后,线程申请许可信号将会被阻塞。Semaphore可以用来构建一些对象池,资源池之类的,比如数据库连接池,我们也可以创建计数为1的Semaphore,将其作为一种类似互斥锁的机制,这也叫二元信号量,表示两种互斥状态。
它的主要处理流程是:
1、通过Semaphore的acquire()方法申请许可;
2、调用类成员变量sync的acquireSharedInterruptibly(1)方法处理,实际上是父类AbstractQueuedSynchronizer的acquireSharedInterruptibly()方法处理;
3、AbstractQueuedSynchronizer的acquireSharedInterruptibly()方法会先在当前线程未中断的情况下先调用tryAcquireShared()方法尝试获取许可,未获取到则调用doAcquireSharedInterruptibly()方法将当前线程加入等待队列。acquireSharedInterruptibly()
至于如何加入等待队列,还有等待队列的线程如何竞争获取许可,本文不做分析,需要理解AbstractQueuedSynchronizer源码。
4、接下来竞争许可信号的tryAcquireShared()方法则分别由公平性FairSync和非公平性NonfairSync各自实现。
上面的例子也很清晰了,
当信号量都持有semaphore.acquire(),也就是semaphore.availabledPermits()==0时,也就不允许线程访问了;
一旦semaphore.release(),也就是释放锁时,也就是semaphore.availabledPermits()>0时,又有线程可以进行访问了,
推荐阅读
-
Android中onInterceptTouchEvent、dispatchTouchEvent及onTouchEvent的调用顺序及内部原理详解
-
多线程对象及变量的并发访问
-
Android线程—AsyncTask的使用及原理
-
Android多线程断点续传下载原理及实现 AndroidBAT面试多线程
-
Android 线程的控制及原理 并发
-
Android线程池控制并发数多线程下载
-
Java并发编程:Java线程池核心ThreadPoolExecutor的使用和原理分析
-
Android线程专题:AsynchTask的使用场景及源码分析
-
我的并发编程(三):Volatile的底层实现及原理
-
Java并发编程深入理解之Synchronized的使用及底层原理详解 下