黑马程序员-JAVA-多线程使用初探

——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-


Java中建立线程的一般方法

Java中对于线程的操作依赖于类Thread,这个类是线程在Java中的抽象

Thread类的构造方法有3个要素和一个可选项,三个参数分别为String name,Runnable target和ThreadGroup group。另外可以指定long stackSize堆栈大小。

可以通过两种方法来建立线程:
1.继承Thread类,覆盖run()方法。
2.实现Runnable接口,实现run()方法,并传入Thread构造函数启动。一般来说,采用这种方式的情况比较多,因为继承的限制比较大。

如果一个Thread子类(比如常见的匿名类)用Runnable对象构造,那么运行取决于其run()是否调用Thread类中的run(),或是target.run();如果不显式的super.run()或者target.run()那么传入的Runnable是无意义的
例子如下:

        // duplicate run()
        new Thread(new Runnable() {

            @Override
            public void run() {
                sp("anonymous runnable:" + this);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }) {
            @Override
            public void run() {
                while (true) {
                    sp("anonymous subclass:" + this.getName());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();//anonymous subclass:Thread-

Timer

Timer是一种工具,线程用其安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行。每个 Timer 对象相对应的是单个后台线程,用于顺序地执行所有计时器任务。Timer的任务是由传入的schedule参数TimerTask。而且构造对象即是开始Timer线程。
也可以使用Timer嵌套创建Timer,下面是一个用TimerTask嵌套交替创建Timer执行的例子:

        // alternate timer shedule
        new Timer().schedule(new YTask(), 2000);

    static class YTask extends TimerTask {
        int nextSpan;

        public YTask(int i) {
            this.nextSpan = i == 2 ? 4 : 2;
        }

        public YTask() {
            this(2);
        }

        @Override
        public void run() {
            sp("bbbb");
            new Timer().schedule(new YTask(nextSpan), 1000 * nextSpan);
        }

    }

线程的安全问题与同步

非同步错误

多个线程访问同一个对象或者资源时,如果没有采取同步手段防止竞争很有可能造成非预期的结果,比如这个简单的例子:


        // unsynchonize problem
        new Thread(new UnsyncedOutput("shengguangzu")).start();
        new Thread(new UnsyncedOutput("liuzhijun")).start();

    static class UnsyncedOutput implements Runnable {
        String content;

        public UnsyncedOutput(String content) {
            super();
            this.content = content;
        }

        @Override
        public void run() {
            while (true) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                output();
            }
        }

        public void output() {
            // uncomment to fix
            // synchronized (System.out) {
                for (char c : content.toCharArray()) {
                    System.out.print(c);
                }
            // }
            System.out.println();
        }

    }

死锁

在使用同步锁机制时,如果没有仔细分析锁的竞争,在使用多个锁的时候可能造成死锁,即锁无法被正确的释放,相关线程都无法继续,进入阻塞状态。
一个简单的例子:

// dead lock
        Object lockA=new Object(),lockB=new Object();
        new Thread(new Runnable() {

            @Override
            public void run() {
                synchronized (lockB) {
                    sp(Thread.currentThread().getName()+"got lockB");
                    ts(100);
                    synchronized (lockA) {//can't got
                        sp(Thread.currentThread().getName()+"got lockA");//unreachable
                    }
                }
                sp(Thread.currentThread().getName()+" finish");//unreachable
            }
        }).start();
        new Thread(new Runnable() {

            @Override
            public void run() {
                synchronized (lockA) {
                    sp(Thread.currentThread().getName()+"got lockA");
                    ts(100);
                    synchronized (lockB) {//can't got
                        sp(Thread.currentThread().getName()+"got lockB");//unreachable
                    }
                }
                sp(Thread.currentThread().getName()+" finish");//unreachable
            }
        }).start();

wait和notify

依赖线程调度大部分情况可以较好的完成任务,但是在线程较多的时候会比较消耗资源,这时需要一种机制来使线程进入相对长时间的休眠,等到信号才唤醒继续。这就是wait和notify机制,下面是一个利用wait和notify机制交替执行线程的例子:

// wait & notify
        Business biz=new Business();
        new Thread(new Runnable() {

            @Override
            public void run() {
                // TODO Auto-generated method stub
                biz.doA();
            }
        }).start();
        new Thread(new Runnable() {

            @Override
            public void run() {
                // TODO Auto-generated method stub
                biz.doB();
            }
        }).start();
    static class Business{
        volatile int signal;

        public Business() {
            super();
            this.signal=1;
        }

        public void doA(){
            while(true){
                synchronized (this) {
                    while(signal!=1){
                        try {
                            this.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    sp("start A in "+Thread.currentThread().getName());
                    ts(new Random().nextInt(2000));
                    sp("done A in "+Thread.currentThread().getName());
                    this.signal=2;
                    this.notify();
                }
            }
        }
        public void doB(){
            while(true){
                synchronized (this) {
                    while(signal!=2){
                        try {
                            this.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    sp("start B in "+Thread.currentThread().getName());
                    ts(new Random().nextInt(2000));
                    sp("done B in "+Thread.currentThread().getName());
                    this.signal=1;
                    this.notify();
                }
            }
        }
    }

多线程中的单例

由于多个线程可能竞争使用一个单例,如果采用懒汉式,则有可能产生超过一个实例对象,这时就需要双重检查,即进入同步块后再检查一次。
下面是一些单例的实现模式,均为线程安全:


    // double check singleton
    static class SyncSingleton {
        //volatile to avoid exception
        private static volatile SyncSingleton instance;

        private SyncSingleton() {
        };

        public static SyncSingleton getInstance() {
            if (instance == null) {// check before lock
                synchronized (SyncSingleton.class) {
                    if (instance == null) {// check after lock
                        instance = new SyncSingleton();
                    }
                }
            }
            return instance;
        }
    }

    // another double check way
    // with prevent deserialize in Serializable
    static class SyncSingleton2 implements Serializable{
        /**
         * 
         */
        private static final long serialVersionUID = 4675690059690737395L;
        //volatile to avoid exception
        private volatile static SyncSingleton instance;

        private SyncSingleton2() {
        };

        //this way has an lower efficient
        public static synchronized SyncSingleton getInstance() {
            if (instance == null) {// check after lock
                instance = new SyncSingleton();
            }
            return instance;
        }

        //this method to prevent deserialize crack
        private Object readResolve() throws ObjectStreamException{
            return instance;
        }
    }

    //hungry way
    static class HungrySingleton{
        private static HungrySingleton instance=new HungrySingleton();
        private HungrySingleton(){};
        public static HungrySingleton getInstance(){
            return instance;
        }
    }

    // inner class way it's lazy
    static class InnerClassSingleton{
        private InnerClassSingleton(){};
        private static class SingletonHolder{
            private static InnerClassSingleton instance=new InnerClassSingleton();
        }
        public InnerClassSingleton getInstance(){
            return SingletonHolder.instance;
        }
    }

    // singleton by enum 
    // concurrent safe ,deserialize safe,and easy
    static enum EnumSingleton {
        INSTANCE;
        private EnumSingleton() {
        };// that's all we need
    }

线程范围的数据共享

有时我们需要在线程级别共享一个数据,一般的,可以使用一个静态HashMap,用线程做key,在使用处调用即可,也不需要同步锁。
但是Java已经提供了一个使用更方便的容器:
ThreadLocal
这个容器很简单,只有4个方法, T get() ,protected T initialValue() , void remove() , void set(T value) 。一目了然,在操作容器内对象的时候,ThreadLocal会自动查询当前线程上下文,对对应的对象进行操作。
简单的示例:

        // ThreadLocal demo
        final ThreadLocal ti = new ThreadLocal();
        for (int i = 0; i < 4; i++) {
            new Thread(new Runnable() {

                @Override
                public void run() {
                    int val = r.nextInt(10000);
                    ti.set(val);
                    sp("ThreadLocal val of " + Thread.currentThread().getName()
                            + " set to " + val);
                    ts(r.nextInt(3000));
                    sp("ThreadLocal val of " + Thread.currentThread().getName()
                            + " is " + ti.get());
                }
            }).start();
        }

Java1.5中关于线程的新特性

java.util.concurrent.atomic

类的小工具包,支持在单个变量上解除锁的线程安全编程。
一般来说,实现了在一个元素上的一次复合操作的原子性,即保证其修改符合预期。
已经支持的类型有Integer,Long,Boolean,还有Reference的引用类型。

线程池

Executor 定义了一个提交Runnable任务的执行器。而ExecutorService则定义了更为实用的可以提交Runnable任务或者Callable任务,并且可以关闭的执行器。
Java已经通过线程池实现了ExecutorService,而且提供了Executors的工厂方法来产生各种线程池执行器。

newCachedThreadPool()

建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。默认的存活时间是60秒。

newFixedThreadPool(int nThreads)

创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在某个线程被显式地关闭之前,池中的线程将一直存在。

newSingleThreadExecutor()

创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。注意,如果因为在关闭前的执行期间出现失败而终止了此单个线程,那么如果需要,一个新线程将代替它执行后续的任务。

Callable & Future

泛型返回的ExecutorService任务,其中Callable指定一个泛型返回类型,交给指定的ExecutorService执行之后,返回Future,在异步执行过程中,可以通过查询Future的isDone() 来了解完成状况。完成之后,可以通过Future的get() 来获取结果,如果get() 时尚未完成,则将阻塞。
Java中对此的默认实现是FutureTask

使用Future的线程池的简单例子

        // ThreadPool demo with Future
        ExecutorService exes = Executors.newFixedThreadPool(3);
        final int times = 20;
        final List> results = new ArrayList>(
                times);
        for (int i = 0; i < times; i++) {
            results.add(exes.submit(() -> {
                ts(1000);
                int nextInt = r.nextInt(1000);
                sp("gen num :" + nextInt + " in "
                        + Thread.currentThread().getName());
                return nextInt;
            }));
        }
        for (Future future : results) {
            try {
                sp(future.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        }
        exes.shutdown();

java.util.concurrent.locks

Java1.5为锁和等待条件提供一个接口和类的框架,它相对内置的synchonized和wait/notify机制有所增强。

Lock

synchronized 方法或语句的使用提供了对与每个对象相关的隐式监视器锁的访问,但却强制所有锁获取和释放均要出现在一个块结构中:当获取了多个锁时,它们必须以相反的顺序释放,且必须在与所有锁被获取时相同的词法范围内释放所有锁。
而Lock提供了更为灵活的机制:它可以以任意顺序解锁,例如,某些遍历并发访问的数据结果的算法要求使用 “hand-over-hand” 或 “chain locking”:获取节点 A 的锁,然后再获取节点 B 的锁,然后释放 A 并获取 C,然后释放 B 并获取 D,依此类推。Lock 还实现提供了使用 synchronized 方法和语句所没有的其他功能,包括提供了一个非块结构的获取锁尝试 (tryLock())、一个获取可中断锁的尝试 (lockInterruptibly()) 和一个获取超时失效锁的尝试 (tryLock(long, TimeUnit))。
锁定和取消锁定出现在不同作用范围中时,必须谨慎地确保保持锁定时所执行的所有代码用 try-finally 或 try-catch 加以保护,以确保在必要时释放锁。
注意,Lock 实例只是普通的对象,其本身可以在 synchronized 语句中作为目标使用。为了避免混淆,建议除了在其自身的实现中之外,决不要以这种方式使用 Lock 实例。

一般的使用Lock的格式:

     Lock l = ...; 
     l.lock();
     try {
         // access the resource protected by this lock
     } finally {
         l.unlock();
     }
ReadWriteLock

ReadWriteLock实现了对同一资源的一对相关锁,其特殊之处在于没有写锁的情况下允许并发读锁,在读的发生频率较高的时候可以提高系统效率。而写锁依然是独占的。
一个简单的例子:读操作可以并发

        //ReadWriteLock
        ReadWriteLockDemo lockObj=new ReadWriteLockDemo();
        for (int i = 0; i < 4; i++) {
            if(i==3){
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        while(true){
                            lockObj.set(r.nextInt(1000));
                        }
                    }
                }).start();
            }
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while(true){
                        lockObj.get();
                    }
                }
            }).start();
        }
    static class ReadWriteLockDemo{
        Object data;
        ReadWriteLock rwl=new ReentrantReadWriteLock();
        {
            data=123;
        }
        public void get(){
            rwl.readLock().lock();
            try {
                sp("prepare to read in"+Thread.currentThread().getName());
                ts(new Random().nextInt(300));
                sp("data is : "+data+" from "+Thread.currentThread().getName());
                ts(new Random().nextInt(100));
                sp("done to read in"+Thread.currentThread().getName());
            }finally{
                rwl.readLock().unlock();
            }
            ts(new Random().nextInt(1000));
        }
        public void set(Object o){
            rwl.writeLock().lock();
            try {
                sp("prepare to write in"+Thread.currentThread().getName());
                ts(new Random().nextInt(300));
                data=o;
                sp("data has set to : "+data+" from "+Thread.currentThread().getName());
                ts(new Random().nextInt(100));
                sp("done to write in"+Thread.currentThread().getName());
            }finally{
                rwl.writeLock().unlock();
            }
            ts(new Random().nextInt(1000));
        }
    }
Condition

Condition 实现了一个面向锁的线程控制等待和唤醒的条件机制,相较于wait¬ify机制,它能对同一个锁添加多个Condition,以实现多重状态的判断。
这是一个利用Condition实现3个操作轮流执行的例子:

        // Condition
        final ConditionDemo cd=new ConditionDemo();
        new Thread(() -> cd.BusinessA()).start();
        new Thread(() -> cd.BusinessB()).start();
        new Thread(() -> cd.BusinessC()).start();
        new Thread(() -> cd.BusinessA()).start();
        new Thread(() -> cd.BusinessB()).start();
        new Thread(() -> cd.BusinessC()).start();

    static class ConditionDemo {
        volatile int signal = 1;
        Lock cl = new ReentrantLock();
        Condition c1 = cl.newCondition(), c2 = cl.newCondition(), c3 = cl.newCondition();

        public void BusinessA() {
            while (true) {
                cl.lock();
                try {
                    while (signal != 1) {
                        try {
                            c1.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    sp("BusinessA in "+Thread.currentThread().getName());
                    ts(1000);
                    this.signal=2;
                    c2.signal();
                } finally {
                    cl.unlock();
                }
            }
        }
        public void BusinessB() {
            while (true) {
                cl.lock();
                try {
                    while (signal != 2) {
                        try {
                            c2.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    sp("BusinessB in "+Thread.currentThread().getName());
                    ts(1000);
                    this.signal=3;
                    c3.signal();
                } finally {
                    cl.unlock();
                }
            }
        }
        public void BusinessC() {
            while (true) {
                cl.lock();
                try {
                    while (signal != 3) {
                        try {
                            c3.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    sp("BusinessC in "+Thread.currentThread().getName());
                    ts(1000);
                    this.signal=1;
                    c1.signal();
                } finally {
                    cl.unlock();
                }
            }
        }
    }

其他concurrent工具类

Semaphore 信号灯

Semaphore提供了一个基于许可量的线程调度,它可以实现对有限资源的线程分配。每个线程可以申请(一个或多个)许可量,当当前许可量不足时,线程会阻塞;线程完成资源使用后,应该释放许可量,一般来说,应该释放与申请相等的许可量。注意,许可量不仅可以由线程申请和释放,也可以由外部管理,这样就提供了相当的弹性。
Semaphore的构造方法接收一个int作为许可量。当设置为1的时候,就实现了一个互斥锁。
Semaphore的构造方法允许传入一个标志以实现“公平”的分配,即先进先出FIFO的阻塞队列,但tryAcquire方法不实现此特性。

CyclicBarrier 循环路障

CyclicBarrier一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。
CyclicBarrier 支持一个可选的 Runnable 命令,在一组线程中的最后一个线程到达之后(但在释放所有线程之前),该命令只在每个屏障点运行一次。若在继续所有参与线程之前更新共享状态,此屏障操作 很有用。

CountDownLatch

CountDownLatch 实现了一个倒计锁,当倒计归零之前,所有被CountDownLatch所await的线程将阻塞。线程或者外部都可以调用CountDownLatch的countDown() 方法来减少计数。
当CountDownLatch的构造方法给定参数1时,就实现了一个命令式的线程控制开关,而且可以实现一对多管理。

Exchanger 交换

可以在对中对元素进行配对和交换的线程的同步点。每个线程将条目上的某个方法呈现给 exchange 方法,与伙伴线程进行匹配,并且在返回时接收其伙伴的对象。Exchanger 可能被视为 SynchronousQueue 的双向形式。

java.util.concurrent中的集合

Queue

多种阻塞队列,包括单向和双向的,线程同步,他们也可以像Java Collectyions Framework中的其他成员一样进行基本的操作,但通常会有效执行。但主要的增强在于引入了阻塞性方法put & take ,当无法完成操作时(例如放入满队列,从空队列取)会阻塞当前线程直至队列发生变化以完成操作。

DelayQueue

Delayed 元素的一个无界阻塞队列,只有在延迟期满时才能从中提取元素。其延时依赖于元素的Delayed实现。

SynchronousQueue

一种阻塞队列,其中每个插入操作必须等待另一个线程的对应移除操作 ,反之亦然。同步队列没有任何内部容量,甚至连一个队列的容量都没有。也就是说,队列直接阻塞put和take的线程直到配对而传递。
使用公平设置为 true 所构造的队列可保证线程以 FIFO 的顺序进行访问。

PriorityBlockingQueue

优先级队列的元素按照顺序进行排序,并且提供了阻塞获取操作。不保证具有同等优先级的元素的顺序。

CopyOnWrite*

一些变体容器,其中所有可变操作(add、set 等等)都是通过对底层进行一次新的复制来实现的。这一般需要很大的开销,但是当遍历操作的数量大大超过可变操作的数量时,这种方法可能比其他替代方法有效。

你可能感兴趣的:(学习,黑马)