JDK5中的多线程并发库.docx
《JDK5中的多线程并发库.docx》由会员分享,可在线阅读,更多相关《JDK5中的多线程并发库.docx(10页珍藏版)》请在冰豆网上搜索。
JDK5中的多线程并发库
1.线程
Ø线程与进程的关系
进程是一个正在执行中的程序,线程是程序执行过程中的一条分支,一个进程可以有多个线程。
Ø创建线程的方式
•Thread子类
自定义一个类继承Thread类,重写其中run()方法。
创建该类对象并调用start()方法,就会开启一条新线程并执行run()方法中的代码。
•Runnable
自定义一个类实现Runnable接口,重启其中run()方法。
创建该类对象并创建Thread对象,在创建Thread对象时将Runnbale对象传入构造函数。
调用Thread对象的start()方法时就会开启线程运行Runnbale对象的run()方法中的代码。
ØThread类常用方法
•sleep(long)
当前线程休眠指定毫秒
•currentThread()
获取当前线程对象
•getName()
获取当前线程的名字
•setName(String)
设置当前线程的名字
•setDaemon(boolean)
设置线程为守护线程,守护线程在进程中没有任何非守护线程执行时自动结束,该方法只能在线程调用start()之前使用
•join()
当前线程暂停,等待加入的线程运行结束之后继续
2.计时器
Ø什么是计时器
Timer是一种工具,用其安排在后台线程中执行的任务。
可安排任务执行一次,或者定期重复执行。
可以通过构造函数设置其线程名及是否为守护线程。
Ø安排任务
•schedule(TimerTask,long)
安排任务,延迟指定毫秒后执行
•schedule(TimerTask,Date)
安排任务,在指定时间执行
•schedule(TimerTask,long,long)
安排任务,延迟指定毫秒后执行第一次,之后每间隔指定毫秒重复运行
•schedule(TimerTask,Date,long)
安排任务,在指定时间时执行第一次,之后每间隔指定毫秒重复运行
Ø练习
使用计时器安排任务,先2秒执行一次,然后4秒执行一次,再2秒执行一次,4秒执行一次,循环……
使用计时器安排任务,周一到周五每天凌晨4点执行。
3.同步
Ø同步代码块
使用synchronized(锁对象){同步代码}形式进行同步,多个线程执行同步代码块时如果使用的锁对象相同,只能有一个线程执行。
Ø同步方法
使用synchronized关键字修饰方法,这时整个方法都是同步的,使用this作为锁对象。
Ø静态同步方法
静态方法也可以使用synchronized关键字修饰,方法内部的代码也是同步的,这时的锁对象是当前类的Class对象。
4.通信
Ø等待
在同步代码中调用锁对象的wait()方法,可以让当前线程等待
Ø通知唤醒
使用锁对象的notify()方法可以唤醒在该对象上等待的随机一个线程
使用锁对象的notifyAll()方法可以唤醒在该对象上等待的所有线程
Ø练习
创建2个线程,其中一个线程内部执行3次打印,另外一个线程内部执行5次打印,第一个线程再执行3次,第二个执行5次,如此交替执行10次。
5.线程范围内共享数据
Ø应用场景
在程序开发过程中我们经常需要在同一个线程中共享数据,例如我们常见的银行转账的案例,转入和转出是同一个线程上执行的两个方法,他们应该共享一个事务对象。
Ø解决方案
•自定义Map
使用一个Key对象为Thread类型的Map用来保存数据。
在存储对象时将当前线程存为Key,数据存为Value。
获取对象时使用当前线程对象即可获取到线程内部共享的数据。
•ThreadLocal
Java中为我们提供了一个和当前线程相关的容器ThreadLocal。
当调用其set()方法时会将数据和当前线程绑定,调用get()方法时则是获取和当前线程绑定的数据。
一个ThreadLocal只能存储一个数据,如果有多个数据需要在线程范围内共享,可以创建多个ThreadLocal,或者将多个数据存入一个对象,将对象存入ThreadLocal。
Ø练习
开启4个线程,线程开启后均为死循环,它们同时操作一个int变量,2个线程中对变量每次加3并且打印,另外2个线程中对变量每次减3并打印。
6.原子类型
Ø什么是原子类型
JDK5之后,Java中的java.util.concurrent.atomic包中提供了一些原子类型,可以对Integer、Long、Boolean和引用数据类型进行一些常用的操作,这些操作都是具有原子性的。
原子性即为不可分割的,例如我们说一组操作具有原子性,就是指这组操作的执行过程中CPU不会跳转到其他线程工作
Ø常用API
•AtomicInteger:
具有原子性的Integer
addAndGet(int)对数字增加指定值,并且返回更新后的值
incrementAndGet()对数字加1并且返回更新后的值
decrementAndGet()对数字减1并且返回更新后的值
set(int)设置为指定数值
•AtomicIntegerArray:
具有原子性的Integer数组
addAndGet(int,int)对指定索引位置上的数值增加指定值,并且返回更新后的值
incrementAndGet(int)将指定索引上的数值加1并返回
decrementAndGet(int)将指定索引上的数值减1并返回
set(int,int)将指定索引上的值赋值为指定值
•AtomicIntegerFieldUpdater:
Integer字段更新器
newUpdater(Class,String)获取指定类的指定属性的更新器
addAndGet(Object,int)将指定对象上的属性设置为指定值
7.线程池
Ø什么是线程池
当我们需要执行多个任务,每个任务都需要一个线程去执行的时候,并不一定需要每次都创建一个线程,因为线程的创建和销毁都是比较消耗性能的。
我们可以事先创建一些线程,存储在一个容器池中,当需要执行任务的时候从池中获取一个线程,任务执行结束之后再将线程还回池中。
JDK5之后,Java中提供了工具类Exetors,用来创建各种线程池。
ExecutorService、ScheduledExecutorService
Ø固定大小的线程池
使用newFixedThreadPool(int)方法创建一个固定大小的线程池,池内线程个数为指定int值
调用线程池的execute(Runnable)方法来添加一个任务,如果池中有空闲线程,将会立即执行任务,如果池中没有空闲线程,任务将会等待池中线程完成之前的任务之后才执行
Ø缓冲线程池
使用newCachedThreadPool()方法创建一个缓冲线程池,池内最初没有线程
调用线程池的execute(Runnable)方法来添加一个任务,如果池中有空闲线程,将会立即执行任务,如果池中没有空闲线程则会创建新线程执行,线程空闲60秒后销毁
Ø单独线程池
使用newSingleThreadExecutor()方法创建一个单独线程池,池内始终只有1个线程,如果该线程被杀死,则会重新创建
调用线程池的execute(Runnable)方法来添加一个任务,如果池中线程空闲,将会立即执行任务,如果线程忙碌,则等待线程完成上次的任务之后才执行
Ø计时器线程池
使用newScheduledThreadPool(int)方法创建一个计时器线程池,池内线程数为指定int值
调用线程池的execute(Runnable)可以添加一个立即执行的任务,原理和newFixedThreadPool(int)相同
还可以调用schedule(Runnable,long,TimeUnit)方法来指定定时任务
ØCallable和Futrue
ExecutorService还可以调用submit(Callable)方法执行一个任务,在call()方法中定义任务内容并且返回一个值
submit方法会返回一个Future对象,在任务执行结束时,Future对象的get()方法可以得到call()方法返回的值,如果调用get()方法时任务未完成,那么线程将阻塞等待任务完成
ØCompletionService
使用CompletionService可以批量添加任务之后获取最先完成的任务返回的结果
使用构造函数ExecutorCompletionService(Executor)创建对象,然后使用submit(Callable)方法添加任务
调用CompletionService的take()方法可以获取到最先完成的任务返回的Future对象
8.锁
ØReentrantLock
使用在多线程并发时可以使用ReentrantLock对象的lock()方法开始同步,unlock()方法结束同步。
使用相同Lock对象同步的代码同一时间只能一个线程执行,原理和synchronized相同。
通常解锁的代码会放在finally中执行,避免出现一场无法解锁的问题。
ØReentrantReadWriteLock
使用构造函数ReentrantReadWriteLock()可以创建读写锁对象,调用其readLock()可以获取读锁,调用writeLock()方法可以获取写锁
读锁和读锁之间不互斥,写锁和写锁之间互斥,写锁和读锁也互斥
Ø练习
设计一个缓存容器,可以缓存多个对象。
当调用缓存容器取数据时如果其中没有查询数据,则从数据库查找,如果有就直接返回。
9.条件分支
ØCondition
使用Lock的newCondition()方法可以获取一个Condition,Condition对象拥有和Object类似的wait()、notify()、notifyAll()功能,分别为await()、signal()、signalAll()
区别是如果使用synchronized同步时只能使用锁对象来wait()、notify()、notifyAll(),这时notify()方法只能唤醒随机一个线程
而使用Condition时可以创建多个分支对象,让线程在不同的分支上等待,并且可以唤醒指定分支上的线程
Ø练习
创建3个线程,其中一个线程内部执行3次打印,一个线程内部执行5次打印,另外一个线程内部执行7次.打印。
第一个线程再执行3次,第二个执行5次,第三个执行7次,如此交替执行10次。
10.同步工具类
ØSemaphore
可以在多个线程并发时指定同时执行线程的个数。
使用构造函数Semaphore(int)创建信号灯,指定并发个数。
在线程开始执行后调用acquire()方法占用一个并发数,线程结束时使用release()释放一个并发数。
类似银行排号功能,多个客户即为多条线程,并发数量取决于银行开了几个窗口,其他客户等待前面客户办理业务结束之后排队继续。
ØCyclicBarrier
可以在多线程并发执行的时设置标记,等待其他线程。
使用构造函数CyclicBarrier(int)创建对象,指定等待线程的个数。
在线程开始执行之后可以使用CyclicBarrier的await()方法控制先到的线程等待,直到等待的线程到达指定个数时所有线程继续。
类似集体旅游爬山,从山脚开始爬山每个人速度不同,到达山顶的时间不同,但是先到的等待后来的一起开饭。
饭后下山的速度也有所不同,先到的在车上等待后来的一起开车回家。
ØCountDownLatch
可以在多线程并发执行时设置等待,等待倒计时结束之后继续。
使用构造函数CountDownLatch(int)创建对象,指定倒计时次数。
在线程开始之后可以使用await()方法控制线程等待倒计时,使用countDown()方法进行倒计时,当调用countDown()到达指定次数之后await()的线程继续执行。
类似于田径赛跑,运动员等待裁判倒数开始,裁判等待运动员到达终点。
ØExchanger
可以在多线程并发时设置等待,等待另一线程运行到指定位置,并且交换数据。
使用构造函数Exchanger()创建对象。
在线程开始之后可以使用exchange(Object)方法控制当前线程等待,直到有另一个线程也调用该方法时交换数据,并继续执行。
类似于买卖双方约定交易地点,其中一方先到之后等待另外一方,双方到齐之后一手交钱一手交货。
11.阻塞队列
ØBlockingQueue
阻塞队列,可以支持多个线程向队列中添加获取元素。
ArrayBlockingQueue为数组实现的固定大小的阻塞队列
LinkedBlockingQueue为链表实现的不固定大小的阻塞队列
练习:
使用BlockingQueue完成线程之间的通信,3个线程轮流打印
12.同步集合
ConcurrentHashMap线程安全的HashMap,类似于Hashtable
ConcurrentSkipListMap线程安全的TreeMap
ConcurrentSkipListSet线程安全的TreeSet
CopyOnWriteArrayList线程安全的ArrayList