java.util.concurrent的学习

在 Java 5.0 提供了 java.util.concurrent (简称JUC )包,在此包中增加了在并发编程中很常用的实用工具类, 用于定义类似于线程的自定义子系统,包括线程池、异步 IO 和轻量级任务框架。提供可调的、灵活的线程池。还提供了设计用于多线程上下文中的 Collection 实现等。

主要内容:

  1. Java JUC 简介
  2. volatile 关键字-内存可见性
  3. 原子变量-CAS算法
  4. ConcurrentHashMap 锁分段机制
  5. CountDownLatch 闭锁
  6. 实现 Callable 接口
  7. Lock 同步锁
  8. Condition 控制线程通信
  9. 线程按序交替
  10. ReadWriteLock 读写锁
  11. 线程八锁
  12. 线程池
  13. 线程调度
  14. ForkJoinPool 分支/合并框架 工作窃取

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

volatile关键字:当多个线程对共享数据进行操作时,可以保证内存中的数据可见.

  • 内存可见性( Memory Visibility):是指当某个线程正在使用对象状态而另一个线程在同时修改该状态,需要确保当一个线程修改了对象状态后,其他线程能够看到发生的状态变化。
  • 可见性错误是指当读操作与写操作在不同的线程中执行时,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根本不可能的事情。
  • 每个线程都有自己的工作内存,对于共享数据,每个线程会从主存中拷贝一份副本到自己的工作内存,操作的也是这个工作内存的副本.因此当一个线程对共享数据进行写操作进而刷新共享数据到主存中,另外一个线程并没有感知到主存中的共享数据已经发生了变化,仍然操作的是自己工作内存中的旧的数据,这就造成了内存可见性问题.
  • 我们可以通过同步来保证对象被安全地发布。除此之外我们也可以使用一种更加轻量级的 volatile 变量。
  • volatile相较于synchronized是一种较为轻量级的同步策略,是java提供的一种稍弱的同步机制,用来确保将变量的更新操作通知到其他线程.
  • 另外使用了volatile,jvm会禁止指令重排序.
  • 注意:
  • 1.volatile不具备"互斥性"(对于多线程,不是一种互斥关系),像synchronized是互斥锁,即当一个线程持有锁时,其他线程只能等待
  • 2.volatile不能保证变量的原子性,例如i++问题

二.原子变量CAS算法

i++的原子问题,i++实际上在底层是分"读-改-写"三步走的
原子变量:JDK1.5后,增加了java.util.concurrent.atomic包提供常用的原子变量

  • CAS(compare-and-swap)算法保证数据的原子性,CAS是硬件对并发支持,针对多处理器操作而设计的处理器中的一种特殊指令,用于管理对共享数据的并发访问。
  • CAS 是一种无锁的非阻塞算法的实现。
  • CAS包含了三个操作数
    需要读写的内存值 V
    进行比较的值 A
    拟写入的新值 B
    当且仅当 V 的值等于 A 时, CAS 通过原子方式用新值 B 来更新 V 的
    值,否则不会执行任何操作。

原子变量
核心方法: boolean compareAndSet(expectedValue, updateValue)
java.util.concurrent.atomic 包下提供了一些原子操作的常用类:

  • AtomicBoolean 、 AtomicInteger 、 AtomicLong 、 AtomicReference
    (各自提供对相应类型单个变量的线程安全的访问和更新)
  • AtomicIntegerArray 、 AtomicLongArray
  • AtomicMarkableReference
  • AtomicReferenceArray
  • AtomicStampedReference
public class TestCAS {
    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 volatile int serialNum = 0;

    @Override
    public void run() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
	        System.out.print(getSerialNum() + " ");
    }

    private int getSerialNum() {
        return serialNum++;
    }
}

上面的程序可能会输出类似下面的结果:
0 2 1 1 3 4 5 7 6 8
而这显然不是我们期待的结果.
现在将作如下改动,则不会再有重复的数字出现.

public class TestCAS {
    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 serialNum = new AtomicInteger();

    @Override
    public void run() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.print(getSerialNum() + " ");
    }

    private int getSerialNum() {
        return serialNum.getAndIncrement();
    }
}

