多线程并发


多线程并发

传统线程机制的回顾
创建线程的方式
1 在Thread子类覆盖的run方法中编写运行代码
    涉及一个以往知识点,能否在run方法生命上抛出
    InterruptedException异常,
    以便省略run方法内部对Thread.sleep语句的try catch处理?
2 在传递给Thread对象的Runnable对象的run方法中编写代码
总结:查看Thread类的run方法的源代码,可以看到其实这两种方式
    都是在调用Thread对象的run方法,
    如果Thread类的run方法没有被覆盖,
    并且为该Thread对象设置了一个Runnable对象,
    该run方法会调用Runnable对象的run方法
问题,如果在Thread子类覆盖的run方法中编写了运行代码,
也为Thread子类对象传递了一个Runnable对象,
线程运行时执行代码是子类的run方法的代码?
还是Runnable对象的run方法的代码?
涉及到知识点:匿名内部类对象的构造方法如何调用父类的非默认构造方法
多线程机制会提高程序的运行效率吗?为什么会有多线程下载?
其实多线程只会降低计算机运行效率
多线程下载其实不是计算机变快,而是去抢了服务器网络带宽
一个人下载,服务器给你提供20k速度
100个人下载,服务器就分配给你2000k的速度了
这才是加速


第一种方式
new一个对象时候,可以在后面直接用{}
里面重写方法
表示子类,重写父类方法
Thread thread = new Thread(){
    @Override
    public void run() {
        super.run();
    }    
};
thread.start();


如果需要长期运行线程
用个死循环
打印当前线程名字,可以直接用this代表当前线程
但最好用Thread.currentThread()
让它没半秒打印一次,加入sleep
Thread thread = new Thread(){
    @Override
    public void run() {
        while(true){
            try{
                Thread.sleep(500);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName());
            System.out.println("aaa" + this.getName());
        }
    }    
};
thread.start();


第二种方式
传递Runnable对象给构造方法
Thread的init方法会调用该Runnable
Runnable里重写run方法
这时候this就不是当前线程对象了
Thread thread2 = new Thread(new Runnable(){
    @Override
    public void run() {
        while(true){
            try{
                Thread.sleep(500);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName());
            //System.out.println("aaa" + this.getName());
        }
    }
});
thread2.start();


问题:
到底运行runnable的run方法还是thread的run方法
new Thread(new Runnable() {
    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("thread3 runnable "+ Thread.currentThread().getName());
        }
    }
}) {
    public void run() {
        while (true) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("thread3 thread "+ Thread.currentThread().getName());
        }
    }
}.start();
首先new thread.start会找当前new Thread的run方法
找到了的话,就运行该run方法
如果没有找到, 就去找父类Thread的run方法
该run方法中运行Runnable target.start()
即找到Runnable的run方法
就会去找new Runnable的run方法
所以这里只会打印
thread3 thread Thread-'n'








定时器的应用
Timer类
TimerTask类

// 计时器调度某个事件TimerTask,实现Runnable接口
// 定时器 10秒后bombing
// 第一次执行在10秒后, 然后每3秒执行一次,后面这个3秒可以不设置
new Timer().schedule(new TimerTask() {

    @Override
    public void run() {
        System.out.println("bombing");

    }

}, 10000, 3000);


//连环炸弹
new Timer().schedule(new TimerTask() {

    @Override
    public void run() {
        System.out.println("bombing");
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("bombing");
            }
        }, 2000);
    }

}, 2000);

// 计时
while (true) {
    System.out.println(new Date().getSeconds());
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

//更好的写法是创建内部类MyTimerTask
class MyTimerTask extends TimerTask{
    @Override
    public void run() {
        System.out.println("bombing");
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("bombing");
            }
        }, 2000);
    }
}
new Timer().schedule(new MyTimerTask() , 2000);


安卓的Handler就是用这种思想

每天凌晨3点收邮件
该方法可以穿入具体的Date
schedule(TimerTask, Date, long)
开源工具 quatz shcedule
OpenSymphony开源组织在Job scheduling领域又一个开源项目,
它可以与J2EE与J2SE应用程序相结合也可以单独使用。
Quartz可以用来创建简单或为运行十个,百个,
甚至是好几万个Jobs这样复杂的程序。
Jobs可以做成标准的Java组件或 EJBs。
Quartz的最新版本为Quartz 2.2.1。




============================================================================

线程互斥与同步通信
多个线程操作同一份数据造成线程不安全问题
使用Synchronized代码块及其原理
使用synchronized方法
分析静态方法所使用的同步监视器对象是什么
wait与notify实现线程通信
    


线程互斥
package cn.itcast.heima2;
public class TraditionalThreadSynchronized {

    /**
     * @param args
     */
    public static void main(String[] args) {
        new TraditionalThreadSynchronized().init();
    }
    
    private void init(){
        //匿名内部类不能访问局部变量,只有加final才可以访问
        //两个线程访问同一个Outputer对象
        final Outputer outputer = new Outputer();
        new Thread(new Runnable(){
            @Override
            public void run() {
                while(true){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    outputer.output("zhangxiaoxiang");
                }
                
            }
        }).start();
        
        new Thread(new Runnable(){
            @Override
            public void run() {
                while(true){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    outputer.output3("lihuoming");
                }
                
            }
        }).start();
        
    }

