java编程思想之并发

并发

并发的多面性

使用并发需要解决的问题有多个,而实现并发的方式也有多种,并且在这两者之间没有明显的映射关系(通常只具有模糊的界限)。

更快的执行
  • 并发通常是提升运行在单处理器上程序的性能。
  • 上下文切换(在单处理器,好像多线程并没用,增加了上线文切换)
  • 阻塞,特别是io的阻塞
  • 事实上,从性能上来看,如果没有任务会阻塞,那么在单处理器上使用并发就没有任何意义。
  • 进程相互隔离,它们不会互相干涉。使用并发对于进程,每个任务在其自己的地址空间执行,任务之间不可能相互干涉
改进代码设计
  • java的线程机制是抢占式的,这表示调度机制会周期性的中断线程,将一个线程切换到另一个线程,从而为每个线程都提供时间片。
  • 协作式系统的优势是双重的:上下文切换开销通常比抢占式系统低廉的多,并且对可以同时执行的线程数量理论上没有任何限制。当你处理大量的仿真元素时,这是一种理想的解决方案。
基本的线程机制
  • 一个线程就是在进程中的一个单一的顺序控制流。底层机制是切分cpu时间,但通常你不需要考虑它。
定义任务
  • 实现Runnable接口,并且重写run方法
  • 继承Thread接口
使用Executor
  • CachedThreadPool将会为每个任务都创建一个线程。
public class CachedThread {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            executorService.execute(() -> System.out.println(Thread.currentThread().getName()));
        }
    }
}
  • FixedThreadPool使用了优先的线程集来执行所提交的任务
public class CachedThread {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        for (int i = 0; i < 10; i++) {
            executorService.execute(() -> System.out.println(Thread.currentThread().getName()));
        }
    }
}
  • SingleThreadExecutor就像是线程数量为1的FixedThreadPool。在一些我们需要顺序执行任务的场景,这将是很好的一个选择。
public class CachedThread {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            executorService.execute(() -> System.out.println(Thread.currentThread().getName()));
        }
    }
}
从任务中产生返回值
  • 实现Callable接口,并且使用ExecutorService.submit()方法来提交它
ExecutorService executorService = Executors.newCachedThreadPool();
        Future<String> submit = executorService.submit(() -> {
            for (int i = 0; i < 10; i++) {
                Thread.sleep(100);
                System.out.println("a" + i);
            }
            return "a";
        });

使用submit.get()将会阻塞线程,使用isDone()方法将会检查该任务是否执行完毕。

休眠
  • sleep()方法
优先级
  • 你可以通过getPriority()获取当前线程的优先级,并且在任何时刻使用setPriority()来修改它
让步
  • yield()方法
后台线程
  • daemon 通过调用isDaemon方法来确定线程是否是一个后台线程,如果是一个后台线程,那么它创建的任何进程都会被自动设置为后台线程。
术语
  • 执行的任务和驱动它的程序有一个明显的差异,这个差异在java內库中极为明显,因为你对thread没有任何的实际控制权。
  • 在物理上,创建线程可能会代价高昂,因此你必须保存并管理它们
