2.大数据学习之旅——红黑树

红黑树

自平衡二叉查找树 — 时间复杂度O(logn)

特征:

  1. 每一个节点非红即黑
  2. 根节点一定是黑色
  3. 所有的叶子节点一定是黑色的nil节点
  4. 红节点的子节点一定是黑节点
  5. 任意一条路径中的黑色节点个数一致
  6. 插入的节点一定是红色

修复:

  1. 当前节点为红,并且父节点且叔父节点为红,那么将父节点以及叔父
    节点涂黑,然后将祖父节点涂红
  2. 当前节点为红,并且父节点为红且叔父节点为黑,当前节点为右子
    叶,以当前节点为轴进行左旋
  3. 当前节点为红,并且父节点为红且叔父节点为黑,当前节点为左子
    叶,以父节点为轴进行右旋

ConcurrentNavigableMap - 并发导航映射

本身是一个接口,所以更多的是使用实现类 - ConcurrentSkipListMap - 并发
跳跃表映射
跳跃表:为了提高查询效率所产生一种数据结构 — 跳跃表是典型的以空
间换时间的产物
2.大数据学习之旅——红黑树_第1张图片

如果跳跃表中插入新的元素,新的元素是否往上提取遵循"抛硬币"原则 —
1/2原则 - 只要保证这个节点有一半的概率被提取就可以
跳跃表适合于大量查询而不增删的场景
扩展:B+树是MySQl的索引的建立机制

CountDownLatch - 闭锁 - 线程递减锁 - 对线程进行计数,当计数归零的时
候会放开阻塞让线程继续往下执行
2.大数据学习之旅——红黑树_第2张图片

package cn.tedu.concurrent.lock;
import java.util.concurrent.CountDownLatch;
CountDownLatch cdl = new CountDownLatch(5);
new Thread(new Teacher(cdl)).start();
new Thread(new Student(cdl)).start();
public static void main(String[] args) throws InterruptedException {
public class CountDownLatchDemo {
分区 Concurrent 的第 3new Thread(new Student(cdl)).start();
new Thread(new Student(cdl)).start();
new Thread(new Student(cdl)).start();
new Thread(new Student(cdl)).start();
// 表示让线程阻塞,直到计数归零的时候阻塞才能放开
cdl.await();
System.out.println("考试结束~~~");
}
}
private CountDownLatch cdl;
class Student implements Runnable {
this.cdl = cdl;
public Student(CountDownLatch cdl) {
}
@Override
System.out.println("学生来到考场,准备考试~~~");
Thread.sleep(1000);
try {
e.printStackTrace();
} catch (InterruptedException e) {
}
System.out.println("学生交卷离开考场");
// 计数-1
cdl.countDown();
public void run() {
}
}
private CountDownLatch cdl;
class Teacher implements Runnable {
this.cdl = cdl;
public Teacher(CountDownLatch cdl) {
}
@Override
public void run() {
System.out.println("老师来到考场,准备考试~~~");
Thread.sleep(5000);
try {
e.printStackTrace();
} catch (InterruptedException e) {
}
System.out.println("老师收卷离开了考场~~~");
cdl.countDown();
}
}

CyclicBarrier - 栅栏 - 当所有的线程都到达了同一个点之后才继续往下执行
2.大数据学习之旅——红黑树_第3张图片

package cn.tedu.concurrent.lock;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
CyclicBarrier cb = new CyclicBarrier(4);
new Thread(new Runner(cb), "1号").start();
new Thread(new Runner(cb), "2号").start();
new Thread(new Runner(cb), "3号").start();
new Thread(new Runner(cb), "4号").start();
public static void main(String[] args) {
}
}
private CyclicBarrier cb;
this.cb = cb;
public Runner(CyclicBarrier cb) {
}
class Runner implements Runnable {
Thread.sleep((long) (10000 * Math.random()));
try {
e.printStackTrace();
} catch (InterruptedException e) {
}
String name = Thread.currentThread().getName();
System.out.println(name + "选手到了起跑线~~~");
// 表示让当前线程陷入阻塞
// await每执行一次,就会自动减少一个计数
// 当计数减为0的时候,阻塞就会自动放开
// 也就意味着当所有的被计数的线程都到达这个点之后才会放开阻塞继续向下执行
cb.await();
try {
e.printStackTrace();
} catch (InterruptedException | BrokenBarrierException e) {
}
System.out.println(name + "选手听到枪响,跑了出去~~~~");
public void run() {
分区 Concurrent 的第 5 页
System.out.println(name + "选手听到枪响,跑了出去~~~~");
}
}

