JAVA多线程并发库高级应用 (二)

来源:互联网 发布:西安广电网络费用 编辑:程序博客网 时间:2024/06/07 17:44

05. 线程范围内共享变量的概念与作用

线程范围内共享数据图解:

代码演示:

class ThreadScopeShareData

{

       三个模块共享数据,主线程模块和AB模块

       privatestatic int data = 0;      准备共享的数据

       存放各个线程对应的数据

       private Map<Thread, Integer>threadData = new HashMap<Thread, Integer>();

       publicstatic void main(String[] args)

       {     创建两个线程

for (int i=0;i<2; i++)

{

       new Thread(

new Runnable()

{

       public void run()

       {现在当前线程中修改一下数据,给出修改信息

              data = new Random().nextInt();

              SOP(Thread.currentThread().getName()+将数据改为+data);

              将线程信息和对应数据存储起来

              threadData.put(Thread.currentThread(),data);

              使用两个不同的模块操作这个数据,看结果

              new A().get();

              new B().get();

}

}

).start();

}

}

       staticclass A

       {

       public void get()

       {

              data =threadData.get(Thread.currentThread());

       SOP(A+Thread.currentThread().getName()+拿到的数据+data);

}

}

       staticclass B

       {

       public void get()

       {

              data =threadData.get(Thread.currentThread());

       SOP(B+Thread.currentThread().getName()+拿到的数据+data);

}

}

}

结果并没与实现线程间的数据同步,两个线程使用的是同一个线程的数据。要解决这个问题,可以将每个线程用到的数据与对应的线程号存放到一个map集合中,使用数据时从这个集合中根据线程号获取对应线程的数据。代码实现:上面红色部分

程序中存在的问题:获取的数据与设置的数据不同步

               Thread-1共享数据设置为:-997057737

              Thread-1--A模块数据:-997057737

              Thread-0共享数据设置为:11858818

              Thread-0--A模块数据:11858818

              Thread-0--B模块数据:-997057737

              Thread-1--B模块数据:-997057737

最好将Runnable中设置数据的方法也写在对应的模块中,与获取数据模块互斥,以保证数据同步

 

06.ThreadLocal类及应用技巧

       多个模块在同一个线程中运行时要共享同一份数据,实现线程范围内的数据共享可以用上一节中所用的方法。

       JDK1.5提供了ThreadLocal类来方便实现线程范围内的数据共享,它的作用就相当于上一节中的Map。

       每个线程调用全局ThreadLocal对象的set方法,就相当于往其内部的map集合中增加一条记录,key就是各自的线程,value就是各自的set方法传进去的值。

       在线程结束时可以调用ThreadLocal.clear()方法用来更快释放内存,也可以不调用,因为线程结束后也可以自动释放相关的ThreadLocal变量。

       一个ThreadLocal对象只能记录一个线程内部的一个共享变量,需要记录多个共享数据,可以创建多个ThreadLocal对象,或者将这些数据进行封装,将封装后的数据对象存入ThreadLocal对象中。

       将数据对象封装成单例,同时提供线程范围内的共享数据的设置和获取方法,提供已经封装好了的线程范围内的对象实例,使用时只需获取实例对象即可实现数据的线程范围内的共享,因为该对象已经是当前线程范围内的对象了。下边给出张老师的优雅代码:

package cn.itheima;

import java.util.Random;

publicclassThreadLocalShareDataDemo

