JDK5中的多线程并发库

发布时间:2012-08-03 18:56:13   来源:文档文库   
字号:

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包中提供了一些原子类型,可以对IntegerLongBoolean和引用数据类型进行一些常用的操作,这些操作都是具有原子性的。

原子性即为不可分割的,例如我们说一组操作具有原子性,就是指这组操作的执行过程中CPU不会跳转到其他线程工作

常用API

AtomicInteger:具有原子性的Integer

addAndGet(int) 对数字增加指定值,并且返回更新后的值

incrementAndGet() 对数字加1并且返回更新后的值

decrementAndGet() 对数字减1并且返回更新后的值

set(int) 设置为指定数值

AtomicInteger:具有原子性的Integer数组
addAndGet(int, int) 对指定索引位置上的数值增加指定值,并且返回更新后的值

incrementAndGet(int) 将指定索引上的数值加1并返回

decrementAndGet(int) 将指定索引上的数值减1并返回

set(int, int) 将指定索引上的值赋值为指定值

AtomicIntegerFieldUpdaterInteger字段更新器

newUpdater(Class, String) 获取指定类的指定属性的更新器

addAndGet(Object, int) 将指定对象上的属性设置为指定值

7. 线程池

什么是线程池

当我们需要执行多个任务,每个任务都需要一个线程去执行的时候,并不一定需要每次都创建一个线程,因为线程的创建和销毁都是比较消耗性能的。

我们可以事先创建一些线程,存储在一个容器池中,当需要执行任务的时候从池中获取一个线程,任务执行结束之后再将线程还回池中。

JDK5之后,Java中提供了工具类Exetors,用来创建各种线程池。ExecutorServiceScheduledExecutorService

固定大小的线程池

使用newFixedThreadPool(int) 方法创建一个固定大小的线程池,池内线程个数为指定int

调用线程池的execute(Runnable) 方法来添加一个任务,如果池中有空闲线程,将会立即执行任务,如果池中没有空闲线程,任务将会等待池中线程完成之前的任务之后才执行

缓冲线程池

使用newCachedThreadPool () 方法创建一个缓冲线程池,池内最初没有线程

调用线程池的execute(Runnable) 方法来添加一个任务,如果池中有空闲线程,将会立即执行任务,如果池中没有空闲线程则会创建新线程执行,线程空闲60秒后销毁

单独线程池

使用newSingleThreadExecutor() 方法创建一个单独线程池,池内始终只有1个线程,如果该线程被杀死,则会重新创建

调用线程池的execute(Runnable) 方法来添加一个任务,如果池中线程空闲,将会立即执行任务,如果线程忙碌,则等待线程完成上次的任务之后才执行

计时器线程池

使用newScheduledThreadPool(int) 方法创建一个计时器线程池,池内线程数为指定int

调用线程池的execute(Runnable) 可以添加一个立即执行的任务,原理和newFixedThreadPool(int)相同

还可以调用schedule(Runnable, long, TimeUnit) 方法来指定定时任务

CallableFutrue

ExecutorService还可以调用submit(Callable) 方法执行一个任务,在call() 方法中定义任务内容并且返回一个值

submit方法会返回一个Future对象,在任务执行结束时,Future对象的get()方法可以得到call() 方法返回的值,如果调用get()方法时任务未完成,那么线程将阻塞等待任务完成

CompletionService

使用CompletionService可以批量添加任务之后获取最先完成的任务返回的结果

使用构造函数ExecutorCompletionService(Executor) 创建对象,然后使用submit(Callable) 方法添加任务

调用CompletionServicetake() 方法可以获取到最先完成的任务返回的Future对象

8.

ReentrantLock

使用在多线程并发时可以使用ReentrantLock对象的lock()方法开始同步,unlock()方法结束同步。

使用相同Lock对象同步的代码同一时间只能一个线程执行,原理和synchronized相同。

通常解锁的代码会放在finally中执行,避免出现一场无法解锁的问题。

ReentrantReadWriteLock

使用构造函数ReentrantReadWriteLock()可以创建读写锁对象,调用其readLock()可以获取读锁,调用writeLock()方法可以获取写锁

读锁和读锁之间不互斥,写锁和写锁之间互斥,写锁和读锁也互斥

练习

设计一个缓存容器,可以缓存多个对象。当调用缓存容器取数据时如果其中没有查询数据,则从数据库查找,如果有就直接返回。

9. 条件分支

Condition

使用LocknewCondition()方法可以获取一个ConditionCondition对象拥有和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)创建对象,指定等待线程的个数。

在线程开始执行之后可以使用CyclicBarrierawait()方法控制先到的线程等待,直到等待的线程到达指定个数时所有线程继续。

类似集体旅游爬山,从山脚开始爬山每个人速度不同,到达山顶的时间不同,但是先到的等待后来的一起开饭。饭后下山的速度也有所不同,先到的在车上等待后来的一起开车回家。

CountDownLatch

