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

Wesley13
• 阅读 446

七.Callable和Future接口

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

但是Callable是新增的接口 并没有继承Runnable接口,那么肯定不能作为Runnable target来直接作为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(int nThreads)

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

CODE:

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

2. ExecutorService newCachedThreadPool() 

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

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(int corePoolSize)

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

CODE:

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

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

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

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

2.Futuresubmit(Runnable target,T result):因为Runnable中run是没有返回值,这里显示指定该Runnable对象线程执行完成返回一个值,这个值就是result。

3.Futuresubmit(Callable target):Callable中的call是有返回值的

ScheduledExecutorService提供以下四个方法:

1.ScheduledFuture schedule(Callable(V) c,long delay,TimeUnit unit):指定c任务将在delay延迟后执行。

2.ScheduledFuture<?> shedule(Runnable r,long delay,TimeUnit unit):指定r任务将在delay延迟后执行。

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

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

当用完一个线程池后,应该调用该线程池的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)。通过使用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.T get():返回此线程局部变量中当前线程副本中的值。

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

         3.void set(T value):设置此线程局部变量中当前线程副本中的值。

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集合中的ArrayList,LinkedList,HashSet,TreeSet,HashMap都是线程不安全的(线程不安全就是当多个线程想这些集合中放入一个元素时,可能会破坏这些集合数据的完整性)

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

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

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

HashMap hm=Collections.synchronizedMap(new Map());

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

3.线程安全的集合类

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

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

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

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

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
2年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
2个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这