三.ConcurrentHashMap 锁分段机制

  • ConcurrentHashMap 同步容器类是Java 5 增加的一个线程安全的哈希表。对与多线程的操作,介于 HashMap 与 Hashtable 之间。内部采用“锁分段”机制替代 Hashtable 的独占锁(JDK1.8后,采用CAS控制并发),进而提高性能。
    这里写图片描述
  • java.util.concurrent包还提供了设计用于多线程上下文中的 Collection 实现:ConcurrentHashMap、 ConcurrentSkipListMap、 ConcurrentSkipListSet、CopyOnWriteArrayList 和 CopyOnWriteArraySet。当期望许多线程访问一个给定 collection 时, ConcurrentHashMap 通常优于同步的 HashMap,ConcurrentSkipListMap 通常优于同步的 TreeMap。当期望的读数和遍历远远大于列表的更新数时, CopyOnWriteArrayList 优于同步的 ArrayList。
package com.h;

import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * Created by John on 2018/5/12.
 * CopyOnWriteArrayList/CopyOnWriteArraySet : “写入并复制”
 * 注意:添加操作多时,效率低,因为每次添加时都会进行复制,开销非常的大。并发迭代操作多时可以选择。
 */
public class TestCopyOnWriteArrayList {
    //private static List<String> list = new ArrayList<>(); (1)
    //private static List<String> list = Collections.synchronizedList(new ArrayList<>()); (2)
    private static CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();

    static {
        list.add("A");
        list.add("B");
        list.add("C");
    }

    public static void main(String[] args) {
        Iterator<String> it = list.iterator();
        while (it.hasNext()){
            System.out.println(it.next());
            list.add("D");
        }
    }
}

其中,(1)和(2)都会引发并发修改异常

四.线程同步辅助工具类(闭锁CountDownLatch、栅栏CyclicBarrier、信号量Semaphore)

闭锁CountDownLatch

  • CountDownLatch 一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待
  • 闭锁可以延迟线程的进度直到其到达终止状态,闭锁可以用来确保某些活动直到其他活动都完成才继续执行:
    > 确保某个计算在其需要的所有资源都被初始化之后才继续执行;
    > 确保某个服务在其依赖的所有其他服务都已经启动之后才启动;
    > 等待直到某个操作所有参与者都准备就绪再继续执行。
package com.h.concurrent;

import java.util.concurrent.CountDownLatch;

/**
 * Created by John on 2018/5/12.
 * CountDownLatch :闭锁,在完成某些运算时,只有其他所有线程的运算全部完成,当前运算才继续执行
 * 示例:利用闭锁计算各个线程执行完成所需的时间
 */
public class TestCountDownLatch {
    public static void main(String[] args) {
        /**
         * 任务:打印[0,50000]之间的所有偶数,并计算耗费时间
         * 实现:开启5个线程,并发执行任务,在main主线程中汇总耗费的时间
         */
        final CountDownLatch latch = new CountDownLatch(5);
        LatchDemo lt = new LatchDemo(latch);

        long start = System.currentTimeMillis();
        for (int i = 0; i < 5; i++) {
            new Thread(lt).start();
        }

        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("耗费时间为:" + (end - start) + "ms");
    }
}

class LatchDemo implements Runnable {
    private CountDownLatch latch;

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

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

栅栏CyclicBarrier
CyclicBarrier 类是一种同步机制,它能对处理一些算法的线程实现同步。换句话说,它就是一个所有线程必须等待的一个栅栏,直到所有线程都到达这里,然后所有线程才可以继续做其他事情。通过调用 CyclicBarrier 对象的 await()方法,多个线程间可以实现互相等待。一旦 N 个线程在等待 CyclicBarrier 达成,所有线程将被释放掉去继续执行。

package com.h.concurrent;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
 * Created by John on 2018/5/12.
 */
public class TestCyclicBarrier {
    public static void main(String[] args) {
        final CyclicBarrier barrier = new CyclicBarrier(5);
        BarrierDemo bd = new BarrierDemo(barrier);

        long start = System.currentTimeMillis();
        for (int i = 0; i < 5; i++) {
            new Thread(bd).start();
        }

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

        long end = System.currentTimeMillis();
        System.out.println("耗费时间为:" + (end - start) + "ms");
    }
}

class BarrierDemo implements Runnable {
    private CyclicBarrier barrier;