    //在静态方法main中部可以new一个内部类
    //因为内部类的特点就是可以访问外部类的成员变量
    //对象生成后才会有成员变量分配到内存地址
    //所以‘内部类可以访问外部类成员变量’意味着,该外部类一定已经被实例化
    //而静态方法main中,可以不用实例化外部类对象,直接访问其静态成员变量和方法
    //这就矛盾了
    //所以main方法里面需要new出一个外部类对象
    //通过非静态的init方法,调用内部类,这样就没有错误了
    static class Outputer{
        
        public void output(String name){
            int len = name.length();
            //同步代码块,当运行到这段代码,上锁
            //必须锁住同一个对象,而不是某个局部变量
            //如果Outputer类有成员变量,这里也可以锁住该成员变量,也是同一个对象
            //也可以直接用synchronized (this)
            synchronized (Outputer.class)
            {
                for(int i=0;i<len;i++){
                    System.out.print(name.charAt(i));
                }
                System.out.println();
            }
        }
        //整段代码上锁
        //output,output2也有互斥作用
        public synchronized void output2(String name){
            int len = name.length();
            for(int i=0;i<len;i++){
                    System.out.print(name.charAt(i));
            }
            System.out.println();
        }
        
        //静态方法同步的是静态区字节码对象Outputer.class
        //如果使用synchronized (this) ,那么output和output3无法互斥
        //当使用了synchronized (Outputer.class) , 锁住字节码,他们才可以互斥
        public static synchronized void output3(String name){
            int len = name.length();
            for(int i=0;i<len;i++){
                    System.out.print(name.charAt(i));
            }
            System.out.println();
        }    
    }
}





同步通信
子线程循环10次和主线程循环5次,两者交替运行50次
eclipse中设置对话框,观察结果,定向到文本文件
要用到共同数据,包括同步锁 的若干个方法应该归在同一个类上
这种设计正好体现了高类聚和程序的健壮性

思路类似:
客户端访问服务端servlet
服务端返回生成加密的cookie
期间有个filter拦截,解密cookie,校验信息自动登录
把生成加密cookie,解密cookie写入一个类

package cn.itcast.heima2;

public class TraditionalThreadCommunication {

    /**
     * @param args
     */
    public static void main(String[] args) {

        final Business business = new Business();
        //子线程
        new Thread(new Runnable() {

            @Override
            public void run() {

                for (int i = 1; i <= 50; i++) {
                    business.sub(i);
                }

            }
        }).start();

        //主线程
        for (int i = 1; i <= 50; i++) {
            business.main(i);
        }

    }

}

// 提炼出一个类存放业务逻辑代码
//针对同一个对象,sub和main方法可以互斥
class Business {
    private boolean bShouldSub = true;