可以在多线程并发执行时设置等待,等待倒计时结束之后继续。

使用构造函数CountDownLatch(int)创建对象,指定倒计时次数。

在线程开始之后可以使用await()方法控制线程等待倒计时,使用countDown()方法进行倒计时,当调用countDown()到达指定次数之后await()的线程继续执行。

类似于田径赛跑,运动员等待裁判倒数开始,裁判等待运动员到达终点。

Exchanger

可以在多线程并发时设置等待,等待另一线程运行到指定位置,并且交换数据。

使用构造函数Exchanger()创建对象。

在线程开始之后可以使用exchange(Object)方法控制当前线程等待,直到有另一个线程也调用该方法时交换数据,并继续执行。

类似于买卖双方约定交易地点,其中一方先到之后等待另外一方,双方到齐之后一手交钱一手交货。

11. 阻塞队列

BlockingQueue

阻塞队列,可以支持多个线程向队列中添加获取元素。

BlockingQueue 为数组实现的固定大小的阻塞队列

LinkedBlockingQueue 为链表实现的不固定大小的阻塞队列

练习:使用BlockingQueue完成线程之间的通信,3个线程轮流打印

12. 同步集合

ConcurrentHashMap 线程安全的HashMap,类似于Hashtable

ConcurrentSkipListMap 线程安全的TreeMap

ConcurrentSkipListSet 线程安全的TreeSet

CopyOnWriteList 线程安全的List

13. 练习题

第一题

现有的程序代码模拟产生了16个日志对象,并且需要运行16秒才能打印完这些日志。

请在程序中增加4个线程去调用parseLog()方法来分头打印这16个日志对象,程序只需要运行4秒即可打印完这些日志对象。

原始代码如下:

package cn.itcast.test;

import java.util.Date;

/*

* 模拟处理16行日志,下面的代码产生了16个日志对象,当前代码需要运行16秒才能打印完这些日志。

* 修改程序代码,开四个线程让这16个对象在4秒钟打完。

*/

public class Test1 {

public static void main(String[] args) {

for (int i = 0; i < 16; i++) { //这行代码不能改动

final String log = "" + (i + 1); //这行代码不能改动

Test1.parseLog(log);

}

}

public static void parseLog(String log) { //parseLog方法内部的代码不能改动

System.out.println(log + ": " + new Date());

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

第二题

程序中的Test类中的代码在不断地产生数据,然后交给TestDo.doSome()方法去处理。就好像生产者在不断地产生数据,消费者在不断消费数据。

请将程序改造成有10个线程来消费生成者产生的数据,这些消费者都调用TestDo.doSome()方法去进行处理,故每个消费者都需要一秒才能处理完

程序应保证这些消费者线程依次有序地消费数据,同一时间只能有2个线程在消费数据,总共执行5秒。

原始代码如下:

package cn.itcast.test;

import java.util.Date;

public class Test2 {

public static void main(String[] args) {

for (int i = 0; i < 10; i++) { //这行不能改动

final String input = i + ""; //这行不能改动

System.out.println(Thread.currentThread().getName() + ": " + TestDo.doSome(input));

}

}

}

class TestDo { //不能改动此TestDo

public static String doSome(String input) {

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

String output = input + ": " + new Date();

return output;

}

}

第三题

TestThread类在创建对象时需要传入keyvalue 当运行run()方法时会将keyvalue打印。

如果直接运行以下代码,4个线程会同时执行,结果如下:

2: b: Wed Nov 30 13:53:13 CST 2011

1: a: Wed Nov 30 13:53:13 CST 2011

1: d: Wed Nov 30 13:53:13 CST 2011

3: c: Wed Nov 30 13:53:13 CST 2011

要求修改代码,4个线程中如果有key相同的(equals相同,不是地址相同),则不能同时执行,需要运行出如下结果:

2: b: Wed Nov 30 13:53:13 CST 2011

1: a: Wed Nov 30 13:53:13 CST 2011

3: c: Wed Nov 30 13:53:13 CST 2011

1: d: Wed Nov 30 13:53:14 CST 2011

package cn.itcast.test;

import java.util.Date;

public class Test3 {

public static void main(String[] args) {

Thread t1 = new TestThread("1", "a");

Thread t2 = new TestThread("2", "b");

Thread t3 = new TestThread("3", "c");

Thread t4 = new TestThread("1", "d");

t1.start();

t2.start();

t3.start();

t4.start();

}

}

class TestThread extends Thread {

private Object key;

private String value;

public TestThread(Object key, String value) {

this.key = key;

this.value = value;

}

public void run() {

// 大括号以内的代码不能改动!

{

try {

Thread.sleep(1000);

System.out.println(key + ": " + value + ": " + new Date());

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

本文来源:https://www.2haoxitong.net/k/doc/cb94e502cc17552707220877.html

《JDK5中的多线程并发库.doc》
将本文的Word文档下载到电脑,方便收藏和打印
推荐度:
点击下载文档

文档为doc格式