    public BarrierDemo(CyclicBarrier barrier) {
        this.barrier = barrier;
    }

    @Override
    public void run() {
        for (int i = 0; i <= 50000; i++) {
            if (i % 2 == 0) {
                System.out.println(i);
            }
        }
        System.out.println("正在等待执行计算任务的线程数量为:" + barrier.getNumberWaiting());
        try {
            barrier.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}

信号量 Semaphore
Semaphore类是一个计数信号量。这就意味着它具备两个主要方法:
● acquire()获取
● release()释放
计数信号量由一个指定数量的“许可”初始化。每调用一次 acquire(),一个许可会被调用线程取走。每调用一次 release(),一个许可会被还给信号量。因此,在没有任何 release()调用时,最多有 N 个线程能够通过 acquire()方法,N 是该信号量初始化时的许可的指定数量。

Semaphore就是一个信号量,它的作用是限制某段代码块的并发数。Semaphore有一个构造函数,可以传入一个int型整数n,表示某段代码最多只有n个线程可以访问,如果超出了n,那么请等待,等到某个线程执行完毕这段代码块,下一个线程再进入。由此可以看出如果Semaphore构造函数中传入的int型整数n=1,相当于变成了一个synchronized了。

package com.h.concurrent;

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

/**
 * Created by John on 2018/5/12.
 */
public class TestSemaphore {
    public static void main(String[] args) {
        final int THREAD_COUNT = 10;
        ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT);
        Semaphore semaphore = new Semaphore(5);
        for (int i=0;i<THREAD_COUNT;i++){
            threadPool.execute(new SemaphoreDemo(String.valueOf(i),semaphore));
        }
        threadPool.shutdown();
    }
}

class SemaphoreDemo implements Runnable{
    private String id;
    private Semaphore semaphore;
    private static Random random = new Random(100);

    public SemaphoreDemo(String id, Semaphore semaphore) {
        this.id = id;
        this.semaphore = semaphore;
    }

    @Override
    public void run() {
        try {
            semaphore.acquire();
            System.out.println(this.id + "is using the toilet");
            TimeUnit.MICROSECONDS.sleep(random.nextInt(2000));
            System.out.println(this.id + "is leaving");
            semaphore.release();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

0is using the toilet
2is using the toilet
3is using the toilet
4is using the toilet
5is using the toilet
2is leaving
5is leaving
9is using the toilet
0is leaving
1is using the toilet
7is using the toilet
9is leaving
4is leaving
3is leaving
1is leaving
7is leaving
8is using the toilet
6is using the toilet
8is leaving
6is leaving

假设有5个semaphore(厕所),有10个线程(员工)取占用,肯定只有5个线程(员工)能够占用,每当一个线程(员工) 使用完(release) ,会有另一个线程占用(acquire) 。可用来控制线程并发数,比如数据库连接等。

总结:

  • CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同:
    CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;
    而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再继续执行;
    另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。
  • Semaphore其实和锁有点类似,它一般用于控制对某组资源的访问权限。

五.创建线程的方式三:Callable接口

  • Callable 接口类似于 Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是 Runnable不会返回结果,并且无法抛出经过检查的异常。
  • Callable 需要依赖FutureTask , FutureTask 也可以用作闭锁。
package com.h.concurrent;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * Created by John on 2018/5/12.
 * 一、创建执行线程的方式三:实现 Callable 接口。 相较于实现 Runnable 接口的方式,方法可以有返回值,并且可以抛出异常。
 * 二、执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。  FutureTask 是Future 接口的实现类
 */
public class TestCallable {
    public static void main(String[] args) {
        CallableDemo cd = new CallableDemo();
        //1.执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。
        FutureTask<Integer> ft = new FutureTask<Integer>(cd);
        new Thread(ft).start();
        //2.接收线程运算后的结果
        try {
            Integer sum = ft.get(); //FutureTask 可用于 闭锁
            System.out.println(sum);
            System.out.println("=======================================");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

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

六.同步锁Lock

  • 在 Java 5.0 之前,协调共享对象的访问时可以使用的机制只有 synchronized 和volatile 。 Java 5.0 后增加了一些新的机制,但并不是一种替代内置锁的方法,而是当内置锁不适用时,作为一种可选择的高级功能。
  • ReentrantLock 实现了 Lock 接口,并提供了与synchronized相同的互斥性和内存可见性。但相较于synchronized 提供了更高的处理锁的灵活性。

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

/*
 * 一、用于解决多线程安全问题的方式:
 * 
 * synchronized:隐式锁
 * 1. 同步代码块
 * 2. 同步方法
 * 
 * jdk 1.5 后:
 * 3. 同步锁 Lock
 * 注意:是一个显示锁,需要通过 lock() 方法上锁,必须通过 unlock() 方法进行释放锁
 */
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(); //释放锁
			}
		}
	}
}

生产者消费者案例

package com.h.concurrent;

/**
 * Created by John on 2018/5/12.
 */
public class TestProductorAndConsumer {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();

        Productor pro = new Productor(clerk);
        Consumer cus = new Consumer(clerk);

        new Thread(pro, "生产者 A").start();
        new Thread(cus, "消费者 B").start();

        new Thread(pro, "生产者 C").start();
        new Thread(cus, "消费者 D").start();
    }
}

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