    public synchronized void sub(int i) {
        while (!bShouldSub) {
            try {
                //wait需要被唤醒
                this.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        for (int j = 1; j <= 10; j++) {
            System.out.println("sub thread sequence of " + j + ",loop of " + i);
        }
        bShouldSub = false;
        //唤醒当前wait的线程
        this.notify();
    }

    public synchronized void main(int i) {
        while (bShouldSub) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        for (int j = 1; j <= 100; j++) {
            System.out
                    .println("main thread sequence of " + j + ",loop of " + i);
        }
        bShouldSub = true;
        //唤醒当前wait的线程
        this.notify();
    }
}


当控制台不足以写入所有log时,
Ran As - Run configuration - Common标签
选中File,输入文件路径和名称
这样系统日志都会写入文件






线程范围内的共享数据
package cn.itcast.heima2;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
public class ThreadScopeShareData {

    //private static int data = 0;
    private static Map<Thread, Integer> threadData = new HashMap<Thread, Integer>();
    public static void main(String[] args) {
    //起两个线程
        for(int i=0;i<2;i++){
            new Thread(new Runnable(){
                @Override
                public void run() {
                    //随即取32位整数
                    //也可以nextInt(10);10以内的整数
                    //data就是线程范围的数据
                    //我们要拿到这个数据共享给其他线程
                    //使用Map的方式
                    //数据库的事务中就需要用到
                    int data = new Random().nextInt();
                    System.out.println(Thread.currentThread().getName()
                            + " has put data :" + data);
                    //为每个线程存放独立的数据
                    //线程为key,数据为value
                    threadData.put(Thread.currentThread(), data);
                    new A().get();
                    new B().get();
                }
            }).start();
        }
    }
    
    static class A{
        public void get(){
            int data = threadData.get(Thread.currentThread());
            System.out.println("A from " + Thread.currentThread().getName()
                    + " get data :" + data);
        }
    }
    
    static class B{
        public void get(){
            int data = threadData.get(Thread.currentThread());            
            System.out.println("B from " + Thread.currentThread().getName()
                    + " get data :" + data);
        }        
    }
}

结果
Thread-0 has put data :4
Thread-1 has put data :3
A from Thread-1 get data :3
A from Thread-0 get data :4
B from Thread-0 get data :4
B from Thread-1 get data :3





============================================================================


ThreadLocal类及应用技巧
实现线程范围的共享变量
相当于一个map
package cn.itcast.heima2;
import java.util.Random;
public class ThreadLocalTest {
    //把ThreadLocal当Map用
    private static ThreadLocal<Integer> x = new ThreadLocal<Integer>();
    private static ThreadLocal<MyThreadScopeData> myThreadScopeData = new ThreadLocal<MyThreadScopeData>();
    public static void main(String[] args) {
        for(int i=0;i<2;i++){
            new Thread(new Runnable(){
                @Override
                public void run() {
                    int data = new Random().nextInt(20);
                    System.out.println(Thread.currentThread().getName()
                            + " has put data :" + data);
                    x.set(data);
                    /*MyThreadScopeData myData = new MyThreadScopeData();
                    myData.setName("name" + data);
                    myData.setAge(data);
                    myThreadScopeData.set(myData);*/
                    MyThreadScopeData.getThreadInstance().setName("name" + data);
                    MyThreadScopeData.getThreadInstance().setAge(data);
                    new A().get();
                    new B().get();
                }
            }).start();
        }
    }
    
    static class A{
        public void get(){
            int data = x.get();
            System.out.println("A from " + Thread.currentThread().getName()
                    + " get data :" + data);
/*            MyThreadScopeData myData = myThreadScopeData.get();;
            System.out.println("A from " + Thread.currentThread().getName()
                    + " getMyData: " + myData.getName() + "," +
                    myData.getAge());*/
            MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();
            System.out.println("A from " + Thread.currentThread().getName()
                    + " getMyData: " + myData.getName() + "," +
                    myData.getAge());
        }
    }
    
    static class B{
        public void get(){
            int data = x.get();            
            System.out.println("B from " + Thread.currentThread().getName()
                    + " get data :" + data);
            MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();
            System.out.println("B from " + Thread.currentThread().getName()
                    + " getMyData: " + myData.getName() + "," +
                    myData.getAge());            
        }        
    }
}
//实体类,把它当作单例来实例化
//这里是饿汉模式,需要的时候才去加载,在getInstance方法里面放new,
//线程不安全,需要加同步锁
//饱汉模式,直接成员变量里面先new一个实例出来,虽然别人未必需要
class MyThreadScopeData{
    private MyThreadScopeData(){}
    public static /*synchronized*/ MyThreadScopeData getThreadInstance(){
        MyThreadScopeData instance = map.get();
        if(instance == null){
            instance = new MyThreadScopeData();
            map.set(instance);
        }
        return instance;
    }
    //private static MyThreadScopeData instance = null;//new MyThreadScopeData();
    private static ThreadLocal<MyThreadScopeData> map = new ThreadLocal<MyThreadScopeData>();
    
    private String name;
    private int age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}


ThreadLocal
get()
set()
remove()
    ThreadLocal还有remove方法
    可以把当前线程的数据清除
    如果不清除, JVM也会在最后当作垃圾回收

Thread死掉时,应该有个回调方法,通知该线程销毁
应该有这样一个事件类
ThreadDeathEvent
ThreadDeathRequest

Java虚拟机的类叫做Runtime
其中有方法addShutdownHook(Thread hook)
每次虚拟机关闭的时候调用该方法
你可以传入一个Thread,里面写发邮件代码
这样每次JVM 挂了,就发邮件出来




============================================================================
多线程访问共享数据方式

设计4个线程,其中两个线程每次对j增加1,另外两个线程每次对j减少1

如果每个线程执行的代码相同,
可以使用同一个Runnable对象
这个对象中有那个共享数据,例如,卖票系统可以这么做
如果每个线程执行的代码不同
这时候需要不同的Runnable对象,有如下两种方式实现Runnable对象间数据共享
1 将共享数据封装在另一个对象中,
    然后将这个对象逐一传递给各个Runnable对象
    每个线程对共享数据的操作方法也分配到那个对象上去完成
2 将这些Runnalbe对象作为某一个类中的内部类
    共享数据作为这个外部类中的成员变量,
    每个线程对共享数据的操作方法分配给外部类
    作为内部类的各个Runnable对象调用外部类的这些方法
3 组合上面两种方法,
    共享数据封装在另一个对象中
    每个线程对共享数据的操作方法也分配到那个对象上去完成
    对象作为这个外部类中的成员变量或方法中的局部变量
    每个线程Runnable对象作为外部类中的成员内部类或局部内部类
4 总之要同步互斥的极端代码最好分别放在几个独立的方法中
    这些方法再放在同一个类中。



package cn.itcast.heima2;

public class MultiThreadShareData {

    private static ShareData1 data1 = new ShareData1();

    public static void main(String[] args) {
        ShareData1 data2 = new ShareData1();
        new Thread(new MyRunnable1(data2)).start();
        new Thread(new MyRunnable2(data2)).start();

        final ShareData1 data1 = new ShareData1();
        new Thread(new Runnable() {
            @Override
            public void run() {
                data1.decrement();

            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                data1.increment();

            }
        }).start();

    }

}

class MyRunnable1 implements Runnable {
    private ShareData1 data1;

    public MyRunnable1(ShareData1 data1) {
        this.data1 = data1;
    }

    public void run() {
        data1.decrement();

    }
}

class MyRunnable2 implements Runnable {
    private ShareData1 data1;

    public MyRunnable2(ShareData1 data1) {
        this.data1 = data1;
    }

    public void run() {
        data1.increment();
    }
}

class ShareData1 /* implements Runnable */{
    /*
     * private int count = 100;
     *
     * @Override public void run() { // TODO Auto-generated method stub
     * while(true){ count--; } }
     */

    private int j = 0;

    public synchronized void increment() {
        j++;
    }

    public synchronized void decrement() {
        j--;
    }
}

============================================================================

线程并发库
了解java.util.concurrent包以及子包的API文档
了解java.util.concurrent.atomic包
了解java.util.concurrent.lock包

并发编程网
http://ifeve.com/

cqrs, hadoop,disruptor

AtomicInteger类
多线程访问整数问题

构造方法
AtomicInteger(int initialValue)

当传入一个整数后,
该方法会锁住这个整数对象
其他线程将无法对其修改

调用下面方法对该整数进行数学操作并返回
int addAndGet(int delta)
int decrementAndGet() 减一
int incrementAndGet() 加一

这个类用在成员变量上
局部变量不需要用这个方法

还有
AtomicBoolean
AtomicLong
AtomicIntegerArray 操作数组里的整数
AtomicLongArray
AtomicIntegerFieldUpdater 操作对象里的整数
......




线程池

一个客户端连接tomcat
tomcat要起一个线程
多个客户端一起访问,
每个连接都存放在一个池子中
形成队列
抢到线程的就可以访问服务器
没有拿到就等待
对于空闲的线程可以
notifyAll()

new Thread(){
 run(){
    while(查询某个池子中是否还有客户要接待)
        接待与聊天一会儿
 }
}.start()



Java5的线程池与Executors类的应用
    创建固定大小的线程池
    创建缓存线程池
    创建单一线程池
关闭线程池
    shutdown与shutdownNow比较
用线程池启动定时器
    调用ScheduledExecutorService的schedule方法
    返回的ScheduleFuture对象可以取消任务
    支持间隔重复任务的定式方式
    不直接支持绝对定时方式,需要转换成相对事件方式

package cn.itcast.heima2;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class ThreadPoolTest {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // 表示每次可以起三个固定的线程
        // ExecutorService threadPool = Executors.newFixedThreadPool(3);
        // 缓存线程池,自动增加新的线程,在处理能力内,有多少线程起多少线程
        // ExecutorService threadPool = Executors.newCachedThreadPool();
        // 单个线程,如果线程死了,自动找一个新的线程
        ExecutorService threadPool = Executors.newSingleThreadExecutor();
        //起10个线程
        for (int i = 1; i <= 10; i++) {
            // 匿名内部类里只能访问final的局部变量,所以定义task,代表i
            final int task = i;
            // 把一个runnable任务丢给线程池
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    for (int j = 1; j <= 10; j++) {
                        try {
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()
                                + " is looping of " + j + " for  task of "
                                + task);
                    }
                }
            });
        }
        System.out.println("all of 10 tasks have committed! ");
        // threadPool.shutdownNow(); 结束线程池

        //创建调度线程池,支持同时处理3个线程
        //用来定时启动线程,6秒后执行,每2秒执行一次
        //还可以直接用schedule方法,不指定频率
        //如果要在固定时间执行,可以这么处理
        //date.getTime()-System.currentTimeMillis()
        Executors.newScheduledThreadPool(3).scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("bombing!");

            }
        }, 6, 2, TimeUnit.SECONDS);
    }

}




