Java多线程(全)学习笔记(下)

资源下载地址:http://download.csdn.net/detail/cloudyxuq/3763101

七.Callable和Future接口

C#可以把任意方法包装成线程执行体,包括那些有返回值的方法。Java也从jdk1.5开始,加入了Callable接口用来扩展Runnable接口的功能,Callable接口提供一个call()来增强Runnablerun()。因为call()可以有返回值,可以声明抛出异常。

但是Callable是新增的接口并没有继承Runnable接口,那么肯定不能作为Runnabletarget来直接作为Thread构造方法的参数。必须由一个中间的类来包装Callable对象。这个类就是实现了Future接口(继承至Runnable接口)的FutureTask类。

CODE:

import java.util.concurrent.Callable; public class CallableThread implements Callable<Integer> { @Override public Integer call() throws Exception { int i=0; for(;i<6;i++){ System.out.println(Thread.currentThread().getName()+"循环:"+i); } return i; } } ---------------------------------------------------------------------------- public class TestCallable { public static void main(String []args){ try { CallableThread ct=new CallableThread(); FutureTask<Integer> target=new FutureTask<Integer>(ct); for(int i=0;i<5;i++){ System.out.println(Thread.currentThread().getName()+"循环变量:"+i); if(i==2){ new Thread(target,"子线程").start(); //boolean isDone():如果Callable任务已完成,则返回true,否则返回false System.out.println(target.isDone()); Thread.sleep(1); } } //V get():返回Callable任务里call()的返回值,调用该方法会导致阻塞,必须等到子线程结束时才会得到返回值 System.out.println("子返回值是:"+target.get()); System.out.println(target.isDone()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } } ----------- 结果: main循环变量:0 main循环变量:1 main循环变量:2 false 子线程循环:0 子线程循环:1 子线程循环:2 子线程循环:3 子线程循环:4 main循环变量:3 子线程循环:5 main循环变量:4 子返回值是:6 true

八.线程池

Jdk1.5后java也内置支持线程池,为了提高性能。(当程序中需要创建大量生存期很短暂的线程时,更应该考虑使用线程池)

Jdk1.5提供一个Executors工厂类来产生线程池。工厂类中包含5个静态工厂方法来创建线程池:

.返回类型是ExecutorService的共3个:

1.newFixedThreadPool(intnThreads)

创建一个可重用的,具有固定线程数的线程池

CODE:

public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }

2.ExecutorServicenewCachedThreadPool()

创建一个具有缓存功能的线程池,系统根据需要创建线程,这些线程将会被缓存在线程池中。

CODE:

public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }

3.newSingleThreadExecutor()

创建一个只有单线程的线程池,等于newFixedThreadPool(1)。

CODE:

public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }

.返回类型是ScheduledExecutorService(是ExecutorService的子类)的共2个:

1.newSingleThreadScheduledExecutor()

创建只有一条线程的线程池,它可以在指定延迟后执行线程任务

CODE:

public static ScheduledExecutorService newSingleThreadScheduledExecutor() { return new DelegatedScheduledExecutorService (new ScheduledThreadPoolExecutor(1)); }

2.newScheduledThreadPool(intcorePoolSize)

创建具有指定线程数的线程池,它可以在指定延迟后执行线程任务。其中参数指池中所保存的线程数,即使线程时空闲的也被保存在线程池内。

CODE:

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); }

ExecutorService代表尽快执行线程的线程池(只要线程池中有空闲线程立即执行线程任务),程序只需要传入RunnableCallable对象即可。

ExecutorService提供三个方法来执行线程:

1.Future<?>submit(Runnabletarget):Runnablerun是没有返回值的所以执行完成后返回null,可以调用FutureisDone(),isCanclled()来判断当前target的执行状态。

2.<T>Future<T>submit(Runnabletarget,Tresult):因为Runnablerun是没有返回值,这里显示指定该Runnable对象线程执行完成返回一个值,这个值就是result

3.<T>Future<T>submit(Callable<T>target):Callable中的call是有返回值的

ScheduledExecutorService提供以下四个方法:

1.ScheduledFuture<V>schedule(Callable(V)c,longdelay,TimeUnitunit):指定c任务将在delay延迟后执行。