	//进货
	public synchronized void get(){//循环次数:0
		while(product >= 1){//为了避免虚假唤醒问题,应该总是使用在循环中
			System.out.println("产品已满!");
			try {
				this.wait();
			} catch (InterruptedException e) {
			}
		}
		System.out.println(Thread.currentThread().getName() + " : " + ++product);
		this.notifyAll();
	}

	//卖货
	public synchronized void sale(){//product = 0; 循环次数:0
		while(product <= 0){
			System.out.println("缺货!");
			try {
				this.wait();
			} catch (InterruptedException e) {
			}
		}
		System.out.println(Thread.currentThread().getName() + " : " + --product);
		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 < 20; i++) {
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
			}
			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 < 20; i++) {
			clerk.sale();
		}
	}
}

注意虚假唤醒问题的解决.
这里写图片描述

七.Condition 控制线程通信

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

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

/**
 * Created by John on 2018/5/12.
 * 使用Lock+Condition实现生产者和消费者
 * 线程通信,等待唤醒机制,线程同步
 */
public class TestProductorAndConsumerForLock {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();

        Productor pro = new Productor(clerk);
        Consumer con = new Consumer(clerk);

        new Thread(pro, "生产者 A").start();
        new Thread(con, "消费者 B").start();

//		 new Thread(pro, "生产者 C").start();
//		 new Thread(con, "消费者 D").start();
    }
}

class Clerk2{
    private int product = 0;
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    //进货
    public void get(){
        lock.lock();
        try {
            while (product >= 1){//为了避免虚假唤醒,应该总是使用在循环中
                System.out.println("产品已满!");

                //进入等待
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            //可以继续生产
            System.out.println(Thread.currentThread().getClass()+":"+ ++product);
            //唤醒在等待的消费者
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }

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

        try {
            while (product <= 0){
                System.out.println("缺货!");

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

            System.out.println(Thread.currentThread().getClass()+":"+ --product);
            //唤醒在等待的生产者
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }
}


// 生产者
class Productor2 implements Runnable {

    private Clerk2 clerk;

    public Productor2(Clerk2 clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            clerk.get();
        }
    }
}

// 消费者
class Consumer2 implements Runnable {

    private Clerk2 clerk;

    public Consumer2(Clerk2 clerk) {
        this.clerk = clerk;
    }

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

}

八.线程按序交替

package com.h.concurrent;

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

/**
 * Created by John on 2018/5/12.
 *  编写一个程序,开启 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<=10;i++){
                    ad.loopA(i);
                }
            }
        }, "A").start();

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

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i=1;i<=10;i++){
                    ad.loopC(i);
                }
            }
        }, "C").start();
    }
}

class AlternateDemo{
    private int numberFlag = 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 (numberFlag != 1){
                try {
                    condition1.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            /**
             * 这里之所以用for循环,是因为可以扩展为如:
             * 先打印A5次,在打印B10次,最后打印C15次,如此递归
             * 需要通过如下控制:
             *  for (int i=1;i<=5;i++){
             *      ...
             *  }
             */
            //2.打印
            for (int i=1;i<=1;i++){
                System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + totalLoop);
            }

