JUC学习笔记

尚硅谷JUC源码讲授实战教程完整版(java juc线程精讲)

1.volatile 关键字与内存可见性

当程序运行,JVM会为每一个执行任务的线程分配独立的缓存用于提高效率。

内存可见性问题是,当多个线程操作共享数据时,彼此不可见。

volatile 关键字:当多个线程进行操作共享数据时,可以保证内存中的数据可见。相较于synchronized是一种较为轻量级的同步策略。

volatile 不具备"互斥性",也不能保证变量的"原子性"

public class VolatileTest {

    public static void main(String[] args) {
        ThreadDemo threadDemo = new ThreadDemo();
        new Thread(threadDemo).start();

        while (true) {
//            synchronized (threadDemo) {
                if (threadDemo.isFlag()) {
                    System.out.println("---------");
                    break;
                }
//            }
        }
    }
}

class ThreadDemo implements Runnable {

//    private boolean flag = false;
    private volatile boolean flag = false;

    @Override
    public void run() {
        try {
            Thread.sleep(200);
        } catch (Exception e) {
        }
        flag = true;
        System.out.println("flag=" + isFlag());
    }

    public boolean isFlag() {
        return flag;
    }
}

2.原子变量与CAS算法

原子变量:jdk1.5后java.util.concurrent.atomic包下提供了常用的原子变量(其中有AtomicInteger)
1、volatile保证内存可见性
2、CAS (Compare-And-Swap)算法保证数据的原子性CAS算法是硬件对于并发操作共享数据的支持
CAS包含了三个操作数:内存值V、预估值A、更新值B。当且仅当V == A 时,V = B(将B赋值给A)。否则,将不做任何操作

public class AtomicTest {

    public static void main(String[] args) {
        AtomicDemo atomicDemo = new AtomicDemo();
        for (int i = 0; i < 10; i++) {
            new Thread(atomicDemo).start();
        }
    }

}

class AtomicDemo implements Runnable {

//    private int serialNum = 0;
    private AtomicInteger serialNum = new AtomicInteger();

    @Override
    public void run() {

        try {
            Thread.sleep(1000);
        } catch (Exception e) {
        }

        System.out.println(Thread.currentThread().getName() + ":" + getSerialNum());
    }

    public int getSerialNum() {
//        return serialNum++;
        return serialNum.getAndIncrement();
    }
}

3.模拟CAS算法

public class CompareAndSwapTest {

    public static void main(String[] args) {
        final CompareAndSwap cas = new CompareAndSwap();

        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    int expectedValue = cas.get();
                    boolean flag = cas.compareAndSet(expectedValue, (int)(Math.random() * 100));
                    System.out.println(flag);
                }
            }).start();
        }
    }
}

class CompareAndSwap {
    private int value;

    // 获取内存值
    public synchronized int get() {
        return value;
    }

    // 比较
    public synchronized int compareAndSwap(int expectedValue, int newValue) {
        int oldValue = value;

        if (oldValue == expectedValue) {
            this.value = newValue;
        }

        return oldValue;
    }

    // 设置
    public synchronized boolean compareAndSet(int expectedValue, int newValue) {
        return expectedValue == compareAndSwap(expectedValue, newValue);
    }
}

4.同步容器类ConcurrentHashMap

  • Java 5.0在java.util.concurrent包中提供了多种并发容器类来改进同步容器的性能。
  • ConcurrentHashMap同步容器类是Java 5增加的一个线程安全的哈希表。
    对于多线程的操作,介于HashMap 与 Hashtable之间。
    java1.8之前内部采用“锁分段”机制替代 Hashtable的独占锁。进而提高性能,1.8之后是CAS
  • 此包还提供了设计用于多线程上下文中的collection实现:
    ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet、CopyOnWriteArrayList和CopyOnWriteArraySet。
    当期望许多线程访问一个给定collection时,ConcurrentHashMap通常优于同步的HashMap,
    ConcurrentSkipListMap通常优于同步的TreeMap。
    当期望的读数和遍历远远大于列表的更新数时,CopyOnWriteArrayList优于同步的ArrayList。
/**
 * CopyOnWriteArrayList/CopyOnWriteArraySet : 写入并复制
 * 注意:不适合添加操作多的场景,因为每次添加都会进行复制,开销大,效率低
 *      适合并发迭代操作多的场景
 */