============================================================================

Callable与Future的应用
向线程提交任务,返回任务结果

Future取得的结果类型和Callable返回的结果类型必须一致
    通过泛型来实现
    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。


Callable要采用ExecutorService的submit方法提交
    public interface Callable<V>
    返回结果并且可能抛出异常的任务。
    实现者定义了一个不带任何参数的叫做 call 的方法。
    Callable 接口类似于 Runnable,
    两者都是为那些其实例可能被另一个线程执行的类设计的。
    但是 Runnable 不会返回结果,
    并且无法抛出经过检查的异常。
    返回的future对象可以取消任务
    只有一个方法V call() 计算结果,
    如果无法计算结果,
    则抛出一个Exception异常。
    使用方法
    ExecutorService threadPool = Executors.newSingleThreadExccutor();
    Future<T> future = threadPool.submit(new Callable<T>(){
    //接收一个Callable接口的实例对象
            //覆盖Callable接口中的call方法,抛出异常
            public T call() throws Exception
            {
                ruturn T
            }
    });

    
CompletionService也可以取得结果
    public interface CompletionService<V>
    CompletionService用于提交一组Callable任务,
    其take方法返回一个已完成的Callable任务对应的Future对象。
    将生产新的异步任务与使用已完成任务的结果分离开来的服务。
    生产者 submit 执行的任务。使用者 take 已完成的任务,
    并按照完成这些任务的顺序处理它们的结果。
    好比同时种几块麦子等待收割,收割时哪块先熟先收哪块。

package cn.itcast.heima2;