加入一个线程
  • join方法,如果在某个线程调用此方法,那么该线程将会被挂起,直到插入者执行完才继续执行。
    Thread t1 = new Thread() {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t1====" + i);
            }
        }
    };
    Thread t2 = new Thread() {
        @Override
        public void run() {
            try {
                for (int i = 0; i < 10; i++) {
                    Thread.sleep(10);
                    if (i == 5) {
                        t1.join();
                        System.out.println("t2====" + i);
                    }
                }
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    };
    t1.start();
    t2.start();
捕获异常
  • 由于线程的本质,使得你不能捕获从线程中逃逸你异常。
public class Main {

    public static void main(String[] args) {

        try {
            ExceptionThread exceptionThread = new ExceptionThread();
            exceptionThread.start();
        } catch (Exception e) {
            // 此处不能捕获异常
            System.out.println(111);
        }

    }
}

class ExceptionThread extends Thread {
    @Override
    public void run() {
        try {
            throw new RuntimeException("111");
        } catch (RuntimeException e) {
            // 此处可以捕获异常
            System.out.println(444);
        }
    }
}

共享受限资源

不正确的访问资源
  • 多线程访问同一个资源出现的问题,例如
public class Main {

    public static void main(String[] args) {
        new ShareThread().start();
        new ShareThread().start();
    }
}

class ShareThread extends Thread {
    private static int a = 100;
    @Override
    public void run() {
        while (a != 0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            a--;
            System.out.println(a);
        }
    }
}
  • 执行上述的代码出线负数
解决共享资源竞争问题
  • 加锁,在需要同步的地方加上锁,这是最保险的方式,关键字synchronized
public class Main {

    public static void main(String[] args) {
        new ShareThread().start();
        new ShareThread().start();
    }
}

class ShareThread extends Thread {
    private static int a = 100;
    @Override
    public void run() {
        synchronized (ShareThread.class){
            while (a != 0) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                a--;
                System.out.println(a);
            }

        }
    }
}
  • 使用并发时,将域设置为private是非常重要的,否则synchronized就不能阻止其他地方访问。
  • 针对每一个类,都以一个锁(作为每个类的Class对象的一部分)
  • 同比不规则:如果你正在写一个变量,它可能接下来被另一个线程读取,或者正在读取上一次已经被另一个线程修改过的变量,那么你必须使用同步,并且读写线程必须使用相同的监视锁同步。
使用显式的Lock对象
  • lock和unlock方法
public class Main {
    public static void main(String[] args) {
        new ShareThread().start();
        new ShareThread().start();
        new ShareThread().start();
        new ShareThread().start();

    }
}

class ShareThread extends Thread {
    private volatile static int a = 100;
    static Lock lock = new ReentrantLock();

    @Override
    public void run() {
        try {
            lock.lock();
            while (a != 0) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                a--;
                System.out.println(a);
            }
        } finally {
            lock.unlock();
        }
    }
}
  • 使用该方法的好处在于,我们对于同步的控制能力增强了,在finally的地方,我们可以自己释放锁。
原子性与易变性
  • 原子性是可以应用除了double和long之外的所有基本类型操作。
  • volatile关键字确保了变量的可视性,如果你讲一个域声明为volatile,那么只要对这个域进行了写操作,那么所有的读操作都可以看到这个更改。即使使用了本地缓存,情况也会如此,volatile域会被立即写入主内存中,而读操作就发生在主内存中。

原子类

  • AtomicInteger AtomicLong ….
临界区
  • 很多时候我们需要同步的并不是所有的线程,而是共享同一类资源的线程进行并发访问。例如,用户修改自己的金额,我们只需要对用户加锁就行了。
线程本地变量存储
  • ThreadLocal的使用

终结任务

线程的状态
  • 新建,当线程被创建时,它只会短暂的处于这种状态。此时它已经分配到了必须的系统资源,并执行了初始化
  • 就绪,在这种状态下,只要调度器把时间片分配给线程,线程就可以运行。
  • 阻塞:线程能够运行,但是有某个条件阻止它的运行,当线程处于阻塞状态时,调度器将忽略线程,不会分配线程任何cpu时间。
  • 死亡:处于死亡或终止状态的线程将不再是可调度的,并且再也不会得到cpu时间
进入阻塞状态
  • sleep
  • 通过wait方法将线程挂起
  • 任务在等待某个输入、输出完成
  • 调用方法的锁被另外线程使用。
中断
  • Thread.interrupt()方法中断