2.ScheduledFuture<?>shedule(Runnabler,longdelay,TimeUnitunit:指定r任务将在delay延迟后执行。

3.ScheduledFuture<?>scheduleAtFixedRate(Runnabler,longinitialDelay,longperiod,TimeUnitunit):指定r任务将在delay延迟后执行,而且以设定的频率重复执行(在initialDelay后开始执行,然后开始依次在initialDelay+period,initialDelay+period*2...处重复执行)。

4.ScheduledFuture<?>scheduledWithFixedDelay(Runnabler,longnitialDelay,longdelay,TimeUnitunit):创建并执行一个在给定初始延迟后首次启用的定期操作,随后,在每一次执行中止和下一次执行开始之间都存在给定的延迟。如果任务的任意依次执行时异常,就会取消后序执行。否则,只能通过程序来显示取消或中止该任务。

当用完一个线程池后,应该调用该线程池的shutdown()方法,启用了shutdown()方法后线程池不再接受新任务,但会将以前所有已提交的任务执行完成,然后启动线程池的关闭序列。

当线程池所有任务都执行完成后,池中所有线程都会死亡,另外也可以调用线程池的shutdownNow()方法来关闭线程池,该方法试图停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表。

例子:

class TestThread implements Runnable{ public void run() { for(int i=0;i<10;i++){ System.out.println(Thread.currentThread().getName()+":"+i); } } } public class ThreadPoolTest { public static void main(String []args){ //创建一个固定线程数为3的线程池 ExecutorService pool=Executors.newFixedThreadPool(3); //像线程池提交10个线程 for(int i=0;i<10;i++){ pool.submit(new TestThread()); if(i==4){ } } //执行完成后关闭线程 pool.shutdown(); } } 结果: pool-1-thread-2:1 pool-1-thread-2:2 pool-1-thread-2:3 pool-1-thread-1:9 pool-1-thread-3:8 pool-1-thread-3:9 pool-1-thread-2:4 ......

九.附录:线程相关的类

1.ThreadLocal(线程局部变量)

为了避免并发线程的安全问题,可以添加支持泛型的ThreadLocal类(ThreadLocal<T>)。通过使用ThreadLocal可以简化多线程编程时的并发访问,使用这个工具类可以很简洁的写出有没的多线程程序(例如Hibernate的官方提供HibernateUtil类里设置当前线程的局部变量是当前线程副本中session的值):

Hibernate代码:

public class HibernateUtil { /** 日志 */ private static final Log LOG=LogFactory.getLog(HibernateUtil.class); /** 线程本地变量*/ private static final ThreadLocal MAP = new ThreadLocal(); /** 会话工厂 */ private static final SessionFactory SESSION_FACTORY; private HibernateUtil() { } static { try { LOG.debug("HibernateUtil.static - loading config"); SESSION_FACTORY = new Configuration().configure() .buildSessionFactory(); LOG.debug("HibernateUtil.static - end"); } catch (HibernateException e) { throw new RuntimeException("建立会话工厂错误" + e.getMessage(), e); } } /** * 获得一个会话从当前的线程变量,当这个任务完成后,用户必须返回会话关闭方法 */ public static Session currentSession()throws HibernateException{ Session session=(Session)MAP.get(); //如果会话还没有,就打开会话 if(session==null){ session=SESSION_FACTORY.openSession(); //设置此线程局部变量的值是当前线程副本中session的值 MAP.set(session); } return session; } /** * 关闭会话 */ public static void closeSession(){ Session session=(Session)MAP.get(); MAP.set(null); if(session!=null){ session.close(); } } }

根据上面代码来看:

线程局部变量功能很简单,就是为每一个使用该变量的线程都提供一个变量值的副本,使每一个线程都可以独立地改变自己的副本,而不会和其他线程的副本冲突。仿佛就好像每一个线程都可以完全拥有该变量

ThreadLocal类提供的常用方法:

1.Tget():返回此线程局部变量中当前线程副本中的值。

2.voidremove():删除此线程局部变量中当前线程的值。

3.voidset(Tvalue):设置此线程局部变量中当前线程副本中的值。

CODE:

/** * 线程局部变量测试 * @author Cloudy * */ class Accout{ //定义一个ThreadLocal变量,只要调用该类的线程都会保留该变量的一个副本 private ThreadLocal<String> threadLocal=new ThreadLocal<String>(); //初始化threadLocal public Accout(String str) { this.threadLocal.set(str); System.out.println("-------初始化(为ThreadLocal在main中副本)值是:"+this.threadLocal.get()); } public String getThreadLocal() { return threadLocal.get(); } public void setThreadLocal(String threadLocal) { this.threadLocal.set(threadLocal); } } /**定义一个线程*/ class MyThread implements Runnable{ //模拟一个Accout private Accout accout; public MyThread(Accout accout) { super(); this.accout = accout; } //线程执行体 public void run() { for(int i=0;i<3;i++){ if(i==2){ //设置此线程局部变量的值为当前线程名字 accout.setThreadLocal(Thread.currentThread().getName()); } System.out.println(i+"------"+Thread.currentThread().getName()+"线程局部变量副本值:"+accout.getThreadLocal()); } } } public class ThreadLocalVarTest { public static void main(String []args){ ExecutorService pool=Executors.newFixedThreadPool(3); //启动三条线程,公用同一个Accout Accout ac=new Accout("ThreadLocal本尊"); /* * 虽然Accout类的只有一个变量所以ThreadLocal类型的变量就导致了同一个Accout对象, * 当i=2后,将会看到3条线程访问同一个ac 而看到不同的ThreadLocal值。 */ pool.submit(new MyThread(ac)); pool.submit(new MyThread(ac)); pool.submit(new MyThread(ac)); pool.shutdown(); } }

结果:

-------初始化(为ThreadLocal在main中副本)值是:ThreadLocal本尊

0------pool-1-thread-1线程局部变量副本值:null

1------pool-1-thread-1线程局部变量副本值:null

2------pool-1-thread-1线程局部变量副本值:pool-1-thread-1

0------pool-1-thread-2线程局部变量副本值:null

1------pool-1-thread-2线程局部变量副本值:null

2------pool-1-thread-2线程局部变量副本值:pool-1-thread-2

0------pool-1-thread-3线程局部变量副本值:null

1------pool-1-thread-3线程局部变量副本值:null

2------pool-1-thread-3线程局部变量副本值:pool-1-thread-3

总结:

ThreadLocal并不能代替同步机制,两者面向的问题领域不同,同步机制是为了多个线程同步对相同资源的并发访问,是多个线程之间进行通信的有效方式。而ThreadLocal是隔离多个线程的数据共享,根本就没有在多个线程之间共享资源,也就更不用对多个线程同步了。

所以:如果进行多个线程之间共享资源,达到线程之间通信功能,就同步。

如果仅仅需要隔离多个线程之间的共享冲突,就是用ThreadLocal

1.包装线程不安全的集合成为线程安全集合

Java集合中的ArrayListLinkedListHashSetTreeSetHashMap都是线程不安全的(线程不安全就是当多个线程想这些集合中放入一个元素时,可能会破坏这些集合数据的完整性)

如何将上面的集合类包装成线程安全的呢?



例子:使用CollectionssynchronizedMap方法将一个普通HashMap包装成线程安全的类

HashMaphm=Collections.synchronizedMap(newMap());

如果需要包装某个集合成线程安全集合,则应该在创建之后立即包装如上。

3.线程安全的集合类

位于java.util.concurrent包下的ConcurrentHashMap集合和ConcurrentLinkedQueue集合都支持并发访问,分别对应支持并发访问的HashMapQueue,它们都可以支持多线程并发写访,这些写入线程的所有操作都是线程安全的,但读取的操作不必锁定。(为什么?因为算法我也看不懂)

当多个线程共享访问一个公共集合时,使用ConcurrentLinkedQueue是一个恰当的选择,因为ConcurrentLinkedQueue不允许使用null元素。ConcurrentLinkedQueue实现了多线程的高效访问,多线程访问ConcurrentLinkedQueue集合时不需要等待。

ConcurrentHashMap支持16条多线程并发写入。

用迭代器访问这两种可支持多线程的集合而言,该迭代器可能不反应出创建迭代器之后所做的修改,但不会抛出异常,而如果Collection作为对象,迭代器创建之后修改,则会抛出ConcurrentModificationException


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值