------- android培训、java培训、期待与您交流! ----------
回顾一下至今为止接触的所有的java面试题,大概可以分为两部分:一部分是,对于一些关于java基础的某个知识点的理解,
比如说说明一下面向对象的思想,类的加载机制等,另外一部分也是比较实际比较棘手的一部分,就是给你一个项目,让你回去做,
而这些项目的实现大都用到了java多线程技术,比如说张老师所讲的两个7k面试题,交通灯和银行调度系统,都是借助了多线程的技术,
所以个人感觉java多线程这部分的内容还是很重要的,至少对于解决面试来说是重要的。所以这篇学习日记记录的是jdk1.5以后出现的
关于java多线程的新技术。
一、多线程技术
线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。
当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。
在线程池的编程模式下,任务是提交给整个线程池,而不是直接交给某个线程,线程池在拿到任务后,它就在内部找有无空闲的线程,
再把任务交给内部某个空闲的线程,这就是封装。记住,任务是提交给整个线程池,一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。
线程池的分类:
固定尺寸的线程池、可变尺寸连接池
jdk中对于线程池提供的api:
Executor接口:是用来执行Runnable任务的
方法:
execute(Runnable command):执行Ruannable类型的任务
ExecutorService 接口 ExecutorService继承了Executor的方法,并提供了执行Callable任务和中止任务执行的服务
方法:
void shutdown() --在完成已提交的任务后关闭服务,不再接受新任务
Future<T> submit(Runnable task) --提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。
Future<T> submit(Callable<T> task) --提交一个返回值的任务用于执行,返回一个表示任务的未决结果的 Future。
实现这个接口的类有:
ThreadPoolExecutor --它使用可能的几个池线程之一执行每个提交的任务,通常使用 Executors 工厂方法配置。
ScheduledExecutorService 接口,可安排在给定的延迟后运行或定期执行的命令(用作定时器)
方法:
ScheduledFuture<?> schedule(Runnable command,long delay,TimeUnit unit) --创建并执行在给定延迟后启用的一次性操作。
ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay, long period,TimeUnit unit)
--解读参数:
command - 要执行的任务
initialDelay - 首次执行的延迟时间
period - 连续执行之间的周期
unit - initialDelay 和 period 参数的时间单位
实现类
ScheduledThreadPoolExecutor --继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。
callable(Runnable task): 将Runnable的任务转化成Callable的任务
newSingleThreadExecutor: 产生一个ExecutorService对象,这个对象只有一个线程可用来执行任务,若任务多于一个,任务将按先后顺序执行。
newCachedThreadPool(): 产生一个ExecutorService对象,这个对象带有一个线程池,线程池的大小会根据需要调整,线程执行完任务后返回线程池,供执行下一次任务使用。
newFixedThreadPool(int poolSize):产生一个ExecutorService对象,这个对象带有一个大小为poolSize的线程池,若任务数量大于poolSize,任务会被放在一个queue里顺序执行。
newSingleThreadScheduledExecutor:产生一个ScheduledExecutorService对象,这个对象的线程池大小为1,若任务多于一个,任务将按先后顺序执行。
newScheduledThreadPool(int poolSize): 产生一个ScheduledExecutorService对象,这个对象的线程池大小为poolSize,若任务数量大于poolSize,任务会在一个queue里等待执行
二、带有返回结果的线程
jdk中的api的支持:
Callable 接口 -- Callable 接口类似于 Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。
但是 Runnable 不会返回结果,并且无法抛出经过检查的异常。
方法:
V call() 计算结果,如果无法计算结果,则抛出一个异常 。相似于run()方法
Future 接口 -- 表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。
计算完成后只能使用 get 方法来获取结果,如有必要,计算完成前可以阻塞此方法。取消则由 cancel 方法来执行。
方法:
V get(long timeout, TimeUnit unit) --如有必要,最多等待为使计算完成所给定的时间之后,获取其结果(如果结果可用)。
FutureTask 类 :是 Future 的一个实现,Future 可实现 Runnable,所以可通过 Executor 来执行。
代码实现:
在说明这个问题之前,首先我想说一下在5.0之前锁定的锁定的功能是由Synchronized关键字来实现的,这样做存在几个问题:
每次只能对一个对象进行锁定。若需要锁定多个对象,编程就比较麻烦,一不小心就会出现死锁现象。
如果线程因拿不到锁定而进入等待状况,是没有办法将其打断的。
而jdk1.5对锁的实现却很好的避免这个问题:
jdk中对新锁的api支持:
Lock 接口 -- 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。
方法:
lock(): --请求锁定,如果锁已被别的线程锁定,调用此方法的线程被阻断进入等待状态。
unlock():取消锁定,需要注意的是Lock不会自动取消,编程时必须手动解锁。
实现类:ReentrantLock 无特别的方法
ReadWriteLock 接口:
为了提高效率有些共享资源允许同时进行多个读的操作,但只允许一个写的操作,比如一个文件,只要其内容不变可以让多个线程同时读,不必做排他的锁定,
排他的锁定只有在写的时候需要,以保证别的线程不会看到数据不完整的文件。ReadWriteLock可满足这种需要。ReadWriteLock内置两个Lock,一个是读的Lock,一个是写的Lock。
多个线程可同时得到读的Lock,但只有一个线程能得到写的Lock,而且写的Lock被锁定后,任何线程都不能得到Lock。ReadWriteLock提供的方法有:
readLock(): 返回一个读的lock
writeLock(): 返回一个写的lock, 此lock是排他的。
Condition 接口:
有时候线程取得lock后需要在一定条件下才能做某些工作,比如说经典的Producer和Consumer问题,Consumer必须在篮子里有苹果的时候才能吃苹果,否则它必须暂时放弃对篮子的锁定,等到Producer往篮子里放了苹果后再去拿来吃。而Producer必须等到篮子空了才能往里放苹果,否则它也需要暂时解锁等Consumer把苹果吃了才能往篮子里放苹果。
在Java 5.0以前,这种功能是由Object类的wait(), notify()和notifyAll()等方法实现的,在5.0里面,这些功能集中到了Condition这个接口来实现,Condition提供以下方法:
await():使调用此方法的线程放弃锁定,进入睡眠直到被打断或被唤醒。
signal(): 唤醒一个等待的线程
signalAll():唤醒所有等待的线程
比如说说明一下面向对象的思想,类的加载机制等,另外一部分也是比较实际比较棘手的一部分,就是给你一个项目,让你回去做,
而这些项目的实现大都用到了java多线程技术,比如说张老师所讲的两个7k面试题,交通灯和银行调度系统,都是借助了多线程的技术,
所以个人感觉java多线程这部分的内容还是很重要的,至少对于解决面试来说是重要的。所以这篇学习日记记录的是jdk1.5以后出现的
关于java多线程的新技术。
一、多线程技术
线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。
当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。
在线程池的编程模式下,任务是提交给整个线程池,而不是直接交给某个线程,线程池在拿到任务后,它就在内部找有无空闲的线程,
再把任务交给内部某个空闲的线程,这就是封装。记住,任务是提交给整个线程池,一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。
线程池的分类:
固定尺寸的线程池、可变尺寸连接池
jdk中对于线程池提供的api:
Executor接口:是用来执行Runnable任务的
方法:
execute(Runnable command):执行Ruannable类型的任务
ExecutorService 接口 ExecutorService继承了Executor的方法,并提供了执行Callable任务和中止任务执行的服务
方法:
void shutdown() --在完成已提交的任务后关闭服务,不再接受新任务
Future<T> submit(Runnable task) --提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。
Future<T> submit(Callable<T> task) --提交一个返回值的任务用于执行,返回一个表示任务的未决结果的 Future。
实现这个接口的类有:
ThreadPoolExecutor --它使用可能的几个池线程之一执行每个提交的任务,通常使用 Executors 工厂方法配置。
ScheduledExecutorService 接口,可安排在给定的延迟后运行或定期执行的命令(用作定时器)
方法:
ScheduledFuture<?> schedule(Runnable command,long delay,TimeUnit unit) --创建并执行在给定延迟后启用的一次性操作。
ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay, long period,TimeUnit unit)
--解读参数:
command - 要执行的任务
initialDelay - 首次执行的延迟时间
period - 连续执行之间的周期
unit - initialDelay 和 period 参数的时间单位
实现类
ScheduledThreadPoolExecutor --继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。
callable(Runnable task): 将Runnable的任务转化成Callable的任务
newSingleThreadExecutor: 产生一个ExecutorService对象,这个对象只有一个线程可用来执行任务,若任务多于一个,任务将按先后顺序执行。
newCachedThreadPool(): 产生一个ExecutorService对象,这个对象带有一个线程池,线程池的大小会根据需要调整,线程执行完任务后返回线程池,供执行下一次任务使用。
newFixedThreadPool(int poolSize):产生一个ExecutorService对象,这个对象带有一个大小为poolSize的线程池,若任务数量大于poolSize,任务会被放在一个queue里顺序执行。
newSingleThreadScheduledExecutor:产生一个ScheduledExecutorService对象,这个对象的线程池大小为1,若任务多于一个,任务将按先后顺序执行。
newScheduledThreadPool(int poolSize): 产生一个ScheduledExecutorService对象,这个对象的线程池大小为poolSize,若任务数量大于poolSize,任务会在一个queue里等待执行
代码实现:
package com.itheima.newthread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ThreadPoolTest {
public static void main(String[] args) {
//用3个大小的固定线程池去执行10个内部循环10次就结束的任务,线程池内只有3个线程在执行,
//固定线程池下的其他任务一直再等待
//ExecutorService service = Executors.newFixedThreadPool(3);
//有多少任务就分配多少个线程
ExecutorService service = Executors.newCachedThreadPool();
for(int i=1;i<=10;i++){
final int sequence = i;
//仔细品味runnable对象放到循环里面和外面的区别,为了让每个对象有自己独立的编号
service.execute(new Runnable(){
public void run() {
try{Thread.sleep(200);}catch(Exception e){}
for(int j=1;j<=5;j++){
System.out.println(Thread.currentThread().getName() + " is serving "
+ sequence + " task:" + "loop of " + j);
}
}
});
}
/*
用下面这句代码来说明上面的代码是在提交任务,并且所有的任务都已经提交了,但任务是什么时候执行的,则是由线程池调度的!
*/
System.out.println("all task have committed!");
//注意与service.shutdownNow()的区别。
service.shutdown();
/* 测试线程池的定时器*/
//定义一个定时器的线程池,线程池的大小是1,即只有池内只有一个定时器
ScheduledExecutorService scheduledService = Executors.newScheduledThreadPool(1);
//给定时器线程池分配的任务
scheduledService.scheduleAtFixedRate(
new Runnable(){
public void run() {
System.out.println("bomb!!!");
}},
5,
1,
TimeUnit.SECONDS);
}
}
二、带有返回结果的线程
jdk中的api的支持:
Callable 接口 -- Callable 接口类似于 Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。
但是 Runnable 不会返回结果,并且无法抛出经过检查的异常。
方法:
V call() 计算结果,如果无法计算结果,则抛出一个异常 。相似于run()方法
Future 接口 -- 表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。
计算完成后只能使用 get 方法来获取结果,如有必要,计算完成前可以阻塞此方法。取消则由 cancel 方法来执行。
方法:
V get(long timeout, TimeUnit unit) --如有必要,最多等待为使计算完成所给定的时间之后,获取其结果(如果结果可用)。
FutureTask 类 :是 Future 的一个实现,Future 可实现 Runnable,所以可通过 Executor 来执行。
代码实现:
package com.itheima.newthread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class ReturnThread {
public static void main(String[] args) throws Exception {
ExecutorService th = Executors.newSingleThreadExecutor();
Future<String> future = th.submit(new Callable<String>() {
@Override
public String call() throws Exception {
// 可以查询数据库中的内容返回
return "data from ds";
}
});
//获取线程得返回值
String str = future.get();
//打印结果
System.out.println("thread returned data:"+str);
}
}
三、jdk1.5对锁的新实现
在说明这个问题之前,首先我想说一下在5.0之前锁定的锁定的功能是由Synchronized关键字来实现的,这样做存在几个问题:
每次只能对一个对象进行锁定。若需要锁定多个对象,编程就比较麻烦,一不小心就会出现死锁现象。
如果线程因拿不到锁定而进入等待状况,是没有办法将其打断的。
而jdk1.5对锁的实现却很好的避免这个问题:
jdk中对新锁的api支持:
Lock 接口 -- 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。
方法:
lock(): --请求锁定,如果锁已被别的线程锁定,调用此方法的线程被阻断进入等待状态。
unlock():取消锁定,需要注意的是Lock不会自动取消,编程时必须手动解锁。
实现类:ReentrantLock 无特别的方法
ReadWriteLock 接口:
为了提高效率有些共享资源允许同时进行多个读的操作,但只允许一个写的操作,比如一个文件,只要其内容不变可以让多个线程同时读,不必做排他的锁定,
排他的锁定只有在写的时候需要,以保证别的线程不会看到数据不完整的文件。ReadWriteLock可满足这种需要。ReadWriteLock内置两个Lock,一个是读的Lock,一个是写的Lock。
多个线程可同时得到读的Lock,但只有一个线程能得到写的Lock,而且写的Lock被锁定后,任何线程都不能得到Lock。ReadWriteLock提供的方法有:
readLock(): 返回一个读的lock
writeLock(): 返回一个写的lock, 此lock是排他的。
Condition 接口:
有时候线程取得lock后需要在一定条件下才能做某些工作,比如说经典的Producer和Consumer问题,Consumer必须在篮子里有苹果的时候才能吃苹果,否则它必须暂时放弃对篮子的锁定,等到Producer往篮子里放了苹果后再去拿来吃。而Producer必须等到篮子空了才能往里放苹果,否则它也需要暂时解锁等Consumer把苹果吃了才能往篮子里放苹果。
在Java 5.0以前,这种功能是由Object类的wait(), notify()和notifyAll()等方法实现的,在5.0里面,这些功能集中到了Condition这个接口来实现,Condition提供以下方法:
await():使调用此方法的线程放弃锁定,进入睡眠直到被打断或被唤醒。
signal(): 唤醒一个等待的线程
signalAll():唤醒所有等待的线程
代码实现:
package com.itheima.newthread;
import java.util.Random;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockTest {
public static void main(String[] args) {
//将对象声明为final的形式,以便于在匿名内部中调用(即后面的new Thread(){})
final Queue queue = new Queue();
//循环产生6个线程,用于读写数据
for(int i=0;i<3;i++)
{
new Thread(){
public void run(){
while(true){
queue.get();
}
}
}.start();
new Thread(){
public void run(){
while(true){
queue.put(new Random().nextInt(10000));
}
}
}.start();
}
}
}
//将线程的共享数据和相关的操作封装到类中
class Queue{
//共享数据,只能有一个线程能写该数据,但可以有多个线程同时读该数据。
private Object data = null;
//定义读写锁
ReadWriteLock rwl = new ReentrantReadWriteLock();
//读数据的方法
public void get(){
rwl.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " be ready to read data!");
//随机休眠
Thread.sleep((long)(Math.random()*1000));
System.out.println(Thread.currentThread().getName() + "have read data :" + data);
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
//将关闭锁的语句放入到finally语句块中,是因为上面的try语句块中可能会出现异常而导致不能解锁
rwl.readLock().unlock();
}
}
public void put(Object data){
//写数据的方法
rwl.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " be ready to write data!");
Thread.sleep((long)(Math.random()*1000));
this.data = data;
System.out.println(Thread.currentThread().getName() + " have write data: " + data);
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
rwl.writeLock().unlock();
}
}
}
下面在给出一个缓存系统设计的代码
package com.itheima.newthread;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class CacheDemo {
//定义一个map集合用于存储需要缓存的数据
private Map<String, Object> cache = new HashMap<String, Object>();
//定义一个读写锁
private ReadWriteLock rwl = new ReentrantReadWriteLock();
//定义从缓存中获取数据的方法
public Object getData(String key){
rwl.readLock().lock();
Object value = null;
try{
value = cache.get(key);
if(value == null){
//先解读锁,在开写锁,这样即使是多个线程执行到这时,
//也只是一个线程会获得写锁
rwl.readLock().unlock();
rwl.writeLock().lock();
try{
if(value==null){
value = "aaaa";//实际失去queryDB();
}
}finally{
rwl.writeLock().unlock();
}
rwl.readLock().lock();
}
}finally{
rwl.readLock().unlock();
}
return value;
}
}
新的锁的总结:
Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象。
两个线程执行的代码片段要实现同步互斥的效果,它们必须用同一个Lock对象。
读写锁:分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,这是由jvm自己控制的,你只要上好相应的锁即可。如果你的代码只读数据,
可以很多人同时读,但不能同时写,那就上读锁;如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁。总之,读的时候上读锁,写的时候上写锁!
在等待 Condition 时,允许发生“虚假唤醒”,这通常作为对基础平台语义的让步。对于大多数应用程序,这带来的实际影响很小,
因为 Condition 应该总是在一个循环中被等待,并测试正被等待的状态声明。某个实现可以随意移除可能的虚假唤醒,
但建议应用程序程序员总是假定这些虚假唤醒可能发生,因此总是在一个循环中等待。
一个锁内部可以有多个Condition,即有多路等待和通知,可以参看jdk1.5提供的Lock与Condition实现的可阻塞队列的应用案例,
从中除了要体味算法,还要体味面向对象的封装。在传统的线程机制中一个监视器对象上只能有一路等待和通知,要想实现多路等待和通知,
必须嵌套使用多个同步监视器对象。(如果只用一个Condition,两个放的都在等,一旦一个放的进去了,那么它通知可能会导致另一个放接着往下走。)
四、接下来是关于java多线程的几个工具类
Semaphore 类 --可以维护当前访问自身的线程个数,并提供了同步机制。使用Semaphore可以控制同时访问资源的线程个数,
构造方法:
Semaphore(int permits) --创建具有给定的许可数和非公平的公平设置的 Semaphore
方法:
void acquire() --从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断
void release() --释放一个许可,将其返回给信号量。
简单的代码实现(主要演示其使用):
package com.itheima.newthread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class SemaphoreTest {
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
final Semaphore sp = new Semaphore(3);
for(int i=0;i<10;i++){
Runnable runnable = new Runnable(){
public void run(){
try {
//记录有一个线程来到,阻塞
sp.acquire();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getName() +
"进入,当前已有" + (3-sp.availablePermits()) + "个并发");
try {
Thread.sleep((long)(Math.random()*10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getName() +
"即将离开");
sp.release();
//下面代码有时候执行不准确,因为其没有和上面的代码合成原子单元
System.out.println("线程" + Thread.currentThread().getName() +
"已离开,当前已有" + (3-sp.availablePermits()) + "个并发");
}
};
service.execute(runnable);
}
}
}
CountDownLatch 类: --CountDownLatch是个计数器,它有一个初始数,等待这个计数器的线程必须等到计数器倒数到零时才可继续
构造方法:
CountDownLatch(int count) --构造一个用给定计数初始化的 CountDownLatch
方法:
void await():使调用此方法的线程阻断进入等待
void countDown(): 倒计数,将计数值减1
long getCount(): 得到当前的计数值
简单代码实现:
package com.itheima.newthread;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CyclicBarrierTest {
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
final CyclicBarrier cb = new CyclicBarrier(3);
for(int i=0;i<3;i++){
Runnable runnable = new Runnable(){
public void run(){
try {
Thread.sleep((long)(Math.random()*10000));
System.out.println("线程" + Thread.currentThread().getName() +
"即将到达集合地点1,当前已有" + (cb.getNumberWaiting()+1) + "个已经到达," + (cb.getNumberWaiting()==2?"都到齐了,继续走啊":"正在等候"));
cb.await();
Thread.sleep((long)(Math.random()*10000));
System.out.println("线程" + Thread.currentThread().getName() +
"即将到达集合地点2,当前已有" + (cb.getNumberWaiting()+1) + "个已经到达," + (cb.getNumberWaiting()==2?"都到齐了,继续走啊":"正在等候"));
cb.await();
Thread.sleep((long)(Math.random()*10000));
System.out.println("线程" + Thread.currentThread().getName() +
"即将到达集合地点3,当前已有" + (cb.getNumberWaiting() + 1) + "个已经到达," + (cb.getNumberWaiting()==2?"都到齐了,继续走啊":"正在等候"));
cb.await();
} catch (Exception e) {
e.printStackTrace();
}
}
};
service.execute(runnable);
}
service.shutdown();
}
}
Exchanger 类:让两个线程可以互换信息。用于实现两个人之间的数据交换,每个人在完成一定的事务后想与对方交换数据,第一个先拿出数据的人将一直等待第二个人拿着数据到来时,才能彼此交换数据。
构造方法:
Exchanger() --创建一个新的 Exchanger
方法:V exchange(V x) 等待另一个线程到达此交换点(除非当前线程被中断),然后将给定的对象传送给该线程,并接收该线程的对象。
代码实现:
package com.itheima.newthread;
import java.util.concurrent.Exchanger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExchangerTest {
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
final Exchanger exchanger = new Exchanger();
service.execute(new Runnable(){
public void run() {
try {
String data1 = "zxx";
System.out.println("线程" + Thread.currentThread().getName() +
"正在把数据" + data1 +"换出去");
Thread.sleep((long)(Math.random()*10000));
String data2 = (String)exchanger.exchange(data1);
System.out.println("线程" + Thread.currentThread().getName() +
"换回的数据为" + data2);
}catch(Exception e){
}
}
});
service.execute(new Runnable(){
public void run() {
try {
String data1 = "lhm";
System.out.println("线程" + Thread.currentThread().getName() +
"正在把数据" + data1 +"换出去");
Thread.sleep((long)(Math.random()*10000));
String data2 = (String)exchanger.exchange(data1);
System.out.println("线程" + Thread.currentThread().getName() +
"换回的数据为" + data2);
}catch(Exception e){
}
}
});
}
}
阻塞队列:
阻塞队列的概念是,一个指定长度的队列,如果队列满了,添加新元素的操作会被阻塞等待,
直到有空位为止。同样,当队列为空时候,请求队列元素的操作同样会阻塞等待,直到有可用元素为止。
BlockingQueue 接口,阻塞队列的顶层接口
方法:
void put(E e) --将指定元素插入此队列中,将等待可用的空间 阻塞式的
E take() -- 获取并移除此队列的头部,在元素变得可用之前一直等待 ,阻塞式的
实现类有:
ArrayBlockingQueue, LinkedBlockingDeque, LinkedBlockingQueue, PriorityBlockingQueue, SynchronousQueue
代码实现:
package com.itheima.newthread;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class BlockingQueueTest {
public static void main(String[] args) {
final BlockingQueue queue = new ArrayBlockingQueue(3);
for(int i=0;i<2;i++){
new Thread(){
public void run(){
while(true){
try {
Thread.sleep((long)(Math.random()*1000));
System.out.println(Thread.currentThread().getName() + "准备放数据!");
queue.put(1);
System.out.println(Thread.currentThread().getName() + "已经放了数据," +
"队列目前有" + queue.size() + "个数据");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
new Thread(){
public void run(){
while(true){
try {
//将此处的睡眠时间分别改为100和1000,观察运行结果
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "准备取数据!");
queue.take();
System.out.println(Thread.currentThread().getName() + "已经取走数据," +
"队列目前有" + queue.size() + "个数据");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
}
同步集合:
传统集合类在并发访问时的问题说明,见附件
传统方式下用Collections工具类提供的synchronizedCollection方法来获得同步集合,分析该方法的实现源码。
传统方式下的Collection在迭代集合时,不允许对集合进行修改。
用空中网面试的同步级线程题进行演示
根据AbstractList的checkForComodification方法的源码,分析产生ConcurrentModificationException异常的原因。
Java5中提供了如下一些同步集合类:
通过看java.util.concurrent包下的介绍可以知道有哪些并发集合
ConcurrentHashMap
CopyOnWriteArrayList
CopyOnWriteArraySet
ps:终于完了,java多线程的新技术和我的最后一篇学习日记,说句心里话,通过写这10篇的博客,
我已经深感到程序员的不容易与辛苦。有人说程序员工资高,但那却是我们艰苦拼搏的结果,我们受得起!!
继续奋斗吧!!