java5线程并发库
线程并发库是JDK 1.5版本级以上才有的针对线程并发编程提供的一些常用工具类,这些类被封装在java.concurrent包下。
该包下又有两个子包,分别是atomic和locks两个包。
java.util.concurrent.atomic包
atomic包提供了一些线程相关的工具类,来实现共享数据在多个线程中保持安全访问而不用使用 synchronized关键字进行同步。下面是该报下的一些类。
这里就拿AtomicInteger类来举例,其他类的操作基本上和该类差不多。在JDK的API中说该类可以以原子的方式操作int值,通俗的说就是该类提供了一下对整数类型变量的操作使用该类可以确保在多个线程中访问同一个整数资源时及时不适用锁机制来保持同步也依然能够确保该变数据的安全。下面是给类提供的一些方法。
1 package cn.wz.traditional.wf;
2
3 import java.util.concurrent.atomic.AtomicInteger;
4
5 /**
6 * Created by WangZhe on 2017/5/8.
7 */
8 public class AtomicTest {
9 static AtomicInteger data =new AtomicInteger(10);
10 public static void main(String[] args) {
11 new Thread(new Runnable() {
12 public void run() {
13 int resoult = data.addAndGet(10);
14 System.out.println(resoult);
15 }
16 }).start();
17 new Thread(new Runnable() {
18 public void run() {
19 int resoult = data.get();
20 System.out.println(resoult);
21 }
22 }).start();
23 }
24 }
以addAndGet方法为例的一个demo
那么Atomic包中的类为什么能够实现数据操作的原始性呢?这个我就不得而知了,因为前sun公司并没有把它体现在源码中。我只知道在addAndGet方法中调用get()方法获取变量的原有值,而get方法中返回的value字段被volatile关键字标示,该关键子线程每次获取该变量的值时都会去主内存区获取该变量最新的值,但是这里值得注意的是volatile关键字并不能像synchronized关键字一样确保变量的原子性,至于AtomicInteger是如何确保其原子性的我也不得而知,希望有知道的大神能够分享下,再此先谢过了!下面附上addAndGet方法的源码.
该方法实现被前sun公司隐藏。
Unsafe类的compareAndSwapInt方法(真正的为变量赋值操作)被隐藏
好了其源码就看到这里了其它的类和方法也都差不多,如果有兴趣你可以自己看看源码(然而并没有什么卵用,呵呵),其实我们也不用纠结于其实怎么实现的我们只要知道其可以确保原子性,在需要的是后能够使用就可以了,其和 synchronized同步代码块而言效率和性能是较高的,但其只能适用于一些简单的赋值运算操作,因为我们可以看到其并没有提供乘除以及其它的复杂操作。
java.util.concurrent.locks包
该包提供了一些关于线程锁相关的一些接口和类,我们之前使用synchronized关键字只能设置对象、类、变量等固定的锁,而是用该包则可以时锁变得更加灵活和广泛,该包有三大接口
Condition:Condition
将 Object
监视器方法(wait
、notify
和 notifyAll
)分解成截然不同的对象,以便通过将这些对象与任意 Lock
Lock:Lock
实现提供了比使用 synchronized
方法和语句可获得的更广泛的锁定操作。
ReadWriteLock:ReadWriteLock 维护了一对相关的锁
,一个用于只读操作,另一个用于写入操作。
Lock 接口方法摘要:
1 package cn.wz.traditional.wf;
2
3 import java.util.concurrent.locks.Lock;
4 import java.util.concurrent.locks.ReentrantLock;
5
6 /**
7 * Created by WangZhe on 2017/5/8.
8 */
9 public class LockDemo {
10 static int data=5;
11 public static void main(String[] args) {
12 final Lock lock=new ReentrantLock(true);
13 new Thread(new Runnable() {
14 public void run() {
15 lock.lock();
16 try {
17 System.out.println("线程1获取锁\t时间:"+System.currentTimeMillis());
18 data+=5;
19 System.out.println("线程1休眠1秒钟\t正在等待该所的线程:"+((ReentrantLock)lock).getQueueLength());
20 System.out.println(data);
21 Thread.currentThread().sleep(1000);
22 }catch (Exception e){
23 lock.unlock();
24 }finally {
25 lock.unlock();
26 };
27 }
28 }).start();
29 new Thread(new Runnable() {
30 public void run() {
31 lock.lock();
32 try {
33 System.out.println("线程2获取锁\t时间:"+System.currentTimeMillis());
34 System.out.println(data);
35 }catch (Exception e){
36 lock.unlock();
37 }finally {
38 lock.unlock();
39 };
40 }
41 }).start();
42 }
43 }
LockDemo
从上面的demo中可以看出Lock接口不再指定同步锁定的对象,而是一起本身为锁对象别synchronized更加灵活且没有synchronized关键字必须在同一个代码块儿的限制。上面demo中使用的ReentrantLock类是Lock接口的实现类Lock接口下面还提供了控制读写锁的实现类:ReentrantReadWriteLock.ReadLock和ReentrantReadWriteLock.WriteLock两个类,而这两个类都是ReentrantReaWriteLock类的内部类,如图所示:
而ReentrantReadWriteLock类是ReadWriteLock接口的实现类。这里就在顺便说一下这个接口。该接口只有两个方法定义如下所示:
一个适用于获取ReadLock的锁另一个是用于获取WriteLock的锁这两个锁一般成对出现用来分离数据的读和写进行加锁,比如说一个线程对A资源进行写入操作时另一个线程读取A资源时就需要等待上一个线程释放A资源的写入锁才能获取A资源的读取锁进行数据读取,反之亦然。
1 package cn.wz.traditional.wf;
2
3 import java.util.concurrent.locks.Lock;
4 import java.util.concurrent.locks.ReentrantReadWriteLock;
5
6 /**
7 * Created by WangZhe on 2017/5/8.
8 */
9 public class ReadLockDemo {
10 static int data=10;
11
12 public static void main(String[] args) {
13 final ReentrantReadWriteLock RWlock = new ReentrantReadWriteLock();//获取读写锁实例
14 final ReentrantReadWriteLock.ReadLock Rlock=RWlock.readLock();//获取读取所实例
15 final ReentrantReadWriteLock.WriteLock Wlock=RWlock.writeLock();//获取写入锁实例
16 new Thread(new Runnable() {
17 public void run() {//线程一
18 Wlock.lock();
19 try{
20 data=5;
21 System.out.println(data);
22 System.out.println("线程一写入数据完毕保持写入锁休眠10秒钟");
23 System.out.println("当前时间:"+System.currentTimeMillis());
24 Thread.currentThread().sleep(10000);
25 }catch (Exception e){
26 Wlock.unlock();
27 }finally {
28 Wlock.unlock();
29 }
30 }
31 }).start();
32 new Thread(new Runnable() {
33 public void run() {//线程二
34 Rlock.lock();
35 try{
36 System.out.println("线程二获取读取锁,开始进行数据读取");
37 System.out.println("当前时间:"+System.currentTimeMillis());
38 System.out.println(data);
39 }catch (Exception e){
40 Rlock.unlock();
41 }finally {
42 Rlock.unlock();
43 }
44 }
45 }).start();
46 }
47 }
读写锁演示demo
Condition接口
Condition接口的出现代替了Object对象的三个改变线程的方法(wait、notify、notifyall)如果说Lock相当于synchronized关键字那Condition就相当于Object监视器的方法。下面是该接口的方法
其中await方法相当于Object的wait方法,Signal方法相当于Object的notify方法,signalAll方法相当于Object对像的notifyAll方法。这里不再详细说明直接通过demo来演示效果。场景如下:
AB两个线程同时操作资源i A线程对i进行加操作,B线程对i进行减操作,当i大于5时A线程等待让B线程进行减操作,反之B线程等待让A线程进行加操作。
1 package cn.wz.traditional.wf;
2
3 import java.util.concurrent.locks.Condition;
4 import java.util.concurrent.locks.Lock;
5 import java.util.concurrent.locks.ReentrantLock;
6
7 /**
8 * Created by WangZhe on 2017/5/9.
9 */
10 public class ConditionDemo {
11 public static void main(String[] args) {
12 final Lock lock=new ReentrantLock();
13 final Condition conditionInc=lock.newCondition();;
14 final Condition conditionDec=lock.newCondition();
15 final DemoData data=new DemoData();
16 new Thread(new Runnable() {
17 public void run() {
18 lock.lock();
19 try {
20 for (int j = 0; j < 50; j++) {
21 while (data.getI() > 5) {
22 conditionInc.await();
23 }
24 data.inc();
25 conditionDec.signal();
26 }
27 } catch (Exception e) {
28 lock.unlock();
29 } finally {
30 lock.unlock();
31 }
32 }
33 }).start();
34 new Thread(new Runnable() {
35 public void run() {
36 lock.lock();
37 try{
38 for(int j=0;j<50;j++) {
39 while (data.getI() <= 5) {
40 conditionDec.await();
41 }
42 data.dec();
43 conditionInc.signal();
44 }
45 }catch (Exception e){
46 lock.unlock();
47 }finally {
48 lock.unlock();
49 }
50 }
51 }).start();
52 }
53 static class DemoData{
54 private int i;
55
56 public DemoData(int i) {
57 this.i = i;
58 }
59
60 public DemoData() {
61 }
62 public void inc(){
63 i++;
64 System.out.println("线程A进行加操作后i的值:"+i);
65 }
66 public void dec(){
67
68 i--;
69 System.out.println("线程B进行减操作后i的值:"+i);
70 }
71 public int getI() {
72 return i;
73 }
74
75 public void setI(int i) {
76 this.i = i;
77 }
78 }
79 }
Demo
java线程池的应用
我们知道线程一旦死亡就无法复活(进行重新启动线程。)但是在黑马的面试题中关于java并发的问题中有这样一道面试题:“如何重新启动一个已经死亡的线程?”。刚开始碰到这个问题的人可能会很惊讶,为什么会有这样的面试题呢?我们先来看下面一个demo算不算是重新启动了一个死亡的线程。
1 package cn.wz.traditional.wf;
2
3 import com.sun.org.apache.xpath.internal.SourceTree;
4
5 import java.util.concurrent.Executor;
6 import java.util.concurrent.ExecutorService;
7 import java.util.concurrent.Executors;
8 import java.util.concurrent.ThreadPoolExecutor;
9
10 /**
11 * Created by WangZhe on 2017/5/9.
12 */
13 public class ExecutorSignlePool {
14 public static void main(String[] args) {
15 ExecutorService executorService = Executors.newSingleThreadExecutor();
16 executorService.execute(new MyRunnable());
17
18 }
19 static class MyRunnable implements Runnable{
20 public void run() {
21 for (int i=0;i<10;i++){
22 if(i==5)
23 Thread.currentThread().stop();
24 System.out.println("hello");
25 }
26 }
27 }
28 }
Demo
从上面例子中可以看到我没在循环到第五次的时候停止当前线程使其死亡,但是我们从结果中可以看到程序并没有结束,那就说明还有线程正在运行,那我们是否可以这样假设当停止当前线程时线程池(executorService)一直试图重新启动线程但重启之后立马又被我们的代码关掉所以一直执行不下去,但也不会结束。那具体是不是这样我也不知道(哈哈),但我知道的是单例线程池(也就是我们上边demo中的executorService)中只能存在一个线程,当期线程死亡后会立马创建一个新的线程, 从一定程度上也可以说是重新启动了一个线程,而且即使线程执行完毕也不会结束除非手动停止线程池
1 package cn.wz.traditional.wf;
2
3 import com.sun.org.apache.xpath.internal.SourceTree;
4
5 import java.util.concurrent.Executor;
6 import java.util.concurrent.ExecutorService;
7 import java.util.concurrent.Executors;
8 import java.util.concurrent.ThreadPoolExecutor;
9
10 /**
11 * Created by WangZhe on 2017/5/9.
12 */
13 public class ExecutorSignlePool {
14 public static void main(String[] args) {
15 ExecutorService executorService = Executors.newSingleThreadExecutor();
16 MyRunnable myRunnable = new MyRunnable();
17 executorService.execute(myRunnable);
18 executorService.execute(myRunnable);
19
20 }
21 static class MyRunnable implements Runnable{
22 public void run() {
23 System.out.println("开始执行");
24 System.out.println("hello");
25 }
26 }
27 }
demo
除了单线程池意外我们开可以创建固定大小的线程池(newFixedThreadPool方法创建)和缓存线程池(newCachedThreadPool方法创建)这两个方法都返回ExecutorService类型对象。
固定大小的线程池:该池中的线程个数是固定的当线程任务(Runnable对象)个数大于设置的线程个数时超出的任务等待之前任务执行后再执行。
缓存线程池:该池初始没有线程,当有任务进来时创建线程并在执行结束后缓存一段时间。在改时间之内有新的任务进来则不再创建新线程直接使用原有线程。