import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class CallableAndFuture {

    /**
     * @param args
     */
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newSingleThreadExecutor();
        Future<String> future = threadPool.submit(
            new Callable<String>() {
                public String call() throws Exception {
                    Thread.sleep(2000);
                    return "hello";
                };
            }
        );
        System.out.println("等待结果");
        try {
            //拿到结果对象
            System.out.println("拿到结果" + future.get());
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        //创建线程池,传递给coms用threadPool执行任务,执行的任务返回结果都是整数
        ExecutorService threadPool2 = Executors.newFixedThreadPool(10);
        CompletionService<Integer> completionService = new ExecutorCompletionService<Integer>(
                threadPool2);
        //提交10个任务  种麦子
        for (int i = 1; i <= 10; i++) {
            //匿名内部类使用外部变量要用final修饰
            final int seq = i;
            completionService.submit(new Callable<Integer>() {
                @Override
                //覆盖call方法
                public Integer call() throws Exception {
                    Thread.sleep(new Random().nextInt(5000));
                    return seq;
                }
            });
        }
        //等待收获    割麦子
        for (int i = 0; i < 10; i++) {
            try {
                //take获取第一个Future对象,用get获取结果
                System.out.println(completionService.take().get());
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (ExecutionException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

}



============================================================================
线程锁
Lock & Condition 实现线程同步通信

Lock比传统线程模型中的synchronized方式更加面向对象
    锁本身也是个对象
    两个线程执行的代码片断要实现同步互斥的效果
    它们必须用同一个lock对象
    锁上在代表要操作的资源的雷的内部方法中,
    而不是线程代码中
读写锁,分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥
    这时jvm自己控制的,你只要上好锁就可以
    如果你的代码只读数据,可以多人读不可以同时写,就上读锁
    如果代码是修改数据,只能有一个人在写,不能同时读取,就上写锁
Condition的功能类似于传统的wait和notify功能。执行条件。
    在等待Condition时,允许发生虚假唤醒,通常作为对基础平台语义的让步
    Condition总是在一个循环中被等待
    一个锁内部可以有多个Condition,有多路等待和通知。


Lock是个接口,要用Ctrl T找到其实现类来new
修改以前TraditionalThreadSynchronized的代码
public void output(String name){
    int len = name.length();
    //同步代码块,当运行到这段代码,上锁
    //必须锁住同一个对象,而不是某个局部变量
    //如果Outputer类有成员变量,这里也可以锁住该成员变量,也是同一个对象
    //也可以直接用synchronized (this)
    synchronized (Outputer.class)
    {
        for(int i=0;i<len;i++){
            System.out.print(name.charAt(i));
        }
        System.out.println();
    }
}
使用Lock功能
package cn.itcast.heima2;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockTest {

    /**
     * @param args
     */
    public static void main(String[] args) {
        new LockTest().init();
    }
    
    private void init(){
        final Outputer outputer = new Outputer();
        new Thread(new Runnable(){
            @Override
            public void run() {
                while(true){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    outputer.output("zhangxiaoxiang");
                }
                
            }
        }).start();
        
        new Thread(new Runnable(){
            @Override
            public void run() {
                while(true){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    outputer.output("lihuoming");
                }
                
            }
        }).start();
        
    }

    static class Outputer{
        Lock lock = new ReentrantLock();
        public void output(String name){
            int len = name.length();
            //锁必须使用try/catch,在finally时释放
            //否则万一之间的语句出错,直接返回,锁就永远不得释放了
            lock.lock();
            try{
                for(int i=0;i<len;i++){
                    System.out.print(name.charAt(i));
                }
                System.out.println();
            }finally{
                lock.unlock();
            }
        }
        
        public synchronized void output2(String name){
            int len = name.length();
            for(int i=0;i<len;i++){
                    System.out.print(name.charAt(i));
            }
            System.out.println();
        }
        
        public static synchronized void output3(String name){
            int len = name.length();
            for(int i=0;i<len;i++){
                    System.out.print(name.charAt(i));
            }
            System.out.println();
        }    
    }
}




读写锁案例
多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥
如果上以前的Lock,无法满足上面功能
ReadWriteLock rwl = new ReentrantReadWriteLock();
package cn.itcast.heima2;
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 Queue3 q3 = new Queue3();
        
        for (int i = 0; i < 3; i++) {
            //3个线程读数据
            new Thread() {
                public void run() {
                    while (true) {
                        q3.get();
                    }
                }

            }.start();
            //3个线程写数据
            new Thread() {
                public void run() {
                    while (true) {
                        q3.put(new Random().nextInt(10000));
                    }
                }

            }.start();
        }

    }
}

class Queue3 {
    //共享数据
    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 {
            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();
        }

    }
}


hibernate
//无论是否有数据,会得到一个User$Proxy 继承User,
//里面有个id,以及一个真正的User,初始是null
User user = session.load(User.class, id, <LockMode>);

User$Proxy extends User{
    private Integer id = id;
    User realUser = null;
    getName(){
        if(realUser == null){
            realUser = session.get(id);
            if(realUser == null){
                throw Exception;
            }
        }
        return realUser.getName();
    }
}

//没有数据返回Null
User user = session.get(User.class, id);

代理缓存机制:
嵌套使用读写锁,
当多个线程同时读数据时,
先挂读锁
当没有数据可读了
挂上写锁,禁止再读
当数据写完了,
再解放写锁,大家又可以读了
最后释放读锁

面试题,缓存系统伪代码
很多对象存在缓存里
这个缓存设计成一个Map
线程访问缓存,先检查是否有数据
没有就写入,有的话就拿出来

package cn.itcast.heima2;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class CacheDemo {

    private Map<String, Object> cache = new HashMap<String, Object>();

    public static void main(String[] args) {
        // 多线程访问cache代码

    }

    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;
    }
}