{   /**06.ThreadLocal类及应用技巧

     * 将线程范围内共享数据进行封装,封装到一个单独的数据类中,提供设置获取方法

     * 将该类单例化,提供获取实例对象的方法,获取到的实例对象是已经封装好的当前线程范围内的对象

     */

    publicstaticvoidmain(String[] args)

    {

        for (inti=0; i<2; i++)

        {

            newThread(

                    newRunnable()

                    {                      

                        publicvoidrun()

                        {

                            intdata =new Random().nextInt(889);

    System.out.println(Thread.currentThread().getName()+"产生数据:"+data);

                            MyDatamyData = MyData.getInstance();

                            myData.setAge(data);

                            myData.setName("Name:"+data);

                            newA().get();

                            newB().get();

                        }

                    }).start();

        }

    }

   

    staticclass A

    {   //可以直接使用获取到的线程范围内的对象实例调用相应方法

        Stringname = MyData.getInstance().getName();

        intage =MyData.getInstance().getAge();

        publicvoidget()

        {

            System.out.println(Thread.currentThread().getName()+"--AA name:"+name+"...age:"+age);

        }

    }  

   

    staticclass B

    {

        //可以直接使用获取到的线程范围内的对象实例调用相应方法

        Stringname = MyData.getInstance().getName();

        intage =MyData.getInstance().getAge();

        publicvoidget()

        {

            System.out.println(Thread.currentThread().getName()+"--BB name:"+name+"...age:"+age);

        }

    }  

   

    staticclassMyData

    {

        privateStringname;

        privateintage;

        publicString getName()

        {

            returnname;

        }

        publicvoidsetName(String name)

        {

            this.name =name;

        }

        publicintgetAge()

        {

            returnage;

        }

        publicvoidsetAge(int age)

        {

            this.age =age;

        }

        //单例

        privateMyData() {};

        //提供获取实例方法

        publicstaticMyData getInstance()

        {

            //从当前线程范围内数据集中获取实例对象

            MyDatainstance = threadLocal.get();

            if(instance==null)

            {

                instance= new MyData();

                threadLocal.set(instance);

            }

            returninstance;

        }

        //将实例对象存入当前线程范围内数据集中

        staticThreadLocal<MyData>threadLocal =newThreadLocal<MyData>();

    }

}

 

07. 多个线程之间共享数据的方式探讨

       例子:卖票:多个窗口同时卖这100张票,票就需要多个线程共享

a、如果每个线程执行的代码相同,可以使用同一个Runnable对象,这个对象中有共享数据。

卖票就可以这样做,每个窗口都在做卖票任务,卖的票都是同一个数据。

b、如果每个线程执行的代码不同,就需要使用不同的Runnable对象,有两种方式实现

Runnable对象之间的数据共享:

       a)将共享数据单独封装到一个对象中,同时在对象中提供操作这些共享数据的方法,可以方便实现对共享数据各项操作的互斥和通信。

       b)将各个Runnable对象作为某个类的内部类,共享数据作为外部类的成员变量,对共享数据的操作方法也在外部类中提供,以便实现互斥和通信,内部类的Runnable对象调用外部类中操作共享数据的方法即可。

       注意:要同步互斥的几段代码最好分别放在几个独立的方法中,这些方法再放在同一个类中,这样比较容易实现它们之间的同步互斥和通信。

 

08.java5原子性操作类的应用

       Java5的线程并发库

       java.util.concurrent在并发编程中很常用的实用工具类。

                     |----locks为锁和等待条件提供一个框架的接口和类,

它不同于内置同步和监视器

                     |----atomic类的小工具包,支持在单个变量上解除锁的线程安全编程。

                            可以对基本类型、数组中的基本类型、类中的基本类型等进行操作

                            |----AtomicInteger

构造方法摘要

AtomicInteger()           创建具有初始值 0 的新 AtomicInteger。

AtomicInteger(int initialValue)           创建具有给定初始值的新 AtomicInteger。

方法摘要

 int

addAndGet(int delta)           以原子方式将给定值与当前值相加。

 boolean

compareAndSet(int expect, int update)           如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。

 int

decrementAndGet()           以原子方式将当前值减 1。

 double

doubleValue()           以 double 形式返回指定的数值。

 float

floatValue()           以 float 形式返回指定的数值。

 int

get()           获取当前值。

 int

getAndAdd(int delta)           以原子方式将给定值与当前值相加。

 int

getAndDecrement()           以原子方式将当前值减 1。

 int

getAndIncrement()           以原子方式将当前值加 1。

 int

getAndSet(int newValue)           以原子方式设置为给定值,并返回旧值。

 int

incrementAndGet()           以原子方式将当前值加 1。

 int

intValue()           以 int 形式返回指定的数值。

 void

lazySet(int newValue)           最后设置为给定值。

 long

longValue()           以 long 形式返回指定的数值。

 void

set(int newValue)           设置为给定值。

 String

toString()           返回当前值的字符串表示形式。

 boolean

weakCompareAndSet(int expect, int update)           如果当前值 == 预期值,则以原子方式将该设置为给定的更新值。

                            |----AtomicIntegerArray

构造方法摘要

AtomicIntegerArray(int length)           创建给定长度的新 AtomicIntegerArray。