Exchanger - 交换机 - 用于交换两个线程之间的信息的

package cn.tedu.concurrent.lock;
import java.util.concurrent.Exchanger;
public class ExchangerDemo {
public static void main(String[] args) {
Exchanger<String> ex = new Exchanger<>();
new Thread(new Spy1(ex)).start();
new Thread(new Spy2(ex)).start();
}
}
class Spy1 implements Runnable {
// 泛型表示交换的信息类型
private Exchanger<String> ex;
this.ex = ex;
public Spy1(Exchanger<String> ex) {
}
@Override
public void run() {
String info = "天王盖地虎";
// 要和第二个线程交换信息
// 获取到了交换过来的信息
String msg = ex.exchange(info);
System.out.println("间谍1收到了间谍2的信息:" + msg);
try {
e.printStackTrace();
} catch (InterruptedException e) {
}
}
}
class Spy2 implements Runnable {
private Exchanger<String> ex;
this.ex = ex;
public Spy2(Exchanger<String> ex) {
}
@Override
public void run() {
String info = "宝塔镇河妖";
分区 Concurrent 的第 6// 和第一个线程交换信息
// 换过来第一个线程的信息
String msg = ex.exchange(info);
System.out.println("间谍2收到了间谍1的信息:" + msg);
try {
e.printStackTrace();
} catch (InterruptedException e) {
}
}
}

Semaphore - 信号量 - 用于计数 - 信号在被用完之后,后来的线程就会被
阻塞,直到有信号被归还,被阻塞的线程才能取得信号继续执行

package cn.tedu.concurrent.lock;
import java.util.concurrent.Semaphore;
Semaphore s = new Semaphore(10);
new Thread(new Restaurant(s)).start();
for (int i = 0; i < 15; i++) {
}
public static void main(String[] args) {
}
public class SemaphoreDemo {
}
class Restaurant implements Runnable {
private Semaphore s;
this.s = s;
public Restaurant(Semaphore s) {
}
@Override
// 获取到了1个信号,使计数-1
s.acquire();
try {
e.printStackTrace();
} catch (InterruptedException e) {
}
System.out.println("一位客人占用了一张桌子~~~");
Thread.sleep((long) (10000 * Math.random()));
try {
e.printStackTrace();
} catch (InterruptedException e) {
}
System.out.println("一位客人起身离开,空出一张桌子~~~");
// 释放1个信号,使计数+1
s.release();
public void run() {
}
分区 Concurrent 的第 7}

线程池

线程池 - 如果每一个请求对应一个线程,那么会导致线程大量的创建和销
毁。减少线程的创建和销毁,希望能够重复使用已有的线程,有了线程
池 — 存储线程的队列

特点:

  1. 线程池在创建的时候里面是没有线程的
  2. 当过来请求的时候,就会线程池中创建一个线程来处理这个请求。当
    请求处理完毕的时候,线程就会还回线程池,等待下一个请求
  3. 核心线程在线程池中需要限定数量 3.
    如果所有的核心线程都在使用,那么再来的请求就会放入工作队列
    中。工作队列是一个阻塞式队列。
  4. 如果所有的核心线程都被占用并且工作队列已满,那么会创建临时线
    程去处理新的请求
  5. 临时线程处理完请求之后并不是立即销毁,而是存活一段时间,如果
    过了这段时间依然没有新的请求,那么临时线程就被销毁

2.大数据学习之旅——红黑树_第4张图片

package cn.tedu.concurrent.pool;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolDemo {
public static void main(String[] args) {
// 执行器服务
// corePoolSize - 核心线程数量
// maximumPoolSize - 线程池的容量 = 核心线程数量 + 临时线程数量
// keepAliveTime - 临时线程的存活事件
// unit - 时间单位
// workQueue - 工作队列
// handler - 拒绝执行助手
new ArrayBlockingQueue<Runnable>(5), new RejectedExecutionHandler() {
ExecutorService es = new ThreadPoolExecutor(5, 10, 3000, TimeUnit.MILLISECONDS,
@Override
System.out.println(new Thread(r).getName() + "线程被拒绝~~~");
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
}
});
// 执行线程
es.execute(new RDemo());
for (int i = 0; i < 17; i++) {
}
// 关闭线程池
es.shutdown();
}
}
class RDemo implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":hello");
Thread.sleep(8000);
try {
e.printStackTrace();
} catch (InterruptedException e) {
}
}
}