            //3.改变状态标记,唤醒指定线程
            numberFlag = 2;
            condition2.signal();
        } finally {
            lock.unlock();
        }
    }

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

        try {
            //1.判断
            if (numberFlag != 2){
                try {
                    condition2.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            /**
             * 这里之所以用for循环,是因为可以扩展为如:
             * 先打印A5次,在打印B10次,最后打印C15次,如此递归
             * 需要通过如下控制:
             *  for (int i=1;i<=5;i++){
             *      ...
             *  }
             */
            //2.打印
            for (int i=1;i<=1;i++){
                System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + totalLoop);
            }

            //3.改变状态标记,唤醒指定线程
            numberFlag = 3;
            condition3.signal();
        } finally {
            lock.unlock();
        }
    }

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

        try {
            //1.判断
            if (numberFlag != 3){
                try {
                    condition3.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            /**
             * 这里之所以用for循环,是因为可以扩展为如:
             * 先打印A5次,在打印B10次,最后打印C15次,如此递归
             * 需要通过如下控制:
             *  for (int i=1;i<=5;i++){
             *      ...
             *  }
             */
            //2.打印
            for (int i=1;i<=1;i++){
                System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + totalLoop);
            }

            //3.改变状态标记,唤醒指定线程
            numberFlag = 1;
            condition1.signal();
        } finally {
            lock.unlock();
        }
    }
}

使用Synchronized + wait/notifyAll实现

package com.h.concurrent;

import java.util.concurrent.atomic.AtomicInteger;

public class TestWaitNotify {
    public static void main(String[] args) {
        PrintABC2 printABC2 = new PrintABC2();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i=0;i<10;i++){
                    printABC2.loopA();
                }
            }
        }, "A").start();

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

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

class PrintABC2{
    //private volatile int flag = 1;
    private AtomicInteger flag = new AtomicInteger(1);

    public synchronized void loopA(){
        //while (flag != 1){
        while (flag.get() != 1){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.print(Thread.currentThread().getName());
        //flag = 2;
        flag.set(2);
        this.notifyAll();
    }

    public synchronized void loopB(){
       // while (flag != 2){
        while (flag.get() != 2){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.print(Thread.currentThread().getName());
       // flag = 3;
        flag.set(3);
        this.notifyAll();
    }

    public synchronized void loopC(){
        //while (flag != 3){
        while (flag.get() != 3){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.print(Thread.currentThread().getName());
        //flag = 1;
        flag.set(1);
        this.notifyAll();
    }

}

也可以写成下面这样:

package com.h.concurrent;

import java.util.concurrent.atomic.AtomicInteger;

public class TestWaitNotify2 {
    public static void main(String[] args) {
        AtomicInteger flag = new AtomicInteger(1);
        final Object lock = new Object();

        Thread A = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    for (int i = 0; i < 10; i++) {
                        while (flag.get() != 1) {
                            try {
                                lock.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }

                        System.out.print("A");
                        flag.set(2);
                        lock.notifyAll();
                    }
                }
            }
        });

        Thread B = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    for (int i = 0; i < 10; i++) {
                        while (flag.get() != 2) {
                            try {
                                lock.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }

                        System.out.print("B");
                        flag.set(3);
                        lock.notifyAll();
                    }
                }
            }
        });

        Thread C = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    for (int i = 0; i < 10; i++) {
                        while (flag.get() != 3) {
                            try {
                                lock.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }

                        System.out.print("C");
                        flag.set(1);
                        lock.notifyAll();
                    }
                }
            }
        });
        
        A.start();
        B.start();
        C.start();
    }
}

九.ReadWriteLock 读写锁

  • ReadWriteLock 维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。只要没有 writer,读取锁可以由多个 reader 线程同时保持。写入锁是独立的。
  • ReadWriteLock 读取操作通常不会改变共享资源,但执行写入操作时,必须独占方式来获取锁。对于读取操作占多数的数据结构.ReadWriteLock 能提供比独占锁更高的并发性。而对于只读的数据结构,其中包含的不变性可以完全不需要考虑加锁操作。