Condition 案例
对比传统的线程通信wait, notify, notifyAll
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
lock.lock();
condition.await();
。。。
condition.signal();
lock.unlock();

对比传统的通信
synchronized{
    this.wait();
    。。。
    this.notify();
}


package cn.itcast.heima2;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionCommunication {

    /**
     * @param args
     */
    public static void main(String[] args) {

        final Business business = new Business();
        new Thread(new Runnable() {

            @Override
            public void run() {

                for (int i = 1; i <= 50; i++) {
                    business.sub(i);
                }

            }
        }).start();

        for (int i = 1; i <= 50; i++) {
            business.main(i);
        }

    }

    //把外部类变成内部类
    //再使用static,就可以被静态的main方法访问
    static class Business {
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        private boolean bShouldSub = true;

        public void sub(int i) {
            lock.lock();
            try {
                while (!bShouldSub) {
                    try {
                        condition.await();
                    } catch (Exception e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                for (int j = 1; j <= 10; j++) {
                    System.out.println("sub thread sequence of " + j
                            + ",loop of " + i);
                }
                bShouldSub = false;
                condition.signal();
            } finally {
                lock.unlock();
            }
        }

        public void main(int i) {
            lock.lock();
            try {
                while (bShouldSub) {
                    try {
                        condition.await();
                    } catch (Exception e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                for (int j = 1; j <= 100; j++) {
                    System.out.println("main thread sequence of " + j
                            + ",loop of " + i);
                }
                bShouldSub = true;
                condition.signal();
            } finally {
                lock.unlock();
            }
        }

    }
}




案例,三个线程通信
package cn.itcast.heima2;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ThreeConditionCommunication {

    /**
     * @param args
     */
    public static void main(String[] args) {

        final Business business = new Business();
        new Thread(new Runnable() {

            @Override
            public void run() {

                for (int i = 1; i <= 50; i++) {
                    business.sub2(i);
                }

            }
        }).start();

        new Thread(new Runnable() {

            @Override
            public void run() {

                for (int i = 1; i <= 50; i++) {
                    business.sub3(i);
                }

            }
        }).start();

        for (int i = 1; i <= 50; i++) {
            business.main(i);
        }

    }

    static class Business {
        Lock lock = new ReentrantLock();
        Condition condition1 = lock.newCondition();
        Condition condition2 = lock.newCondition();
        Condition condition3 = lock.newCondition();
        private int shouldSub = 1;

        public void sub2(int i) {
            lock.lock();
            try {
                while (shouldSub != 2) {
                    try {
                        condition2.await();
                    } catch (Exception e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                for (int j = 1; j <= 10; j++) {
                    System.out.println("sub2 thread sequence of " + j
                            + ",loop of " + i);
                }
                shouldSub = 3;
                condition3.signal();
            } finally {
                lock.unlock();
            }
        }

        public void sub3(int i) {
            lock.lock();
            try {
                while (shouldSub != 3) {
                    try {
                        condition3.await();
                    } catch (Exception e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                for (int j = 1; j <= 20; j++) {
                    System.out.println("sub3 thread sequence of " + j
                            + ",loop of " + i);
                }
                shouldSub = 1;
                condition1.signal();
            } finally {
                lock.unlock();
            }
        }

        public void main(int i) {
            lock.lock();
            try {
                while (shouldSub != 1) {
                    try {
                        condition1.await();
                    } catch (Exception e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                for (int j = 1; j <= 100; j++) {
                    System.out.println("main thread sequence of " + j
                            + ",loop of " + i);
                }
                shouldSub = 2;
                condition2.signal();
            } finally {
                lock.unlock();
            }
        }

    }
}



============================================================================

Semaphore 信号灯
维护当前访问自身的线程个数,提供同步机制
控制同时访问资源的线程个数,比如一个文件允许并发访问个数
单个信号量的话可以实现互斥锁功能,并且可以是由一个线程获得了锁
再由另一个线程释放锁,可以应用于死锁恢复的场合
//只允许3个线程同时访问资源
final Semaphore sp = new Semaphore(3);
sp.acquire();
。。。
sp.release();


package cn.itcast.heima2;

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();
        //只允许3个线程同时访问资源
        final Semaphore sp = new Semaphore(3);
        //10个线程并发访问
        //总是3个并发,7个等待
        //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);
        }
        service.shutdown();
    }

}







CyclicBarrier 路障
表示大家彼此等待,大家集合好后才开始出发
分散活动后又在制定地点集合碰面


final CyclicBarrier cb = new CyclicBarrier(3);
。。。
cb.await();
。。。
cb.await();


package cn.itcast.heima2;
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();
    }
}





CountDownLatch
犹如倒计时计数器
调用CountDownLatch对象的countDown方法将计数器减1
达到0时,所有等待着或单个等待者开始执行
可以实现一个人,多个人等待其他所有人都来通知他
可以实现一个人通知多个人的效果

final CountDownLatch cdOrder = new CountDownLatch(1);
//等待归零
cdOrder.await();
//减一
cdOrder.countDown();


package cn.itcast.heima2;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CountdownLatchTest {

    public static void main(String[] args) {
        ExecutorService service = Executors.newCachedThreadPool();
        final CountDownLatch cdOrder = new CountDownLatch(1);
        final CountDownLatch cdAnswer = new CountDownLatch(3);
        for (int i = 0; i < 3; i++) {
            Runnable runnable = new Runnable() {
                public void run() {
                    try {
                        System.out.println("线程"
                                + Thread.currentThread().getName() + "正准备接受命令");
                        //等待归零
                        cdOrder.await();
                        System.out.println("线程"
                                + Thread.currentThread().getName() + "已接受命令");
                        Thread.sleep((long) (Math.random() * 10000));
                        System.out
                                .println("线程"
                                        + Thread.currentThread().getName()
                                        + "回应命令处理结果");
                        cdAnswer.countDown();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            };
            service.execute(runnable);
        }
        //主线程中计数器countDown
        try {
            Thread.sleep((long) (Math.random() * 10000));

            System.out.println("线程" + Thread.currentThread().getName()
                    + "即将发布命令");
            cdOrder.countDown();
            System.out.println("线程" + Thread.currentThread().getName()
                    + "已发送命令,正在等待结果");
            cdAnswer.await();
            System.out.println("线程" + Thread.currentThread().getName()
                    + "已收到所有响应结果");
        } catch (Exception e) {
            e.printStackTrace();
        }
        service.shutdown();

    }
}



Exchanger
用于两个人之间的数据交换
每个人在完成一定的事务后想与对方交换数据
第一个先拿出数据的人将一直等待第二个人拿着数据到来时
才可以彼此交换数据

final Exchanger exchanger = new Exchanger();
String data2 = (String) exchanger.exchange(data1);



package cn.itcast.heima2;
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();
        //线程一把"zxx"交换出去
        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) {

                }
            }
        });
        //线程二把"lhm"交换出去
        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) {

                }
            }
        });
        service.shutdown();
    }
}
线程一和线程二数据交换完成后
再一起执行下去