public class InterruptTest {
    public static void main(String[] args) {
        ExecutorService service = Executors.newSingleThreadExecutor();
        service.execute(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(10);
                    System.out.println(i);
                    if (i == 5) {
                        List<Runnable> runnables = service.shutdownNow();
                        System.out.println(runnables);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        service.execute(()->{
            System.out.println("执行");
        });
        service.execute(()->{
            System.out.println("任务3");
        });
    }
}
  1. 中断并不能立即打断程序运行
  2. 停止接收外部的任务(第二个任务并不能运行)
  3. 忽略正在等在执行的任务
  4. 返回未执行的任务列表

    • 被reentranlock阻断的任务具有被中断的能力,这个方式并不实用
检查中断

当你在线程上调用interrupt方法时,中断发生唯一时刻是任务要进入道阻塞操作中,或者是已经在阻塞操作内部时.

线程之间的协作

wait()和notifyAll()
public class WaitThreadTest {
    public static Object lock = new Object();
    public static void main(String[] args) {
        new Thread() {
            @Override
            public void run() {
                synchronized (lock) {
                    for (int i = 0; i < 100; i++) {
                        if (i % 2 == 0) {
                            try {
                                lock.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            System.out.println(i);
                        }
                        lock.notifyAll();
                    }
                }
            }
        }.start();
        new Thread() {
            @Override
            public void run() {
                synchronized (lock) {
                    for (int i = 0; i < 100; i++) {
                        if (i % 2 != 0) {
                            try {
                                lock.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            System.out.println(i);
                        }
                        lock.notifyAll();
                    }
                }
            }
        }.start();
    }
}
  • 此方法有巨大的弊端,他每次都必须唤醒所有的线程
  • 如果是三个或者以上的线程通信这个并不合适
  • 调用该方法的对象必须是锁对象
  • 在多个任务实用同一个锁的时候,多个任务各自等待锁的释放
  • 惯用的方法实用while就可以编写这种变化
notify和notifyAll的区别
  • 实用notify唤醒众多任务中的一个
  • notifyAll唤醒所有wait的任务
释放锁的条件
  • 占有锁的线程执行完代码,然后释放对锁的占有
  • 线程执行发生异常,此时jvm将会自动释放锁
  • 调用wait方法在等待的时候立即释放锁,方便其他线程使用锁
使用显式的lock和condition对象
  • 需求:我们在0到100中,我们依次输出 我和你
public class ConditionTest {
    static Lock lock = new ReentrantLock();

    static Condition c0 = lock.newCondition();

    static Condition c1 = lock.newCondition();

    static Condition c2 = lock.newCondition();

    volatile static int i = 0;

    public static void main(String[] args) {
        new Thread(() -> {
            try {
                lock.lock();
                for (; i < 100; ) {
                    int value = i % 3;
                    if (value != 0) {
                        c0.await();
                    }
                    System.out.println("我");
                    i++;
                    c1.signal();
                }
            } catch (Exception e) {
                System.out.println(e);
            } finally {
                lock.unlock();
            }
        }).start();
        new Thread(() -> {
            try {
                lock.lock();
                for (; i < 100; ) {
                    int value = i % 3;
                    if (value != 1) {
                        c1.await();
                    }
                    System.out.println("和");
                    i++;
                    c2.signal();
                }
            } catch (Exception e) {
                System.out.println(e);
            } finally {
                lock.unlock();
            }
        }).start();
        new Thread(() -> {
            try {
                lock.lock();
                for (; i < 100; ) {
                    int value = i % 3;
                    if (value != 2) {
                        c2.await();
                    }
                    System.out.println("你");
                    i++;
                    c0.signal();
                }
            } catch (Exception e) {
                System.out.println(e);
            } finally {
                lock.unlock();
            }
        }).start();
    }

}
多线程死锁
  • 多线程通信的时候采用了嵌套synchronized或者其他情况,只要是一个线程没有执行完代码,被阻塞住了,就非常容易产生死锁的情况
public class DealLockTest {
    static String a = "1";
    static String b = "2";
    static String c = "3";

    public static void main(String[] args) {
        new Thread(() -> {
            while (true) {
                synchronized (a) {
                    System.out.println("拿到a等待b");
                    synchronized (b) {
                        System.out.println("a和b都拿到了开始吃饭");
                    }
                }

            }
        }).start();

        new Thread(() -> {
            while (true) {
                synchronized (c) {
                    System.out.println("拿到c等待a");
                    synchronized (a) {
                        System.out.println("c和a都拿到了开始吃饭");
                    }
                }
            }
        }).start();

        new Thread(() -> {
            while (true) {
                synchronized (b) {
                    System.out.println("拿到b等待c");
                    synchronized (c) {
                        System.out.println("b和c都拿到了开始吃饭");
                    }
                }
            }
        }).start();

    }

}
  • 在上述例子中, 使用了嵌套锁,这样的锁不能及时的释放,而其他方法又使用了这个锁
新类库的构件
CountDownLatch
  • 需求:假设我们主线程需要等待两个线程执行完毕才执行下面的方法,这个时候我们可以考虑使用CountDownLatch

  • 它用来同步一个或者多个任务,强制它们等待有其他任务执行的一组操作完成

public class CountDownTest {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(2);
        new Thread(() -> {
            System.out.println("thread1");
            latch.countDown();
        }).start();
        new Thread(() -> {
            System.out.println("thread2");
            latch.countDown();
        }).start();
        latch.await();
        System.out.println(latch.getCount());
    }
}
CyclicBarrier
  • 同步多个线线程的操作,假如有多个任务它们需要执行的时候进行等待,那么此方法适合
public class CyclicBrarric {
    public static void main(String[] args) throws InterruptedException {
        int n = 4;
        CyclicBarrier barrier = new CyclicBarrier(n);
        for (int i = 0; i < n; i++) {
            new Thread(() -> {
                try {
                    System.out.println("回教室");
                    barrier.await();
                    System.out.println("坐下");
                    barrier.await();
                    System.out.println("听课");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}
  • 其中的await方法可以设置等待时间,如果超过await方法设置的等待时间,那么线程组也会继续执行下去。
public class CyclicBrarric {
    public static void main(String[] args) throws InterruptedException, BrokenBarrierException {
        int n = 4;
        CyclicBarrier barrier = new CyclicBarrier(n);
        for (int i = 0; i < n; i++) {
            new Thread("t" + i) {
                @Override
                public void run() {
                    try {
                        System.out.println("回教室");
                        barrier.await();
                        System.out.println("坐下");
                        if (!Thread.currentThread().getName().equals("t3")) {
                            Thread.sleep(2000);
                        }
                        barrier.await(1, TimeUnit.SECONDS);
                        System.out.println("听课");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    } catch (TimeoutException e) {
                        e.printStackTrace();
                    }
                }
            }.start();
        }
    }
}
  • 注意:这个await的时间是从第一个执行到这里的线程开始计时的,如果程序执行超过了这个时间,所有线程都抛出异常,进行终止
DelayQueue
  • 这是一个无界的BlockingQueue,用于放置视线了Delayed接口的对象,其中的对象只能在其到期才能取走。这种队列是有序的,即队头对象的延迟到期时间最长。

public class DelayTest {

    public static void main(String[] args) throws InterruptedException {
        DelayQueue<DelayTask> queue = new DelayQueue<>();

        queue.add(new DelayTask("1", 1000L, TimeUnit.MILLISECONDS));
        queue.add(new DelayTask("2", 2000L, TimeUnit.MILLISECONDS));
        queue.add(new DelayTask("3", 3000L, TimeUnit.MILLISECONDS));

        while (!queue.isEmpty()) {
            try {
                DelayTask task = queue.take();
                System.out.println(task.name + ":" + System.currentTimeMillis());

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class DelayTask implements Delayed {
    // 名字
    String name;
    // 延迟执行时间
    Long time;
    //执行时间单位
    TimeUnit timeUnit;

    DelayTask(String name, Long time, TimeUnit timeUnit) {
        this.name = name;
        this.time = time + System.currentTimeMillis();
        this.timeUnit = timeUnit;
    }
    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert(time - System.currentTimeMillis(), timeUnit);
    }
    @Override
    public int compareTo(Delayed o) {
        long value = this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS);
        if (value > 0) {
            return 1;
        } else if (value < 0) {
            return -1;
        }
        return 0;

    }
}
PriortyBlockingQueue
  • 这是一个很基础的优先级队列,它具有可阻塞的读取操作。
public class PriorityBlockingQueueTest {

    public static void main(String[] args) {
        PriorityBlockingQueue<Integer> queue =new PriorityBlockingQueue<>(10, (o1, o2) -> o2-o1);
        queue.add(1);
        queue.add(2);
        queue.add(3);
        System.out.println(queue.poll());
        System.out.println(queue.poll());
        System.out.println(queue.poll());
        System.out.println(queue.poll());
    }
}
ScheduledThreadPoolExecutor对象
  • 延迟任务
public class ScheduledThreadTest {
    public static void main(String[] args) {
        ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(2);
        executor.schedule(()->{
            System.out.println(111);
        },5000, TimeUnit.MILLISECONDS);
        System.out.println(2222);
    }
}
Semaphore
  • Semaphore也叫做信号量,可以用来控制同时访问特定资源的线程的数量通过协调各个线程,以保证合理的使用资源

  • 访问特定资源前,必须使用acquire方法获得许可,如果许可数量为0,该线程一直阻塞,直到获得许可为止

  • 访问资源后,使用release释放资源
public class SemaphoreTest {

    public static void main(String[] args) {
        new SemRunnable().start();
        new SemRunnable().start();
        new SemRunnable().start();
        new SemRunnable().start();
        new SemRunnable().start();
    }
}


class SemRunnable extends Thread {
    static Semaphore semaphore = new Semaphore(1);
    @Override
    public void run() {
        try {
            semaphore.acquire();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "执行等待");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "执行完毕");
        semaphore.release();
    }
}
  • 可以控制同时执行的有多少个线程,如果设置为1,那么相当于使用synchronized
Exchanger
  • 如果两个线程需要进行数据交换,那么Exchanger将是一个很好的选择
public class ExchangerTest {

    public static void main(String[] args) {
        Exchanger<String> exchanger = new Exchanger<>();
        new Thread(() -> {
            try {
                String a = "a";
                String exchange = exchanger.exchange(a);
                System.out.println("a"+exchange);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {
            try {

                String b = "b";
                String exchange = exchanger.exchange(b);
                System.out.println(exchange);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}
  • 上述代码的字符串将会进行交换
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值