package com.h.concurrent;

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * Created by John on 2018/5/13.
 * ReadWriteLock : 读写锁
 * 写写/读写 需要“互斥”
 * 读读 不需要互斥
 */
public class TestReadWriteLock {
    public static void main(String[] args) {
        final ReadWriteLockDemo demo = new ReadWriteLockDemo();
        new Thread(new Runnable() {
            @Override
            public void run() {
                demo.set((int)Math.random()*101);
            }
        }, "write:").start();

        for (int i=0;i<100;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    demo.get();
                }
            }).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;
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.writeLock().unlock();
        }
    }

}

十.线程八锁

• 一个对象里面如果有多个synchronized方法,某一个时刻内只要一个线程去调用其中的一个synchronized方法了,其它的线程都只能等待,换句话说,某一个时刻内,只能有唯一一个线程去访问这些synchronized方法
• 锁的是当前对象this,被锁定后,其它的线程都不能进入到当前对象的其它的synchronized方法
• 加个普通方法后发现和同步锁无关
• 换成两个对象后,不是同一把锁了,情况立刻变化。
• 都换成静态同步方法后,情况又变化
• 所有的非静态同步方法用的都是同一把锁——实例对象本身,也就是说如果一个实例对象的非静态同步方法获取锁后,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁后才能获取锁,可是别的实例对象的非静态同步方法因为跟该实例对象的非静态同步方法用的是不同的锁,所以毋须等待该实例对象已获取锁的非静态同步方法释放锁就可以获取他们自己的锁。
• 所有的静态同步方法用的也是同一把锁——类对象本身,这两把锁是两个不同的对象,所以静态同步方法与非静态同步方法之间是不会有竞态条件的。但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,而不管是同一个实例对象的静态同步方法之间,还是不同的实例对象的静态同步方法之间,只要它们同一个类的实例对象!


/*
 * 判断打印的 "one" or "two" ?
 * 1. 两个普通同步方法,两个线程,标准打印, 打印? //one  two
 * 2. 新增 Thread.sleep() 给 getOne() ,打印? //one  two
 * 3. 新增普通方法 getThree() , 打印? //three  one   two
 * 4. 两个普通同步方法,两个 Number 对象,打印?  //two  one
 * 5. 修改 getOne() 为静态同步方法,打印?  //two   one
 * 6. 修改两个方法均为静态同步方法,一个 Number 对象?  //one   two
 * 7. 一个静态同步方法,一个非静态同步方法,两个 Number 对象?  //two  one
 * 8. 两个静态同步方法,两个 Number 对象?   //one  two
 * 
 * 线程八锁的关键:
 * ①非静态方法的锁默认为 this,  静态方法的锁为 对应的 Class 实例
 * ②某一个时刻内,只能有一个线程持有锁,无论几个方法。
 */
public class TestThread8Monitor {
	
	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 static synchronized void getOne(){//Number.class
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
		}
		
		System.out.println("one");
	}
	
	public synchronized void getTwo(){//this
		System.out.println("two");
	}
	
	public void getThree(){
		System.out.println("three");
	}
	
}

十一.线程池

  • Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。

  • JDK自带线程池体系结构UML
    这里写图片描述

  • 标记一下比较重要的类
    这里写图片描述

  • 第四种获取线程的方法:线程池,一个 ExecutorService,它使用可能的几个池线程之一执行每个提交的任务,通常使用Executors 工厂方法配置。

  • 线程池可以解决两个不同问题:由于减少了每个任务调用的开销,它们通常可以在 执行大量异步任务时提供增强的性能,并且还可以提供绑定和管理资源(包括执行 任务集时使用的线程)的方法。每个ThreadPoolExecutor 还维护着一些基本的统计数 据,如完成的任务数。

  • 为了便于跨大量上下文使用,此类提供了很多可调整的参数和扩展钩子 (hook)。但 是,强烈建议程序员使用较为方便的 Executors工厂方法 :
    > Executors.newCachedThreadPool()(无界线程池,可以进行自动线程回收)
    > Executors.newFixedThreadPool(int)(固定大小线程池)
    > Executors.newSingleThreadExecutor()(单个后台线程)
    它们均为大多数使用场景预定义了设置。

  • 线程调度ScheduledExecutorService,一个 ExecutorService,可安排在给定的延迟后运行或定期执行的命令。