============================================================================

可阻塞的队列
队列包含固定长度的队列和不固定长度的队列
阻塞队列的作用于实际应用,实现原理
ArrayBlockingQueue
    只有put和take方法才有阻塞功能
用3个空间的队列来掩饰阻塞队列的功能和效果
用2个具有1个空间的队列来实现同步通知的功能
阻塞队列与Semaphore有相似和不同

队列是先进先出
可以有固定大小,也有不固定大小
对固定大小的队列,当放满了,再放进去的话就是阻塞或者报错
对于非阻塞队列,就是报错
对于阻塞队列就是阻塞


案例,阻塞队列
有个发信机,对外发射呼叫信息
信号来自一个服务器电脑,连着发信机
比如发送好多短信
而这个电脑通过网络接收来自各个客户端消息
客户端接收来自寻呼机的短信
消息放入一个数组队列,为一个线程,
比如可以存放100个元素
从0开始放,放到100后
唤醒拿走线程,重新从0开始放
拿走线程中,从数组中从0开始取走数组一个元素
一直取到数组中没有元素,唤醒放入线程

在实际中,已经提供了ArrayBlockingQueue类实现了功能

BlockingQueue queue = new ArrayBlockingQueue(3);
        抛出异常        返回boolean        阻塞直到空出    超时
插入:     add(e)             offer(e)         put(e)            offer(e,time,unit)
删除:     remove()        poll()            take()            poll(time,unit)
查询:     element()         peek()    


package cn.itcast.heima2;

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();
    }
}



阻塞队列同步通信
main方法和sub方法交替执行
通过1个空间的阻塞队列实现

package cn.itcast.heima2;
import java.util.Collections;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
public class BlockingQueueCommunication {

    /**
     * @param args
     */
    public static void main(String[] args) {

        final Business business = new Business();
        new Thread(new Runnable() {

            @Override
            public void run() {

                for (int i = 1; i <= 10; i++) {
                    business.sub(i);
                }

            }
        }).start();

        for (int i = 1; i <= 10; i++) {
            business.main(i);
        }

    }

    static class Business {

        BlockingQueue<Integer> queue1 = new ArrayBlockingQueue<Integer>(1);
        BlockingQueue<Integer> queue2 = new ArrayBlockingQueue<Integer>(1);