AtomicIntegerArray(int[] array)           创建与给定数组具有相同长度的新 AtomicIntegerArray,并从给定数组复制其所有元素。

方法摘要

 int

addAndGet(int i, int delta)           以原子方式将给定值与索引i 的元素相加。

 boolean

compareAndSet(int i, int expect, int update)           如果当前值== 预期值,则以原子方式将位置 i 的元素设置为给定的更新值。

 int

decrementAndGet(int i)           以原子方式将索引i 的元素减 1。

 int

get(int i)           获取位置i 的当前值。

 int

getAndAdd(int i, int delta)           以原子方式将给定值与索引i 的元素相加。

 int

getAndDecrement(int i)           以原子方式将索引i 的元素减 1。

 int

getAndIncrement(int i)           以原子方式将索引i 的元素加 1。

 int

getAndSet(int i, int newValue)           将位置i 的元素以原子方式设置为给定值,并返回旧值。

 int

incrementAndGet(int i)           以原子方式将索引i 的元素加 1。

 void

lazySet(int i, int newValue)           最后将位置i 的元素设置为给定值。

 int

length()           返回该数组的长度。

 void

set(int i, int newValue)           将位置i 的元素设置为给定值。

 String

toString()           返回数组当前值的字符串表示形式。

 boolean

weakCompareAndSet(int i, int expect, int update)           如果当前值== 预期值,则以原子方式将位置 i 的元素设置为给定的更新值。

 

09.java5线程并发库的应用

       如果没有线程池,需要在run方法中不停判断,还有没有任务需要执行

       线程池的通俗比喻:接待客户,为每个客户都安排一个工作人员,接待完成后该工作人员就废掉。服务器每收到一个客户请求就为其分配一个线程提供服务,服务结束后销毁线程,不断创建、销毁线程,影响性能。

       线程池:先创建多个线程放在线程池中,当有任务需要执行时,从线程池中找一个空闲线程执行任务,任务完成后,并不销毁线程,而是返回线程池,等待新的任务安排。

       线程池编程中,任务是提交给整个线程池的,并不是提交给某个具体的线程,而是由线程池从中挑选一个空闲线程来运行任务。一个线程同时只能执行一个任务,可以同时向一个线程池提交多个任务。

线程池创建方法:

a、创建一个拥有固定线程数的线程池

       ExecutorServicethreadPool = Executors.newFixedThreadPool(3);   

       b、创建一个缓存线程池     线程池中的线程数根据任务多少自动增删 动态变化

       ExecutorServicethreadPool = Executors.newCacheThreadPool();

       c、创建一个只有一个线程的线程池  与单线程一样  但好处是保证池子里有一个线程,

当线程意外死亡,会自动产生一个替补线程,始终有一个线程存活

       ExecutorServicethreadPool = Executors.newSingleThreadExector();

往线程池中添加任务

       threadPool.executor(Runnable)

关闭线程池:

       threadPool.shutdown()   线程全部空闲,没有任务就关闭线程池

       threadPool.shutdownNow()  不管任务有没有做完,都关掉

 

用线程池启动定时器:

       a、创建调度线程池,提交任务         延迟指定时间后执行任务

       Executors.newScheduledThreadPool(线程数).schedule(Runnable, 延迟时间,时间单位);

       b、创建调度线程池,提交任务, 延迟指定时间执行任务后,间隔指定时间循环执行

       Executors.newScheduledThreadPool(线程数).schedule(Runnable, 延迟时间,

间隔时间,时间单位);

       所有的 schedule 方法都接受相对延迟和周期作为参数,而不是绝对的时间或日期。将以 Date所表示的绝对时间转换成要求的形式很容易。例如,要安排在某个以后的Date运行,可以使用:schedule(task,date.getTime()- System.currentTimeMillis(), TimeUnit.MILLISECONDS)

 

10.CallableFuture的应用:获取一个线程的运行结果

public interface Callable<V>

返回结果并且可能抛出异常的任务。实现者定义了一个不带任何参数的叫做call 的方法。 Callable 接口类似于 Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是Runnable 不会返回结果,并且无法抛出经过检查的异常。

只有一个方法V call() 计算结果,如果无法计算结果,则抛出一个Exception异常。