java提供的线程池

package cn.tedu.concurrent.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolDemo2 {
分区 Concurrent 的第 9public static void main(String[] args) {
// 获取缓存线程池
// 特点:
// 1. 没有核心线程,所有线程都是临时线程
// 2. 线程池的容量可以认为是无限的
// 3. 每一个临时线程存活时间都是1min - 存活时间不长
// 4. 工作队列是一个同步队列 - 只能存储一个元素
// 总结:
// 1. 大池子小队列
// 2. 理论上能够处理任意多的请求
// 3. 适合于短任务场景,例如:聊天
// ExecutorService es = Executors.newCachedThreadPool();
// 混合线程池
// 特点:
// 1. 需要指定核心线程数量
// 2. 只有核心线程,没有临时线程
// 3. 工作队列是阻塞式链式队列,没有指定容量
// 总结:
// 1. 小池子大队列
// 2. 理论上能够存储任意多的请求
// 3. 适合于长任务场景,例如:文件下载
ExecutorService es = Executors.newFixedThreadPool(5);
}
}

Callable和Runnable的区别:

  1. Runnable线程可以通过Thread启动执行,也可以通过线程池启动执行;
    Callable只能通过线程池来执行
  2. Runnable没有容错机制,一旦出现异常需要自己处理;Callable可以将
    异常抛出利用全局的方式来进行处理

ScheduledExecutorService - 定时执行者服务

package cn.tedu.concurrent.pool;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledExecutorServiceDemo {
public static void main(String[] args) {
// 创建了定时线程池
// 创建了定时线程池
ScheduledExecutorService ses = Executors.newScheduledThreadPool(5);
// 定时执行
// command - 线程
// delay - 推迟的时间数量
// unit - 时间单位
// 表示线程池开启之后,延迟5s再初始化并且执行线程
ses.schedule(new ScheduledRunnable(), 5, TimeUnit.SECONDS);
ses.shutdown();
}
}
class ScheduledRunnable implements Runnable {
@Override
System.out.println("hello");
public void run() {
}
}
package cn.tedu.concurrent.pool;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledExecutorServiceDemo2 {
public static void main(String[] args) {
ScheduledExecutorService ses = Executors.newScheduledThreadPool(5);
// 每5秒钟循环执行一个线程
// command - 线程
// initialDelay - 初始延迟时间 - 当线程池启动以后,线程需要延迟多长时间才能执行
// period - 间隔时间
// unit - 时间单位
// 以上一次的起始时间开始计算
// 如果线程的执行时间超过了指定的间隔时间
// 那么第二次线程不会立即启动而是等上一个线程执行完成之后再启动执行
// ses.scheduleAtFixedRate(new RDemo2(), 0, 5, TimeUnit.SECONDS);
// delay - 表示线程上次完成之后需要间隔多长时间再执行下一次
// 以上一次的结束实际开始计算
ses.scheduleWithFixedDelay(new RDemo2(), 0, 5, TimeUnit.SECONDS);
// 凌晨1点对数据进行更新
// 获取当前时间 - 计算当前时间到凌晨一点的时间间隔
// 每执行一次这个方法,就在当前时间上加上1天
}
}
class RDemo2 implements Runnable {
@Override
System.out.println("hi");
try {
e.printStackTrace();
} catch (InterruptedException e) {
}
}
}

分叉合并 - For and Join

将一个大任务不断的分成许多个小任务(分叉),小任务在执行完成之后
再将结果进行汇总(合并)
任务在分叉之后会分发到CPU不同的核上去,然后利用核进行数据的处
理。原来的for循环只是在一个核上来执行的代码,所以就导致分叉合并的
效率要高于for循环
分叉合并采取了work-stealing(工作窃取)策略来实现线程的高效操作 -当
一个核上的所有线程执行完成,就会随机挑一个核,从这个核的线程队列
中偷取最后一个线程回来,然后执行
2.大数据学习之旅——红黑树_第5张图片

分叉合并会带来资源共享问题

2.大数据学习之旅——红黑树_第6张图片