public class CopyOnWriteArrayListTest {

    public static void main(String[] args) {
        HelloThread ht = new HelloThread();

        for (int i = 0; i < 10; i++) {
            new Thread(ht).start();
        }
    }

}

class HelloThread implements Runnable {

//    private static List<String> list = Collections.synchronizedList(new ArrayList<>());
    private static CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();

    static {
        list.add("AA");
        list.add("BB");
        list.add("CC");
    }

    @Override
    public void run() {
        Iterator<String> it = list.iterator();

        while (it.hasNext()) {
            System.out.println(it.next());
            // 使用的是List<String>时 此处会抛出java.util.ConcurrentModificationException异常
            list.add("AA");
        }
    }
}

5.CountDownLatch闭锁

  • Java 5.0在java.util.concurrent包中提供了多种并发容器类来改进同步容器的性能。

  • CountDownLatch一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。

  • 闭锁可以延迟线程的进度直到其到达终止状态,闭锁可以用来确保某些活动直到其他活动都完成才继续执行:

    确保某个计算 在其需要的所有资源都被初始化之后 才继续执行;
    确保某个服务 在其依赖的所有其他服务都已经启动之后 才启动;
    等待直到某个操作所有参与者都准备就绪再继续执行。

/**
 * CountDownLatch闭锁:在完成某些运算时,只有其他线程的运算全部完成后,当前运算才继续执行
 */
public class CountDownLatchTest {

    public static void main(String[] args) {
        // 后面开了5个线程,此处就传入计数5
        CountDownLatch countDownLatch = new CountDownLatch(5);
        LatchDemo latchDemo = new LatchDemo(countDownLatch);

        long start = System.currentTimeMillis();

        for (int i = 0; i < 5; i++) {
            new Thread(latchDemo).start();
        }


        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 当计数减到0时才执行
        long end = System.currentTimeMillis();
        System.out.println("耗费时间为:" + (end - start));
    }

}

class LatchDemo implements Runnable {
    public CountDownLatch countDownLatch;

    public LatchDemo(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }

    @Override
    public void run() {
        synchronized (this) {
            try {
                for (int i = 0; i < 50000; i++) {
                    if (i % 2 == 0) {
                        System.out.println(i);
                    }
                }
            }finally {
                countDownLatch.countDown();
            }
        }
    }
}

6.实现Callable接口

/**
 * 创建线程方式三:实现Callable接口
 * 相较于实现Runnable接口的方式,方法可以有返回值,并且可以抛出异常
 */
public class CallableTest {
    public static void main(String[] args) {
        CallableImpl callableImpl = new CallableImpl();

        // 执行Callable方式,需要FutureTask实现类的支持,用于接收运算结果。FutureTask是Future接口的实现类
        // 还有另外一个构造方法是FutureTask(Runnable runnable, V result)
        FutureTask<Integer> result = new FutureTask<>(callableImpl);

        new Thread(result).start();

        // 接收线程运算后的结果
        try {
            Integer sum = result.get(); // futureTask也可用于闭锁
            System.out.println(sum);
            System.out.println("---------当分线程执行完 get()拿到结果了才执行此处---------");
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

    }
}

class CallableImpl implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i < 100; i++) {
            sum += i;
        }
        return sum;
    }
}

7.Lock同步锁

—、用于解决多线程安全问题的方式:
1.同步代码块(synchronized隐式锁)
2.同步方法(synchronized隐式锁)
3.同步锁Lock(jdk 1.5后出现,是个显式锁,需要通过lock()方法上锁,必须通过unlock()方法进行释放锁,所以有一定的风险,比如锁未成功释放)

public class LockTest {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();

        new Thread(ticket, "1号窗口").start();
        new Thread(ticket, "2号窗口").start();
        new Thread(ticket, "3号窗口").start();
    }
}

class Ticket implements Runnable {

    private int ticketNum = 100;
    private Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            lock.lock();    // 上锁
            try {
                if (ticketNum > 0) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "完成售票,余票为:" + --ticketNum);
                }
            } finally {
                lock.unlock();  // 释放锁
            }
        }
    }
}

8.生产者消费者案例-虚假唤醒

