一、Java JUC简介
1.1、JUC简介
在Java 5.0 提供了java.util.concurrent(简称JUC )包,在此包中增加了在并发编程中很常用的实用工具类,用于定义类似于线程的自定义子系统,包括线程池、异步IO 和轻量级任务框架。提供可调的、灵活的线程池。还提供了设计用于多线程上下文中的Collection 实现等。
二、volatile 关键字-内存可见性
2.1、volatile关键字
现从一段代码说起叭!!!!
public class TestVolatile {
public static void main(String[] args) {
ThreadDemo threadDemo = new ThreadDemo();
new Thread(threadDemo).start();
while (true){
if (threadDemo.isFlag()){
System.out.println("____________");
break;
}
}
}
}
}
class ThreadDemo implements Runnable{
private boolean flag = false;
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
System.out.println("flag = "+ isFlag());
}
}
大家看完这段代码以为结果是什么呢?
这是由于不同的线程访问同一变量时,会出现内存可见性的问题,那什么是内存可见性呢?
- 内存可见性(Memory Visibility)是指当某个线程正在使用对象状态而另一个线程在同时修改该状态,需要确保当一个线程修改了对象状态后,其他线程能够看到发生的状态变化。
- 可见性错误是指当读操作与写操作在不同的线程中执行时,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根本不可能的事情。
- 我们可以通过同步来保证对象被安全地发布。除此之外我们也可以使用一种更加轻量级的volatile 变量。
- volatile关键字:Java提供了一种稍弱的同步机制,即volatile 变量,用来确保将变量的更新操作通知到其他线程。可以将volatile 看做一个轻量级的锁,但是又与锁有些不同:
- 对于多线程,不是一种互斥关系。
- 不能保证变量状态的“原子性操作”。
三、原子变量-CAS算法
3.1、原子性问题
一个简单的问题
int i = 10;
i = i++;
i的结果是什么呢? 答案i为10;实际上,i++的操作实际上分为三个步骤"读-改-写",即 int temp = i; i = i+1; i=temp;
例如:
public class TestAtomicDemo {
public static void main(String[] args) {
AtomicDemo ad = new AtomicDemo();
for (int i=0;i<10;i++){
new Thread(ad).start();
}
}
}
class AtomicDemo implements Runnable{
private int serialNumber = 0;
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+serialNumber++);
}
}
运行结果:
3.2、原子变量
- 原子变量:JDK1.5以后,java.util.concurrent.atomic包下提供了常用的原子变量。
- volatile:保证内存可见性。
- CAS(Compare-And-Swap) 算法保证数据的原子性。
- CAS算法时硬件对于并发操作共享数据的支持。
- CAS 包含了3 个操作数:
- 需要读写的内存值V
- 进行比较的值A
- 拟写入的新值B
volatile实例:
以上面的代码为例:
public class TestAtomicDemo {
public static void main(String[] args) {
AtomicDemo ad = new AtomicDemo();
for (int i=0;i<10;i++){
new Thread(ad).start();
}
}
}
class AtomicDemo implements Runnable{
private AtomicInteger serialNumber = new AtomicInteger();
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+getSerialNumber());
}
public int getSerialNumber(){
return serialNumber.getAndIncrement();
}
}
getAndIncrement():自增
运行结果:
3.3、CAS算法模拟
代码如下:
public class TestCompareAndSwap {
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 b = cas.compareAndSet(expectedValue, (int)(Math.random() * 101));
System.out.println(b);
}
}).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);
}
}
CAS算法大致就是:比如:内存值 V 预估值 A 更新值 B
当且仅当V==A时,V=B,否则,不作任何操作。
运行的结果:
我们可以看出,当i=0时,传入的两个值,此时内存值等于预估值,返回true,此后再传入随机的值,则内存值不等于预估值,此时不作任何操作,返回false。
四、ConcurrentHashMap锁分段机制
4.1、ConcurrentHashMap
- Java 5.0 在java.util.concurrent 包中提供了多种并发容器类来改进同步容器的性能。
- ConcurrentHashMap 同步容器类是Java 5 增加的一个线程安全的哈希表。对与多线程的操作,介于HashMap 与Hashtable 之间。内部采用“锁分段”机制替代Hashtable 的独占锁。进而提高性能。
- 此包还提供了设计用于多线程上下文中的Collection 实现:ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet、CopyOnWriteArrayList 和CopyOnWriteArraySet。当期望许多线程访问一个给定collection 时,ConcurrentHashMap 通常优于同步的HashMap,ConcurrentSkipListMap 通常优于同步的TreeMap。当期望的读数和遍历远远大于列表的更新数时,CopyOnWriteArrayList 优于同步的ArrayList。
4.2、实例
CopyOnWriteArrayList/CopyOnWriteArraySet:写入并复制
public class TestCopyOnWriteArrayList {
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<String>());
//创建一个CopyOnWriteArrayList实例
private static CopyOnWriteArrayList 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());
//往集合中添加"AA"
list.add("AA");
}
}
}
运行结果:
自己可以运行一下 !!!!啊哈哈 太多了
注意:添加操作多时,效率低,因为每次添加时都会进行复制,开销非常的大。 当遍历操作多时,效率会非常的高。
五、CountDownLatch 闭锁
5.1、CountDownLatch
- Java 5.0 在java.util.concurrent 包中提供了多种并发容器类来改进同步容器的性能。
- CountDownLatch 一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
- 闭锁可以延迟线程的进度直到其到达终止状态,闭锁可以用来确保某些活动直到其他活动都完成才继续执行:
a. 确保某个计算在其需要的所有资源都被初始化之后才继续执行;
b.确保某个服务在其依赖的所有其他服务都已经启动之后才启动;
c.等待直到某个操作所有参与者都准备就绪再继续执行。
5.2、实例
public class TestCountDownLatch {
public static void main(String[] args) {
//定义latch=50
final CountDownLatch latch = new CountDownLatch(50);
LatchDemo ld = new LatchDemo(latch);
//开始时间
long start = System.currentTimeMillis();
//50个线程
for (int i = 0; i < 50; i++) {
new Thread(ld).start();
}
try {
//等待异常
latch.await();
} catch (InterruptedException e) {
}
long end = System.currentTimeMillis();
System.out.println("耗费时间为:" + (end - start));
}
}
//创建一个类继承Runnable接口
class LatchDemo implements Runnable {
//创建一个闭锁的变量
private CountDownLatch latch;
//构造函数
public LatchDemo(CountDownLatch latch) {
this.latch = latch;
}
//重写run方法
@Override
public void run() {
synchronized (this){
try {
//遍历从0到500,打印出偶数
for (int i = 0; i < 50000; i++) {
if (i % 2 == 0) {
System.out.println(i);
}
}
} finally {
//执行完一次latch-1
latch.countDown();
}
}
}
}
运行结果大家可以去运行一下哈!!!!!!!
闭锁就是当其他线程执行完毕后,才能继续执行。
六、实现Callable接口
6.1、Callable 接口
- Java 5.0 在java.util.concurrent 提供了一个新的创建执行线程的方式:Callable 接口。
- Callable 接口类似于Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是Runnable 不会返回结果,并且无法抛出经过检查的异常。
- Callable 需要依赖FutureTask ,FutureTask 也可以用作闭锁。
6.2、实例
public class TestCallable {
public static void main(String[] args) {
ThreadDemo td = new ThreadDemo();
//执行Callable方式:需要FutureTask 实现类的支持,用于接收运算结果。
FutureTask<Integer> result = new FutureTask<>(td);
new Thread(result).start();
//接收线程运算后的结果
try {
Integer sum = result.get();//FutureTask也可用于闭锁
System.out.println(sum);
System.out.println("********************");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class ThreadDemo implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int sum= 0;
for (int i= 0;i<=1000;i++){
System.out.println(i);
sum+=i;
}
return sum;
}
}
/*class ThreadDemo1 implements Runnable{
@Override
public void run() {
}
}*/
总结:
Callable和Runable的区别:
1.方法有返回值
2.可以抛出异常
七、Lock同步锁
7.1、显示锁Lock
- 在Java 5.0 之前,协调共享对象的访问时可以使用的机制只有synchronized 和volatile 。Java 5.0 后增加了一些新的机制,但并不是一种替代内置锁的方法,而是当内置锁不适用时,作为一种可选择的高级功能。
- ReentrantLock 实现了Lock 接口,并提供了与synchronized 相同的互斥性和内存可见性。但相较于synchronized 提供了更高的处理锁的灵活性。
7.2、实例
买票问题大家都知道,不多说直接上代码
public class TestLock {
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 ticket = 100;
@Override
public void run() {
while (true){
try {
while (ticket>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"完成售票,余票为:"+(--ticket));
}
} finally {
}
}
}
}
运行结果:上一部分的结果,会出现重票的现象:
上锁:
public class TestLock {
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 tick = 100;
private Lock lock = new ReentrantLock();
@Override
public void run() {
while(true){
lock.lock(); //上锁
try{
if(tick > 0){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + " 完成售票,余票为:" + --tick);
}
}finally{
lock.unlock(); //释放锁
}
}
}
}
运行结果:
八、Condition控制线程通讯
8.1、Condition
- Condition 接口描述了可能会与锁有关联的条件变量。这些变量在用法上与使用Object.wait 访问的隐式监视器类似,但提供了更强大的功能。需要特别指出的是,单个Lock 可能与多个Condition 对象关联。为了避免兼容性问题,Condition 方法的名称与对应的Object 版本中的不同。
- 在Condition 对象中,与wait、notify 和notifyAll 方法对应的分别是await、signal 和signalAll。
- Condition 实例实质上被绑定到一个锁上。要为特定Lock 实例获得Condition 实例,请使用其newCondition() 方法。
九、线程八锁
9.1、两个普通方法,两个线程判断打印"one"或者"two"
public class TestThread8Monitor {
public static void main(String[] args) {
Number number = new Number();
new Thread(new Runnable() {
@Override
public void run() {
number.getOne();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
number.getTwo();
}
}).start();
}
}
class Number{
public void getOne(){
System.out.println("one");
}
public void getTwo(){
System.out.println("two");
}
}
运行结果:
9.2、新增Thread.sleep()给getOne()方法
public class TestThread8Monitor {
public static void main(String[] args) {
Number number = new Number();
new Thread(new Runnable() {
@Override
public void run() {
number.getOne();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
number.getTwo();
}
}).start();
}
}
class Number{
public void getOne(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("one");
}
public void getTwo(){
System.out.println("two");
}
}
运行结果:先打印two,等待3秒再打印one
9.3、在9.2的基础上新增普通方法getThree()方法
public class TestThread8Monitor {
public static void main(String[] args) {
Number number = new Number();
new Thread(new Runnable() {
@Override
public void run() {
number.getOne();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
number.getTwo();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
number.getThree();
}
}).start();
}
}
class Number{
public void getOne(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("one");
}
public void getTwo(){
System.out.println("two");
}
public void getThree(){
System.out.println("three");
}
}
运行结果:先出现two,three,等待3秒钟,再出现one.
9.4、在9.3的基础上,在getOne和getTwo方法加上同步锁
public class TestThread8Monitor {
public static void main(String[] args) {
Number number = new Number();
Number number1 = new Number();
new Thread(new Runnable() {
@Override
public void run() {
number.getOne();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
number1.getTwo();
}
}).start();
}
}
class Number{
public synchronized void getOne(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("one");
}
public synchronized void getTwo(){
System.out.println("two");
}
}
运行结果:先出现two,等到3秒,出现one
9.5、在9.4的基础上,将getOne()设置为静态方法
public class TestThread8Monitor {
public static void main(String[] args) {
Number number = new Number();
//Number number1 = new Number();
new Thread(new Runnable() {
@Override
public void run() {
Number.getOne();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
number.getTwo();
}
}).start();
}
}
class Number{
public static synchronized void getOne(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("one");
}
public synchronized void getTwo(){
System.out.println("two");
}
}
运行结果:先出现two,等待3秒,出现one
9.6、在9.5的基础上,改两个方法均为静态同步方法,一个Number对象?
public class TestThread8Monitor {
public static void main(String[] args) {
//Number number = new Number();
//Number number1 = new Number();
new Thread(new Runnable() {
@Override
public void run() {
Number.getOne();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
Number.getTwo();
}
}).start();
}
}
class Number{
public static synchronized void getOne(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("one");
}
public static synchronized void getTwo(){
System.out.println("two");
}
}
运行结果:先打印two,等待3秒钟在打印one
9.7、在9.6的基础上,一个是静态同步方法,一个是非静态同步方法,两个Number对象?
public class TestThread8Monitor {
public static void main(String[] args) {
Number number = new Number();
//Number number1 = new Number();
new Thread(new Runnable() {
@Override
public void run() {
Number.getOne();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
number.getTwo();
}
}).start();
}
}
class Number{
public static synchronized void getOne(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("one");
}
public synchronized void getTwo(){
System.out.println("two");
}
}
运行结果:先打印two,等待3秒钟在打印one
9.8、在9.7的基础上,两个静态同步方法,两个number对象 ??
public class TestThread8Monitor {
public static void main(String[] args) {
Number number = new Number();
Number number1 = new Number();
new Thread(new Runnable() {
@Override
public void run() {
number.getOne();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
number1.getTwo();
}
}).start();
}
}
class Number{
public static synchronized void getOne(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("one");
}
public static synchronized void getTwo(){
System.out.println("two");
}
}
运行结果:先等待3秒钟,再依次打印one,two
9.9、总结
- 非静态方法的锁默认为this,静态方法的锁为对应的Class实例
- 某一个时刻内,只能由一个线程持有锁,无论几个方法。
- 一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了,其它的线程都只能等待,换句话说,某一个时刻内,只能有唯一一个线程去访问这些synchronized方法。
- 锁的是当前对象this,被锁定后,其它的线程都不能进入到当前对象的其它的synchronized方法。
- 加个普通方法后发现和同步锁无关。
- 换成两个对象后,不是同一把锁了,情况立刻变化。
- 都换成静态同步方法后,情况又变化。
- 所有的非静态同步方法用的都是同一把锁——实例对象本身,也就是说如果一个实例对象的非静态同步方法获取锁后,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁后才能获取锁,可是别的实例对象的非静态同步方法因为跟该实例对象的非静态同步方法用的是不同的锁,所以毋须等待该实例对象已获取锁的非静态同步方法释放锁就可以获取他们自己的锁。
- 所有的静态同步方法用的也是同一把锁——类对象本身,这两把锁是两个不同的对象,所以静态同步方法与非静态同步方法之间是不会有竞态条件的。但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,而不管是同一个实例对象的静态同步方法之间,还是不同的实例对象的静态同步方法之间,只要它们同一个类的实例对象!
十、线程按序交替
10.1、实例
编写一个程序,开启3 个线程,这三个线程的ID 分别为A、B、C,每个线程将自己的ID 在屏幕上打印10 遍,要求输出的结果必须按顺序显示。如:ABCABCABC… 依次递归
public class TestABCAlternate {
public static void main(String[] args) {
AlternateDemo ad = new AlternateDemo();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 20; i++) {
ad.loopA(i);
}
}
}, "A").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 20; i++) {
ad.loopB(i);
}
}
}, "B").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 20; i++) {
ad.loopC(i);
System.out.println("-----------------------------------");
}
}
}, "C").start();
}
}
class AlternateDemo{
private int number = 1; //当前正在执行线程的标记
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
/**
* @param totalLoop : 循环第几轮
*/
public void loopA(int totalLoop){
lock.lock();
try {
//1. 判断
if(number != 1){
condition1.await();
}
//2. 打印
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + totalLoop);
}
//3. 唤醒
number = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void loopB(int totalLoop){
lock.lock();
try {
//1. 判断
if(number != 2){
condition2.await();
}
//2. 打印
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + totalLoop);
}
//3. 唤醒
number = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void loopC(int totalLoop){
lock.lock();
try {
//1. 判断
if(number != 3){
condition3.await();
}
//2. 打印
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + totalLoop);
}
//3. 唤醒
number = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
十一、ReadWriter 读写锁
11.1、ReadWriter
- ReadWriteLock 维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。只要没有writer,读取锁可以由多个reader 线程同时保持。写入锁是独占的。
- ReadWriteLock 读取操作通常不会改变共享资源,但执行写入操作时,必须独占方式来获取锁。对于读取操作占多数的数据结构。ReadWriteLock 能提供比独占锁更高的并发性。而对于只读的数据结构,其中包含的不变性可以完全不需要考虑加锁操作。
- 写写/读写 需要互斥
- 读读 不需要互斥
11.2、实例
public class TestReadWriterLock {
public static void main(String[] args) {
ReadWriteLockDemo rw = new ReadWriteLockDemo();
new Thread(new Runnable() {
@Override
public void run() {
rw.set((int)(Math.random()*101));
}
},"write:").start();
for (int i = 0; i <100 ; i++) {
new Thread(new Runnable() {
@Override
public void run() {
rw.get();
}
},"read").start();
}
}
}
class ReadWriteLockDemo{
private int number = 0;
private ReadWriteLock lock = new ReentrantReadWriteLock();
//读取数据
public void get(){
lock.readLock().lock();//上锁
try {
System.out.println(Thread.currentThread().getName()+" : "+number);
} finally {
lock.readLock().unlock();
}
}
//写数据
public void set(int number){
lock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName());
this.number=number;
} finally {
lock.writeLock().unlock();
}
}
}
十二、线程池
12.1、线程池
- 第四种获取线程的方法:线程池,一个ExecutorService,它使用可能的几个池线程之一执行每个提交的任务,通常使用Executors 工厂方法配置。
- 线程池可以解决两个不同问题:由于减少了每个任务调用的开销,它们通常可以在执行大量异步任务时提供增强的性能,并且还可以提供绑定和管理资源(包括执行任务集时使用的线程)的方法。每个ThreadPoolExecutor 还维护着一些基本的统计数据,如完成的任务数。
- 为了便于跨大量上下文使用,此类提供了很多可调整的参数和扩展钩子(hook)。但是,强烈建议程序员使用较为方便的Executors 工厂方法:Executors.newCachedThreadPool()(无界线程池,可以进行自动线程回收)Executors.newFixedThreadPool(int)(固定大小线程池)Executors.newSingleThreadExecutor()(单个后台线程)它们均为大多数使用场景预定义了设置。
12.2、实例
1、在普通方法中,创建线程
ublic class TestThreadPool {
public static void main(String[] args) throws Exception {
ThreadPoolDemo tpd = new ThreadPoolDemo();
new Thread(tpd).start();
new Thread(tpd).start();
}
}
class ThreadPoolDemo implements Runnable{
private int i = 0;
@Override
public void run() {
while(i <= 100){
System.out.println(Thread.currentThread().getName() + " : " + i++);
}
}
}
2、使用Executors工具类
public class TestThreadPool {
public static void main(String[] args) throws Exception {
ThreadPoolDemo tpd = new ThreadPoolDemo();
//1.创建线程池
ExecutorService es = Executors.newFixedThreadPool(5);
//2、为线程池中的线程分配任务
es.submit(tpd);
//3、关闭线程池
es.shutdown();
}
}
class ThreadPoolDemo implements Runnable{
private int i = 0;
@Override
public void run() {
while(i <= 100){
System.out.println(Thread.currentThread().getName() + " : " + i++);
}
}
}
3、使用Executors工具类,并实现Callable接口
public class TestThreadPool {
public static void main(String[] args) throws Exception {
//1、创建线程池
ExecutorService pool = Executors.newFixedThreadPool(5);
//3、创建一个List用来封装结果
List<Future<Integer>> list = new ArrayList<>();
//2、为线程池中的线程分配任务
for (int i = 0; i <10 ; i++) {
Future<Integer> future = pool.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <=100 ; i++) {
sum+=i;
}
return sum;
}
});
list.add(future);
}
//4、关闭线程池
pool.shutdown();
//5、遍历结果
for (Future<Integer> future : list){
System.out.println(future.get());
}
}
}
十三、线程调度
13.1、ScheduledExecutorService
- ScheduledExecutorService:一个ExecutorService,可安排在给定的延迟后运行或定期执行的命令。
13.2、实例
打印一个随机数,延迟3秒
public class TestScheduledThreadPool {
public static void main(String[] args) {
//1、创建线程池
ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);
//2、线程池对象的schedule方法调用线程调度
Future<Integer> result = pool.schedule(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
//创建100以内的随机数
int num = new Random().nextInt(100);
//打印线程名和随机数
System.out.println(Thread.currentThread().getName()+" : "+num);
return num;
}
//随后两个参数第一个是几,第二个是时间类型,时分秒
},3, TimeUnit.SECONDS);
try {
//打印线程获取的值
System.out.println(result.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
//关闭线程
pool.shutdown();
}
十四、ForkJoinPool 分支/合并框架 工作窃取
14.1、Fork/Join 框架
- Fork/Join 框架:就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行join 汇总。
- 采用“工作窃取”模式(work-stealing):当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中。
- 相对于一般的线程池实现,fork/join框架的优势体现在对其中包含的任务的处理方式上.在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态。而在fork/join框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行。那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行.这种方式减少了线程的等待时间,提高了性能。
14.2、实例
使用普通for循环,使用Fork/Join 框架和java 8新特性实现从0到50000000000L相加后的值。
public class TestForkJoinPool {
public static void main(String[] args) {
//定义时间
Instant start = Instant.now();
//创建一个ForkJoinPool对象,ForkJoinSumCalculate需要借助pool
ForkJoinPool pool = new ForkJoinPool();
//对ForkJoinSumCalculate传入开始值和末值
ForkJoinTask<Long> task = new ForkJoinSumCalculate(0L, 50000000000L);
//invoke方法进行执行
Long sum = pool.invoke(task);
//输出sum
System.out.println(sum);
//运行到当前的时间
Instant end = Instant.now();
//消耗时间
System.out.println("耗费时间为:" + Duration.between(start, end).toMillis());//166-1996-10590
}
/**
* 普通的for循环方法
*/
@Test
public void test1(){
//定义时间
Instant start = Instant.now();
long sum = 0L;
for (long i = 0L; i <= 50000000000L; i++) {
sum += i;
}
System.out.println(sum);
Instant end = Instant.now();
System.out.println("耗费时间为:" + Duration.between(start, end).toMillis());//35-3142-15704
}
//java8 新特性
@Test
public void test2(){
Instant start = Instant.now();
Long sum = LongStream.rangeClosed(0L, 50000000000L)
.parallel()
.reduce(0L, Long::sum);
System.out.println(sum);
Instant end = Instant.now();
System.out.println("耗费时间为:" + Duration.between(start, end).toMillis());//1536-8118
}
}
class ForkJoinSumCalculate extends RecursiveTask<Long>{
//序列化
private static final long serialVersionUID = -259195479995561737L;
//定义一个开始值
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();
//join汇总
return left.join() + right.join();
}
}
}
代码注释很详细,所以大家可以运行一下,比较一下哪个效率比较高。