package com.h.concurrent;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.*;

/**
 * Created by John on 2018/5/13.
 * 一、线程池:提供了一个线程队列,队列中保存着所有等待状态的线程。避免了创建与销毁额外开销,提高了响应的速度。
 *
 * 二、线程池的体系结构:
 * 	java.util.concurrent.Executor : 负责线程的使用与调度的根接口
 * 		|--**ExecutorService 子接口: 线程池的主要接口
 * 			|--ThreadPoolExecutor 线程池的实现类
 * 			|--ScheduledExecutorService 子接口:负责线程的调度
 * 				|--ScheduledThreadPoolExecutor :继承 ThreadPoolExecutor, 实现 ScheduledExecutorService
 *
 * 三、工具类 : Executors
 * ExecutorService newFixedThreadPool() : 创建固定大小的线程池
 * ExecutorService newCachedThreadPool() : 缓存线程池,线程池的数量不固定,可以根据需求自动的更改数量。
 * ExecutorService newSingleThreadExecutor() : 创建单个线程池。线程池中只有一个线程
 *
 * ScheduledExecutorService newScheduledThreadPool() : 创建固定大小的线程,可以延迟或定时的执行任务。
 */
public class TestThreadPool {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //1.创建线程池
        ExecutorService pool = Executors.newFixedThreadPool(5);
        List<Future<Integer>> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            //2.为线程池中的线程分配任务
            Future<Integer> future = pool.submit(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    System.out.println(Thread.currentThread().getName());
                    int sum = 0;
                    for (int i = 0; i <= 100; i++) {
                        sum += i;
                    }
                    return sum;
                }
            });

            list.add(future);
        }

        /**
         * 3.关闭线程池
         * pool.shutdown()会等待线程池的线程执行完任务才关闭
         * pool.shutdownnow()不会等待线程执行完任务就立即关闭线程
         */
        pool.shutdown();
        // pool.shutdownNow();

        //4.获取线程执行的结果
        for (Future<Integer> f:list){
            System.out.println(f.get());
        }

        System.out.println("==================");
        final Random random = new Random();
        //定时线程池
        ScheduledExecutorService schedulePool = Executors.newScheduledThreadPool(5);
        for (int i=0;i<5;i++){
            Future<Integer> result = schedulePool.schedule(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    int num = random.nextInt(100);
                    System.out.println(Thread.currentThread().getName() + ":" + num);
                    return num;
                }
            },1,TimeUnit.SECONDS);

            System.out.println(result.get());
        }

        schedulePool.shutdown();
    }
}

十二.ForkJoinPool 分支/合并框架

  • Fork/Join 框架:就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行 join 汇总。
    这里写图片描述
package com.h.concurrent;

import org.junit.Test;

import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
import java.util.stream.LongStream;

/**
 * Created by John on 2018/5/13.
 */
public class TestForkJoinPool {
    public static void main(String[] args) {
        Instant start = Instant.now();
        ForkJoinPool pool = new ForkJoinPool();
        ForkJoinTask<Long> task = new ForkJoinSumCalculate(0L,50000000000L);
        Long sum = pool.invoke(task);
        System.out.println(sum);
        Instant end = Instant.now();
        System.out.println("耗费时间为:" + Duration.between(start, end).toMillis());
    }

    @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>{//RecursiveAction
    private static final long serialVersionUID = -259195479995561737L;

    private long start;
    private long end;

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

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

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

}

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

  • 采用 “工作窃取”模式( work-stealing): 当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队 列中。
    这里写图片描述
  • 相对于一般的线程池实现, fork/join框架的优势体现在对其中包含的任务 的处理方式上.在一般的线程池中, 如果一个线程正在执行的任务由于某些 原因无法继续运行, 那么该线程会处于等待状态。 而在fork/join框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行。 那么处理 该子问题的线程会主动寻找其他尚未运行的子问题来执行.这种方式减少了 线程的等待时间, 提高了性能.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值