如何用Lock同步锁实现等待唤醒机制,也就是像synchronized关键字的wait()和notify()的等待唤醒机制

8.1未使用等待唤醒机制出现的问题

public class ProductorAndConsumerTest {

    public static void main(String[] args) {
        Clerk clerk = new Clerk();
        Productor productor = new Productor(clerk);
        Consumer consumer = new Consumer(clerk);

        new Thread(productor, "生产者A").start();
        new Thread(consumer, "消费者B").start();
    }
}

// 店员
class Clerk {
    private int productNum = 0;

    // 进货
    public synchronized void get() {
        if (productNum >= 5) {
            System.out.println("商品仓位已满!");
//            try {
//                this.wait();
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
        } else {
            System.out.println(Thread.currentThread().getName() + "进货:" + ++productNum);
//            this.notifyAll();
        }
    }

    // 卖货
    public synchronized void sale() {
        if (productNum <= 0) {
            System.out.println("商品已售罄!");
//            try {
//                this.wait();
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
        } else {
            System.out.println(Thread.currentThread().getName() + "售出:" + --productNum);
//            this.notifyAll();
        }
    }
}

// 生产者
class Productor implements Runnable {

    private Clerk clerk;

    public Productor(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            clerk.get();
        }
    }
}

// 消费者
class Consumer implements Runnable {

    private Clerk clerk;

    public Consumer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            clerk.sale();
        }
    }
}

即使仓位满了,生产者还是不停地进货;即使商品售罄了,消费者还是不停地想要去消费、

在这里插入图片描述

8.2使用等待唤醒机制

将8.1中注释的代码放开、此时运行结果都是有效的数据、

在这里插入图片描述

8.3问题修正1

8.2的等待唤醒机制有些问题、
当把仓位改为1,并给生产者加0.2秒的延迟、