使用方法:

       ExecutorServicethreadPool = Executors.newSingleThreadExccutor();

       如果不需要返回结果,就用executor方法 调用submit方法返回一个Future对象

       Future<T> future = threadPool.submit(new Callable<T>(){//接收一个Callable接口的实例对象

                     覆盖Callable接口中的call方法,抛出异常

                     publicTcall() throws Exception

                     {

                            ruturnT

}

});

获取Future接收的结果

future。get();会抛出异常

future.get()没有拿到结果就会一直等待

       Future取得的结果类型和Callable返回的结果类型必须一致,通过泛型实现。Callable要通过ExecutorService的submit方法提交,返回的Future对象可以取消任务。

 

public interface Future<V>

Future 表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。计算完成后只能使用get 方法来获取结果,如有必要,计算完成前可以阻塞此方法。取消则由cancel 方法来执行。还提供了其他方法,以确定任务是正常完成还是被取消了。一旦计算完成,就不能再取消计算。如果为了可取消性而使用Future 但又不提供可用的结果,则可以声明Future<?> 形式类型、并返回 null 作为底层任务的结果。

方法摘要

 boolean

cancel(boolean mayInterruptIfRunning)           试图取消对此任务的执行。

 V

get()           如有必要,等待计算完成,然后获取其结果。

 V

get(long timeout,TimeUnit unit)           如有必要,最多等待为使计算完成所给定的时间之后,获取其结果(如果结果可用)。

 boolean

isCancelled()           如果在任务正常完成前将其取消,则返回 true。

 boolean

isDone()           如果任务已完成,则返回 true。

 

public interface CompletionService<V>

       CompletionService用于提交一组Callable任务,其take方法返回一个已完成的Callable任务对应的Future对象。好比同时种几块麦子等待收割,收割时哪块先熟先收哪块。

将生产新的异步任务与使用已完成任务的结果分离开来的服务。生产者submit 执行的任务。使用者 take 已完成的任务,并按照完成这些任务的顺序处理它们的结果。例如,CompletionService 可以用来管理异步 IO ,执行读操作的任务作为程序或系统的一部分提交,然后,当完成读操作时,会在程序的不同部分执行其他操作,执行操作的顺序可能与所请求的顺序不同。

通常,CompletionService 依赖于一个单独的 Executor 来实际执行任务,在这种情况下,CompletionService 只管理一个内部完成队列。ExecutorCompletionService 类提供了此方法的一个实现。

CompletionService方法摘要

 Future<V>

poll()           获取并移除表示下一个已完成任务的 Future,如果不存在这样的任务,则返回 null。

 Future<V>

poll(long timeout,TimeUnit unit)          获取并移除表示下一个已完成任务的 Future,如果目前不存在这样的任务,则将等待指定的时间(如果有必要)。

 Future<V>

submit(Callable<V> task)           提交要执行的值返回任务,并返回表示挂起的任务结果的 Future。

 Future<V>

submit(Runnable task,V result)           提交要执行的 Runnable 任务,并返回一个表示任务完成的 Future,可以提取或轮询此任务。

 Future<V>

take()           获取并移除表示下一个已完成任务的 Future,如果目前不存在这样的任务,则等待。

ExecutorCompletionService构造方法摘要

ExecutorCompletionService(Executor executor)
          使用为执行基本任务而提供的执行程序创建一个 ExecutorCompletionService,并将 LinkedBlockingQueue 作为完成队列。

 

ExecutorCompletionService(Executor executor,BlockingQueue<Future<V>> completionQueue)
          使用为执行基本任务而提供的执行程序创建一个 ExecutorCompletionService,并将所提供的队列作为其完成队列。

 

示例:

ExecutorService threadPool = Executors.newFixedThreadPool(10);   //创建线程池,传递给coms

       用threadPool执行任务,执行的任务返回结果都是整数

CompletionService<Integer> coms = newExecutorCompletionService<Integer>(threadPool);

       提交10个任务  种麦子

for (int i=0; i<10; i++)

{

       finalint num = i+1;

coms.submit(newCallable<Integer>(){

public Integercall()       覆盖call方法

{匿名内部类使用外部变量要用final修饰

       SOP(任务+num);

       Thread.sleep(newRandom().nextInt(6)*1000);

       return num;

}

});

}

       等待收获       割麦子

for (int i=0; i<10; i++)

{     take获取第一个Future对象,用get获取结果

       SOP(coms.take().get());

}

 


0 0
原创粉丝点击