package cn.tedu.concurrent.pool;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
public class ForkJoinPoolDemo {
public static void main(String[] args) {
long start = System.currentTimeMillis();
ForkJoinPool fk = new ForkJoinPool();
Long sum = fk.invoke(new Calc(1, 100000000000L));
fk.shutdown();
System.out.println(sum);
// long sum = 0;
// for(long i = 1; i <= 100000000000L; i++)
// sum += i;
// System.out.println(sum);
long end = System.currentTimeMillis();
System.out.println(end - start);
}
}
// RecursiveTask和RecursiveAction都可以实现分叉合并
// RecursiveTask表示有返回结果
// RecursiveAction不需要返回结果
class Calc extends RecursiveTask<Long> {
private static final long serialVersionUID = 2145165687757218781L;
private long start;
private long end;
private static final long THREHOLD = 1000; // 阈值
this.start = start;
this.end = end;
public Calc(long start, long end) {
}
@Override
protected Long compute() {
// 如果start-end之间的数字个数>阈值,需要继续进行分叉
// 如果start-end之前的数字个数<=阈值,进行求和
long count = end - start;
if (count <= THREHOLD) {
long sum = 0;
sum += i;
for (long i = start; i <= end; i++)
return sum;
} else {
long mid = (start + end) / 2;
Calc left = new Calc(start, mid);
Calc right = new Calc(mid + 1, end);
// 将这个线程分叉出去
left.fork();
right.fork();
return left.join() + right.join();
}
}
}

Lock

比synchronized更加的灵活
公平和非公平策略:
非公平策略是当线程释放锁之后依然可以再次抢占;
公平策略是当线程释放锁之后会到队列中让其他线程进行抢占
从效率上考虑,非公平的效率会高

package cn.tedu.concurrent.lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockDemo {
public static void main(String[] args) {
Product p = new Product();
// 默认是非公平的
// Lock l = new ReentrantLock();
// 表示将其设置为公平策略
Lock l = new ReentrantLock(true);
new Thread(new Producer(p, l)).start();
new Thread(new Consumer(p, l)).start();
}
}
class Product {
private int count;
return count;
public int getCount() {
}
public void setCount(int count) {
}
}
class Producer implements Runnable {
private Product p;
private Lock l;
this.p = p;
this.l = l;
public Producer(Product p, Lock l) {
}
@Override
public void run() {
// 加锁
l.lock();
while (true) {
int count = (int) (Math.random() * 1000 + p.getCount());
p.setCount(count);
System.out.println("生产了" + count);
// 解锁
l.unlock();
}
}
}
class Consumer implements Runnable {
private Product p;
private Lock l;
this.p = p;
this.l = l;
public Consumer(Product p, Lock l) {
}
@Override
l.lock();
while (true) {
public void run() {
int count = (int) (p.getCount() * Math.random());
p.setCount(p.getCount() - count);
System.out.println("本次剩余了" + p.getCount());
l.unlock();
}
}
}

ReadWriteLock - 读写锁
readlock:允许多个线程读,但是不允许线程写(增加/删除/更新)
writelock:允许一个线程写,但是不允许线程读

原子性操作

2.大数据学习之旅——红黑树_第7张图片

volatile本身是一个关键字,保证属性在多线程场景下的原子性
A — 1 -> 2 -> 3
线程在底层的执行过程中可能产生的指令的错乱 — 寄存器/PC计数器
A — 1 ->3 -> 2
导致实际结果和预期结果不符
volatile — 限定线程在执行过程中必须按照给定的指令顺序来执行

package cn.tedu.concurrent.atomic;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicDemo2 {
// jdk1.8之前是 volatile + 锁
// jdk1.8开始是 volatile + CAS
static AtomicInteger ai = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
CountDownLatch cdl = new CountDownLatch(2);
new Thread(new Add2(cdl)).start();
new Thread(new Add2(cdl)).start();
cdl.await();
System.out.println(ai.get());
}
}
class Add2 implements Runnable {
private CountDownLatch cdl;
this.cdl = cdl;
public Add2(CountDownLatch cdl) {
}
@Override
public void run() {
// AtomicDemo2.ai.addAndGet(1);
// 默认+1
AtomicDemo2.ai.incrementAndGet();
}
cdl.countDown();
}
}

上一篇 1.大数据学习之旅——NIO

你可能感兴趣的:(大数据学习之旅,大数据)