// 进货
public synchronized void get() {
    if (productNum >= 1) {		// 把仓位改为1
        System.out.println("商品仓位已满!");
        try {
            this.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    } else {
        System.out.println(Thread.currentThread().getName() + "进货:" + ++productNum);
        this.notifyAll();
    }
}

@Override
public void run() {
    for (int i = 0; i < 10; i++) {
        try {
            Thread.sleep(200);	// 给生产者加0.2秒的延迟
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        clerk.get();
    }
}

执行程序,程序未能停止、

在这里插入图片描述原因:
生产者有延迟,所以生产者生产得慢,消费者消费得快、
假设消费者线程循环还剩最后一次,生产者线程循环还剩最后两次,
消费者进入sale(),productNum=0,然后wait()等待唤醒、
生产者倒数第2次循环,进入get(),走进else使得productNum=1,然后notifyAll()、
此时消费者最后一次循环在等待被唤醒的线程 和 生产者 争抢资源,
假设消费者抢到,则消费者从wait处继续往下执行完成,并没有去消费productNum,因此productNum仍然为1
此时最后一个生产者进入get(),满足条件productNum >= 1并wait(),此时不会再被唤醒(执行结果最后一句是“商品仓位已满!”)、

解决:
将唤醒语句拿出来,不再放在else语句里

// 进货
public synchronized void get() {
    if (productNum >= 1) {
        System.out.println("商品仓位已满!");
        try {
            this.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    //        else {
    System.out.println(Thread.currentThread().getName() + "进货:" + ++productNum);
    this.notifyAll();
    //        }
}

// 卖货
public synchronized void sale() {
    if (productNum <= 0) {
        System.out.println("商品已售罄!");
        try {
            this.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    //        else {
    System.out.println(Thread.currentThread().getName() + "售出:" + --productNum);
    this.notifyAll();
    //        }
}

执行结果

在这里插入图片描述

8.4问题修正2

8.3问题修正后 若有多个生产者和消费者 仍然会出现问题、

在这里插入图片描述在这里插入图片描述在某些版本中 中断和虚假唤醒是可能的 因此方法应该总是在循环中使用。

解决:
将进货和卖货方法中的if改为while,这样被唤醒的线程需重新判断productNum的值、

// 进货
public synchronized void get() {
//	if (productNum >= 1) {
	while (productNum >= 1) {   // 为避免虚假唤醒问题,应总是使用在循环中

// ------------------

// 卖货
public synchronized void sale() {
//	if (productNum <= 0) {
	while (productNum <= 0) {

9.Condition控制线程通信

  • Condition接口描述了可能会与锁有关联的条件变量。这些变量在用法上与使用Object.wait访问的隐式监视器类似,但提供了更强大的功能。
    特别注意:单个Lock 可能与多个Condition对象关联。为避免兼容性问题,Condition方法的名称与对应的Object版本中的不同。
  • 在Condition对象中,与wait()、notify()和notifyAll()方法对应的分别是await()、signal()和 signalAll()。
  • Condition实例实质上被绑定到一个锁上。要为特定Lock实例获得Condition实例,请使用其newCondition()方法。
// 店员
class ClerkForLock {
    private int productNum = 0;

    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    // 进货
    public void get() {
        lock.lock();

        try {
            while (productNum >= 1) {
                System.out.println("商品仓位已满!");
                try {
//                    this.wait();
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + "进货:" + ++productNum);
//            this.notifyAll();
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }

    // 卖货
    public void sale() {
        lock.lock();

        try {
            while (productNum <= 0) {
                System.out.println("商品已售罄!");
                try {
//                    this.wait();
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + "售出:" + --productNum);
//            this.notifyAll();
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }
}

10.线程按序交替

编写一个程序,开启3个线程,这三个线程的ID分别为A、B、C,每个线程将自己的ID打印10遍,要求输出的结果必须按顺序显示。
如:ABCABCABC…依次递归

public class ABCAlternateTest {

    public static void main(String[] args) {
        AlternateDemo alternateDemo = new AlternateDemo();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    alternateDemo.loopA(i);
                }
            }
        }, "A").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    alternateDemo.loopB(i);
                }
            }
        }, "B").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    alternateDemo.loopC(i);
                    System.out.println("---------");
                }
            }
        }, "C").start();
    }

}

class AlternateDemo {
    private int threadNum = 1;  // 当前正在执行线程的一个标记

    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    
//	new Thread(() -> {for (int i = 0; i < 10; i++) alternateDemo.loopA(i);}, "A").start();
//	new Thread(() -> {for (int i = 0; i < 10; i++) alternateDemo.loopB(i);}, "B").start();
//	new Thread(() -> {for (int i = 0; i < 10; i++) alternateDemo.loopC(i);}, "C").start();

	// 以下三块可以使用上方被注释的代码的写法,上面是Lambda表达式,下面是匿名内部类
    public void loopA(int loopIndex) {
        lock.lock();

        try {
            if (threadNum != 1) {
                condition1.await();
            }

            System.out.println(Thread.currentThread().getName() + "\t" + threadNum + "\t" + loopIndex);
            threadNum = 2;
            condition2.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void loopB(int loopIndex) {
        lock.lock();

        try {
            if (threadNum != 2) {
                condition2.await();
            }

            System.out.println(Thread.currentThread().getName() + "\t" + threadNum + "\t" + loopIndex);
            threadNum = 3;
            condition3.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void loopC(int loopIndex) {
        lock.lock();

        try {
            if (threadNum != 3) {
                condition3.await();
            }

            System.out.println(Thread.currentThread().getName() + "\t" + threadNum + "\t" + loopIndex);
            threadNum = 1;
            condition1.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

}

11.ReadWriteLock读写锁

A ReadWriteLock maintains a pair of associated locks, one for read-only operations and one for writing. The read lock may be held simultaneously by multiple reader threads, so long as there are no writers. The write lock is exclusive.

ReadWriteLock 维护一对关联的锁,一个用于只读操作,一个用于写入。只要没有写者,读锁就可以被多个读者线程同时持有。写锁是独占的。

// 写写 读写 需要互斥;读读 不需要互斥
public class ReadWriteLockTest {
    public static void main(String[] args) {
        ReadWriteLockDemo readWriteLockDemo = new ReadWriteLockDemo();

        new Thread(new Runnable() {
            @Override
            public void run() {
                readWriteLockDemo.set((int)(Math.random() * 101));
            }
        }, "写线程").start();

        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    readWriteLockDemo.get();
                }
            }).start();
        }

    }
}

class ReadWriteLockDemo {
    private int num = 0;
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    // 读操作
    public void get() {
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + " : " + num);
        } finally {
            readWriteLock.readLock().unlock();
        }
    }

    // 写锁
    public void set(int num) {
        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + " write " + num);
            this.num = num;
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }
}

在这里插入图片描述

12.线程八锁

  1. 两个普通同步方法 同一对象 两个线程 标准打印 // 12或21随机 谁先抢到锁就执行谁
  2. getOne()里新增sleep,同一对象 两个线程 打印 // 12或21随机 sleep不释放锁
  3. 新增普通方法getThree(),同一对象 三个线程 (getOne()里有sleep) 打印 // 3不需要管谁正持有锁就可以进入方法体打印,1和2随机 看看123谁先抢到CPU时间片进入到对应方法体
  4. 两个普通同步方法 两个对象 两个线程 (getOne()里有sleep) 打印 // 21
  5. getOne()为static 同一对象 两个线程 (getOne()里有sleep) 打印 // 21 类锁和对象锁的区别
  6. getOne()和getTwo()均为static 同一对象 两个线程 (getOne()里有sleep) 打印 // 12 类锁和对象锁的区别
  7. 只有getOne()为static, 两个对象 两个线程 (getOne()里有sleep) 打印 // 21
  8. getOne()和getTwo()均为static 两个对象 两个线程 (getOne()里有sleep) 打印 // 12

非静态方法的锁默认为this, 静态方法的锁为对应的Class实例

public class Thread8MonitorTest {

    public static void main(String[] args) {
        Number number = new Number();
        Number number2 = new Number();

        new Thread(new Runnable() {
            @Override
            public void run() {
                number.getOne();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
//                number.getTwo();
                number2.getTwo();
            }
        }).start();

//        new Thread(new Runnable() {
//            @Override
//            public void run() {
//                number.getThree();
//            }
//        }).start();
    }
}

class Number {

//    public synchronized void getOne() {
    public static synchronized void getOne() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("1");
    }

//    public synchronized void getTwo() {
    public static synchronized void getTwo() {
        System.out.println("2");
    }

    public void getThree() {
        System.out.println("3");
    }
}

