版权声明:本文为博主菜菜龙原创文章,转载请注明地址http://blog.csdn.net/jambestwick。
前言:上次讲解讲解了下线程的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:
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
呀,怎么产品还是负数了????
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、通过Semaphore的acquire()方法申请许可;
2、调用类成员变量sync的acquireSharedInterruptibly(1)方法处理,实际上是父类AbstractQueuedSynchronizer的acquireSharedInterruptibly()方法处理;
3、AbstractQueuedSynchronizer的acquireSharedInterruptibly()方法会先在当前线程未中断的情况下先调用tryAcquireShared()方法尝试获取许可,未获取到则调用doAcquireSharedInterruptibly()方法将当前线程加入等待队列。acquireSharedInterruptibly()
至于如何加入等待队列,还有等待队列的线程如何竞争获取许可,本文不做分析,需要理解AbstractQueuedSynchronizer源码。
4、接下来竞争许可信号的tryAcquireShared()方法则分别由公平性FairSync和非公平性NonfairSync各自实现。