        //这里去掉了static修饰,(静态代码块无论多少对象产生它只生成一次)
        //表示匿名构造方法,运行在任何构造方法之前
        //每个创建的对象都会调用
        {
            //Collections.synchronizedMap(null);
            try {
                System.out.println("xxxxxdfsdsafdsa");
                queue2.put(1);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

        public void sub(int i) {
            try {
                queue1.put(1);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            for (int j = 1; j <= 10; j++) {
                System.out.println("sub thread sequece of " + j + ",loop of "
                        + i);
            }
            try {
                queue2.take();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

        public void main(int i) {
            try {
                queue2.put(1);
            } catch (InterruptedException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            }
            for (int j = 1; j <= 100; j++) {
                System.out.println("main thread sequece of " + j + ",loop of "
                        + i);
            }
            try {
                queue1.take();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

}












============================================================================

同步集合类的应用

java.util.concurrent 是java5的并发库包
Executors 创建线程池
Queues 队列 ArrayBlockingQueue LinkedBlockingQueue
Timing 计时器
Synchronizers 同步工具 Semaphore CuntDownLatch CyclicBarrier Exchanger
Concurrent Collections 并发集合

传统集合在并发访问时有问题
    HashSet
    HashMap
    ArrayList
    都是线程不安全的
    while(hasnext())的hasnext()
    使用cursor游标,控制数据的遍历
    这个cursor可以被其他线程访问
    
传统方式用Collections工具类提供的synchronizedCollection方法获得同步集合
Java5提供了下面同步集合
    看java.util.concurrent包下的介绍
    ConcurrentHashMap
    CopyOnWriteArrayList
    CopyOnWriteArraySet
传统方式下Collection在迭代集合时,不允许对集合进行修改
    根据AbstractList的checkForComodification方法的源码
    分析产生ConcurrentModicicationException的原因

锁分段技术

HashTable容器在竞争激烈的并发环境下表现出效率低下的原因是
所有访问HashTable的线程都必须竞争同一把锁,
那假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,
那么当多线程访问容器里不同数据段的数据时,
线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,
这就是ConcurrentHashMap所使用的锁分段技术,
首先将数据分成一段一段的存储,然后给每一段数据配一把锁,
当一个线程占用锁访问其中一个段数据的时候,
其他段的数据也能被其他线程访问。    


以前使用下面方法处理集合的线程不安全
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
        return new SynchronizedMap<>(m);
}

Collections.synchronizedMap(null);

这个SynchronizedMap,就是个Map的代理
它实现Map接口,重写map的方法
但在重写时,都加上synchronized关键字
private static class SynchronizedMap<K,V>
        implements Map<K,V>, Serializable {
        private static final long serialVersionUID = 1978198479659022715L;

        private final Map<K,V> m;     // Backing Map
        final Object      mutex;        // Object on which to synchronize

        SynchronizedMap(Map<K,V> m) {
            if (m==null)
                throw new NullPointerException();
            this.m = m;
            mutex = this;
        }

        SynchronizedMap(Map<K,V> m, Object mutex) {
            this.m = m;
            this.mutex = mutex;
        }

        public int size() {
            synchronized (mutex) {return m.size();}
        }
        public boolean isEmpty() {
            synchronized (mutex) {return m.isEmpty();}
        }
        。。。。。
}



HashMap和HashSet关系
HashSet内部实现其实就是个HashMap
它只适用HashMap的Key,而Value部分不使用
HashMap完全可以当HashSet用

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
{
    static final long serialVersionUID = -5024744406713321676L;

    private transient HashMap<E,Object> map;

    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();

    /**
     * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
     * default initial capacity (16) and load factor (0.75).
     */
    public HashSet() {
        map = new HashMap<>();
    }
。。。。。。
}



Java5之前的集合的另外一个隐患
循环删除集合中元素时会报错
比如有一个User实体类
实现了equals, toString等方法
package cn.itcast.heima2;

public class User implements Cloneable {
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof User)) {
            return false;
        }
        User user = (User) obj;
        // if(this.name==user.name && this.age==user.age)
        if (this.name.equals(user.name) && this.age == user.age) {
            return true;
        } else {
            return false;
        }
    }

    public int hashCode() {
        return name.hashCode() + age;
    }

    public String toString() {
        return "{name:'" + name + "',age:" + age + "}";
    }

    public Object clone() {
        Object object = null;
        try {
            object = super.clone();
        } catch (CloneNotSupportedException e) {
        }
        return object;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }
}


使用该User类
如果使用循环,从集合里面剔出一条数据,会报错
package cn.itcast.heima2;
import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;
public class CollectionModifyExceptionTest {
    public static void main(String[] args) {
        Collection users = new ArrayList();
        //Collection users = new CopyOnWriteArrayList();

        // new ArrayList();
        users.add(new User("张三", 28));
        users.add(new User("李四", 25));
        users.add(new User("王五", 31));
        Iterator itrUsers = users.iterator();
        while (itrUsers.hasNext()) {
            System.out.println("aaaa");
            User user = (User) itrUsers.next();
            if ("李四".equals(user.getName())) {
                users.remove(user);
                // itrUsers.remove();
            } else {
                System.out.println(user);
            }
        }
    }
}





Java5之后都提供了并发集合类
    ConcurrentHashMap
    ConcurrentSkipListMap
    ConcurrentSkipListSet
    CopyOnWriteArrayList
    CopyOnWriteArraySet

将ArrayList改成CopyOnWriteArrayList
在写得时候有一份copy
就可以放心多线程和迭代操作集合了
public class CollectionModifyExceptionTest {
    public static void main(String[] args) {
        //Collection users = new ArrayList();
        Collection users = new CopyOnWriteArrayList();

        // new ArrayList();
        users.add(new User("张三", 28));
        users.add(new User("李四", 25));
        users.add(new User("王五", 31));
        Iterator itrUsers = users.iterator();
        while (itrUsers.hasNext()) {
            System.out.println("aaaa");
            User user = (User) itrUsers.next();
            if ("李四".equals(user.getName())) {
                users.remove(user);
                // itrUsers.remove();
            } else {
                System.out.println(user);
            }
        }
    }
}








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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值