13.线程池

ThreadPoolExecutor类API文档中:

To be useful across a wide range of contexts, this class provides many adjustable parameters and extensibility hooks.
However, programmers are urged to use the more convenient Executors factory methods
Executors.newCachedThreadPool() (unbounded thread pool, with automatic thread reclamation),
Executors.newFixedThreadPool(int) (fixed size thread pool)
Executors.newSingleThreadExecutor() (single background thread),
that preconfigure settings for the most common usage scenarios. Otherwise, use the following guide when manually configuring and tuning this class:

为了在各种情况下都能发挥作用,这个类提供了许多可调整的参数和可扩展的钩子。
然而,建议程序员使用更方便的Executors工厂方法
Executors.newCachedThreadPool()(无界线程池,自动回收线程),
Executors.newFixedThreadPool(int)(固定大小的线程池),
Executors.newSingleThreadExecutor()(单后台线程),
它们为最常见的使用场景预先配置了设置。否则,在手动配置和调整该类时,请使用以下指南。

/**
 * 一、线程池:提供了一个线程队列,队列中保存着所有等待状态的线程。避免了创建与销毁额外开销,提高了响应的速度。
 * 二、线程池的体系结构:
 * java.util.concurrent.Executor:负责线程的使用与调度的根接口
 * 	|--ExecutorService子接口:线程池的主要接口
 * 		|--ThreadPoolExecutor线程池的实现类
 * 		|--ScheduledExecutorService子接口:负责线程的调度
 * 			|--ScheduledThreadPoolExecutor:继承ThreadPoolExecutor,实现 ScheduledExecutorService
 * 三、工具类:Executors
 * ExecutorService newFixedThreadPool(int):创建固定大小的线程池
 * ExecutorService newCachedThreadPool():缓存线程池,线程池的数量不固定,可以根据需求自动的更改数量
 * ExecutorService newSingleThreadExecutor():创建单个线程池。线程池中只有一个线程
 * ScheduledExecutorService newScheduledThreadPool():创建固定大小的线程,可以延迟或定时的执行任务
 */
public class ThreadPoolTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 1.创建线程池
        ExecutorService pool = Executors.newFixedThreadPool(5);

        ThreadPoolDemo threadPoolDemo = new ThreadPoolDemo();

        // 2.为线程池中的线程分配任务 Callable
        List<Future<Integer>> resultList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            Future<Integer> future = pool.submit(new Callable<Integer>() {
                int sum = 0;

                @Override
                public Integer call() throws Exception {
                    int i = 0;
                    for (int i1 = 0; i1 <= 100; i1++) {
                        sum += i1;
                    }
                    return sum;
                }
            });
            resultList.add(future);
        }

        for (Future<Integer> future : resultList) {
            System.out.println(future.get());
        }

//        // 2.为线程池中的线程分配任务 Runnable
//        for (int i = 0; i < 10; i++) {
//            pool.submit(threadPoolDemo);
//        }

        // 3.关闭线程池
        // shutdown等待线程池中所有的任务都完成之后再关闭,并且不再接收新的任务;shutdownNow是立即结束,不管任务是否完成
        pool.shutdown();
    }
}

class ThreadPoolDemo implements Runnable {
    private int i = 0;

    @Override
    public void run() {
        while (i < 20) {
            System.out.println(Thread.currentThread().getName() + " : " + i++);
        }
    }
}

14.线程调度

public class ScheduledThreadPoolTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);

        for (int i = 0; i < 5; i++) {
            Future<Integer> future = pool.schedule(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    int num = new Random().nextInt(100); // 100以内的随机数
                    System.out.println(Thread.currentThread().getName() + " : " + num);
                    return num;
                }
            }, 3, TimeUnit.SECONDS);
            System.out.println(future.get());
        }

        pool.shutdown();
    }
}

15.ForkJoinPool分支/合并框架 工作窃取

jdk1.7之后出现的。Java8对其进行了改进、

Fork/Join框架与线程池的区别

采用“工作窃取”模式(work-stealing):当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中。

相对于一般的线程池实现,fork/join框架的优势体现在对其中包含的任务的处理方式上。
在一般的线程池中,若一个线程正在执行的任务由于某些原因无法继续运行,则该线程会处于等待状态。
而在fork/join框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行,那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行。这种方式减少了线程的等待时间,提高了性能。

public class ForkJoinPoolTest {
    public static void main(String[] args) {
        Instant start = Instant.now();

        ForkJoinPool pool = new ForkJoinPool();

        ForkJoinTask<Long> task = new ForkJoinSumCalculate(0L, 10000000000L);

        Long sum = pool.invoke(task);
        System.out.println(sum);

        Instant end = Instant.now();
        System.out.println("耗费时间:" + Duration.between(start, end).toMillis());
    }

    /**
     * 普通for循环的方式
     */
    @Test
    public void Test() {
        Instant start = Instant.now();

        long sum = 0L;
        for (long i = 0; i <= 10000000000L; i++) {
            sum += i;
        }
        System.out.println(sum);

        Instant end = Instant.now();
        System.out.println("耗费时间:" + Duration.between(start, end).toMillis());
    }

    /**
     * java8新特性
     */
    @Test
    public void test1() {
        Instant start = Instant.now();

        Long sum = LongStream.rangeClosed(0L, 10000000000L)
                                .parallel()
                                .reduce(0L, Long::sum);
        System.out.println(sum);

        Instant end = Instant.now();
        System.out.println("耗费时间:" + Duration.between(start, end).toMillis());
    }

}

class ForkJoinSumCalculate extends RecursiveTask<Long> {    // Task有返回值,Action没有

    private Long start;
    private Long end;

    private static final long THURSHOLD = 10000L;   // 临界值

    public ForkJoinSumCalculate(Long start, Long end) {
        this.start = start;
        this.end = end;
    }

    @Override
    protected Long compute() {
        Long length = end - start;
        if (length <= THURSHOLD) {
            Long sum = 0L;
            for (long i = start; i <= end; i++) {
                sum += i;
            }
            return sum;
        } else {
            Long middle = (start + end) / 2;

            ForkJoinSumCalculate left = new ForkJoinSumCalculate(start, middle);
            left.fork();    // 进行拆分 同时压入线程队列

            ForkJoinSumCalculate right = new ForkJoinSumCalculate(middle+1, end);
            right.fork();    // 进行拆分 同时压入线程队列

            return left.join() + right.join();
        }
    }
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值