目录:

     1-1线程概念

    2-1.线程的基本操作

    2-2 synchronied和同步问题

    2-3 volatile关键字的理解和使用JMM

    3-1 jdk并发包-ReentrantLock 

    3-2 常用的线程池模式以及不同线程池的使用场景

    4-2 Thread local

    4-1 提高锁性能的建议

   5-1 CAS

1-1 线程概念

1. 同步(Synchronous)和异步(Asynchronous

2. 并发和并行

3. 临界区

表示一种公共资源或者说是共享数据,可以被多个线程使用。但是每一次,只能有一个线程使用它,一旦临界区资源被占用,其他线程想要使用这个资源,就必须等待。

4. 阻塞与非阻塞

5. 死锁、饥饿、活锁

死锁:在一组线程中每个线程都在等待仅由该组线程中的其他线程才能触发的条件。

饥饿:是指某一个或者多个线程因为种种原因无法获取所需要的资源,导致一直无法执行。(比如线程级别太低)

活锁:两个线程在交替掌握资源,但又不能完整使用。出现没有一个线程可以同时拿到所有资源而正常执行。

6. 并发级别:阻塞、无饥饿、无阻碍、无锁、无等待

7. 并发的两个重要定律:AmdahlGustafson定律

2-1.线程的基本操作

2.1 创建线程

创建线程的第一种方式:继承Thread类。

步骤:

1,定义类继承Thread

2,复写Thread类中的run方法。

目的:将自定义代码存储在run方法。让线程运行。

3,调用线程的start方法,

该方法两个作用:启动线程,调用run方法。

 

多线程的一个特性:

随机性。谁抢到谁执行,至于执行多长,cpu说的算。

 

线程都有自己默认的名称Thread-编号 该编号从0开始

 

创建线程的第二种方式:实现Runable接口

步骤:

1,定义类实现Runnable接口

2,覆盖Runnable接口中的run方法。

将线程要运行的代码存放在该run方法中。

3,通过Thread类建立线程对象。

4,将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。

为什么要将Runnable接口的子类对象传递给Thread的构造函数。因为,自定义的run方法所属的对象是Runnable接口的子类对象。所以要让线程去指定指定对象的run方法。就必须明确该run方法所属对象。

5,调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。


Thread和Runnable的区别:

    避免点继承的局限,一个类可以继承多个接口。

    适合于资源的共享

 

创建线程的第种方式:通过 Callable Future 创建线程

 

创建线程的第种方式:通过线程池创建线程

2.1.1 Java创建线程后直接调用start()run()的区别

01)调用run()方法跟普通方法一样按序执行,可以重复多次调用。

02)调用start()方法就是创建一个线程和主线程交替执行,不能多次启动一个线程。

用户线程和守护线程

 

Start()方法用来启动一个线程,真正实现多线程,无需等待run方法代码的执行完毕,通过thread类的start()方法来启动一个线程,这时线程处于就绪状态,并没有执行,而是通过执行thread类的run()方法,执行线程体。

 

1.start()方法来启动线程,并在新线程中运行 run()方法,真正实现了多线程运行。这时无需等待 run 方法体代码执行完毕,可以直接继续执行下面的代码;通过调用 Thread 类的 start()方法来启动一个线程,这时此线程是处于就绪状态,并没有运行,然后通过此 Thread 类调用方法 run()来完成其运行操作,这里方法 run()称为线程体,它包含了要执行的这个线程的内容,run()方法运行结束,此线程终止。然后 CPU 再调度其它线程。

2.直接调用 run()方法的话,会把 run()方法当作普通方法来调用,会在当前线程中执行 run()方法,而不会启动新线程来运行 run()方法。程序还是要顺序执行,要等待 run 方法体执行完毕后,才可继续执行下面的代码; 程

序中只有主线程——这一个线程, 其程序执行路径还是只有一条, 这样就没有达到多线程的目的。

 

2.2 终止线程

Stop():当一个线程对文件上锁,进行写操作,当stop()后,文件锁立即被释放,但操作执行了一半才,另外一个线程进来就会有问题。

线程中断

建议使用”异常法”来终止线程的继续运行。在想要被中断执行的线程中,调用 interrupted()方法,该方法用来检验当前线程是否已经被中断,即该线程是否被打上了中断的标记,并不会使得线程立即停止运行,如果返回 true,则抛出异常,停止线程的运行。在线程外,调用 interrupt()方法,使得该线程打上中断的标记。

Thread.interrupt():是一个实例方法,通知目标线程中断,也就是设置中断标志位。中断标志位表示当前线程已经被中断。

Thread.isInterrupted():实例方法,判断当前线程是否被中断(通过检查中断标志位)

Thread.interrupted():判断当前中断标志位,但也同时会清除当前线程的中断标志位状态

 

注:Thread.sleep()会让当前线程休眠一段时间,会抛出InterruptedException中断异常。InterruptedException不是运行时异常,也就是说程序必须捕获并且处理它,当线程在sleep休眠时,如果被中断,就会产生。

2.3 等待(wait)和通知(notify)

Object.wait()方法不是可以随便调用,必须包含在synchronized语句中,无论是wait或者notify都需要先获得目标对象的一个监视器。

2.4 挂起(suspend)和继续执行(resume)线程

都是废弃线程,因为当挂起意外的在suspend前执行,那么挂起的线程就可能很那有机会被继续执行,而且被占用的锁不会被释放,可能导致整个系统工作不正常。

 

2.5 等待线程结束(join)和谦让(yield)

Join:会让其他线程等待此线程执行完毕再执行

Yield:会让出当前CPU,然后重新去竞争

 

2-2 synchronied和同步问题

01)同步问题出现的原因:当多条语句在操作同一个 线程共享数据 时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。

02)同步的方法:

同步代码块(锁是Object对象或其子类)、同步函数(函数需要被对象调用。那么函数都有一个所属对象引用。就是this。所以同步函数使用的锁是this(当前对象))、静态的同步方法,使用的锁是该方法所在类的字节码文件对象:类名.class

03)synchronizedstatic synchronized的区别

正确的加锁程序:(注意main函数,是同步一个对象,里面synchronized同步一个实例方法,所以它或得本类对象为锁)

 

下面是一个有问题的同步程序:(主要看main方法中的对象,对象不同,synchronized不能对不同实例对象做到同步,所以可以用static synchronized

 

2-3 volatile关键字的理解和使用JMM

JMM(Java内存模型)

    原子性:指一个操作不可中断,一个线程开始执行,不会被其他线程干扰。

    可见性;是指当一个线程修改了某一个共享变量的值,其他线程可以立即知道这个修改。

    有序性:并发时,程序的执行顺序可能出现乱序(比如:写在前面的代码,会在后面执行),是因为程序在执行时,可能会进行指令重排。(volatile修饰的变量不会发生指令重排)

 

volatile 变量提供了线程的可见性,并不能保证线程安全性和原子性。

什么是线程的可见性:

锁提供了两种主要特性:互斥(mutual exclusion 和可见性(visibility)。互斥即一次只允许一个线程持有某个特定的锁,因此可使用该特性实现对共享数据的协调访问协议,这样,一次就只有一个线程能够使用该共享数据。可见性要更加复杂一些,它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的 —— 如果没有同步机制提供的这种可见性保证,线程看到的共享变量可能是修改前的值或不一致的值,这将引发许多严重问题。

具体看volatile的语义:

1)保证了不同线程对这个变量进行读取时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。(volatile 解决了线程间共享变量的可见性问题)

第一:使用 volatile 关键字会强制将修改的值立即写入主存;

第二:使用 volatile 关键字的话,当线程 2 进行修改时,会导致线程 1 的工作内存中缓存变量 stop 的缓存行无效(反映到硬件层的话,就是 CPU L1或者 L2 缓存中对应的缓存行无效);

第三:由于线程 1 的工作内存中缓存变量 stop 的缓存行无效,所以线程 1再次读取变量 stop 的值时会去主存读取。

2)禁止进行指令重排序,阻止编译器对代码的优化。

volatile 关键字禁止指令重排序有两层意思:

I)当程序执行到 volatile 变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;

II)在进行指令优化时,不能把 volatile 变量前面的语句放在其后面执行,也不能把 volatile 变量后面的语句放到其前面执行。

volatile需要配合锁去使用才能实现原子性,否则在多线程操作的情况下依然不够用,在程序中,count变量(当前Segment中的key-value对个数)通过volatile修饰,实现内存可见性(关于内存可见性以后会仔细去记录,这里列出大概的一个流程)在有锁保证了原子性的情况下

  • 当我们读取count变量的时候,会强制从主内存中读取count的最新值

  • 当我们对count变量进行赋值之后,会强制将最新的count值刷到主内存中去

  • 通过以上两点,我们可以保证在高并发的情况下,执行这段流程的线程可以读取到最新值

2-4 Atomic

Atomic类的作用

· 使得让对单一数据的操作,实现了原子化

· 使用Atomic类构建复杂的,无需阻塞的代码

访问对2个或2个以上的atomic变量(或者对单个atomic变量进行2次或2次以上的操作)通常认为是需要同步的,以达到让这些操作能被作为一个原子单元。

 

无锁定且无等待算法

基于 CAS compare and swap)的并发算法称为 无锁定算法,因为线程不必再等待锁定(有时称为互斥或关键部分,这取决于线程平台的术语)。无论 CAS 操作成功还是失败,在任何一种情况中,它都在可预知的时间内完成。如果 CAS 失败,调用者可以重试 CAS 操作或采取其他适合的操作。


3-1 jdk并发包-ReentrantLock

1.1 什么是reentrantlock

java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。这就为 Lock 的多种实现留下了空间,各种实现可能有不同的调度算法、性能特性或者锁定语义。 ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。(换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。)

1.2 ReentrantLock与synchronized的比较

相同:ReentrantLock提供了synchronized类似的功能和内存语义。

不同:

(1)ReentrantLock功能性方面更全面,比如时间锁等候,可中断锁等候,锁投票等,因此更有扩展性。在多个条件变量和高度竞争锁的地方,用ReentrantLock更合适,ReentrantLock还提供了Condition,对线程的等待和唤醒等操作更加灵活,一个ReentrantLock可以有多个Condition实例,所以更有扩展性。

(2)ReentrantLock 的性能比synchronized会好点。

(3)ReentrantLock提供了可轮询的锁请求,他可以尝试的去取得锁,如果取得成功则继续处理,取得不成功,可以等下次运行的时候处理,所以不容易产生死锁,而synchronized则一旦进入锁请求要么成功,要么一直阻塞,所以更容易产生死锁。

 

可重入锁:

ReentrantLock 和 synchronized 都是可重入锁。

如果当前线程已经获得了某个监视器对象所持有的锁,那么该线程在该方法中调用另外一个同步方法也同样持有该锁。

1.3 ReentrantLock扩展的功能(三个建锁方式)

Lock.lock();lock.lock();可以写多次:可重入

1.3.1 实现可轮询的锁请求 tryLock

Lock.lock();lock.lock();可以写多次:可重入

在内部锁中,死锁是致命的——唯一的恢复方法是重新启动程序,唯一的预防方法是在构建程序时不要出错。而可轮询的锁获取模式具有更完善的错误恢复机制,可以规避死锁的发生。 
如果你不能获得所有需要的锁,那么使用可轮询的获取方式使你能够重新拿到控制权,它会释放你已经获得的这些锁,然后再重新尝试。可轮询的锁获取模式,由tryLock()方法实现。此方法仅在调用时锁为空闲状态才获取该锁。如果锁可用,则获取锁,并立即返回值true。如果锁不可用,则此方法将立即返回值false。此方法的典型使用语句如下: 

[java] view plain copy

1. Lock lock = ...;   

2. if (lock.tryLock()) {   

3. try {   

4. // manipulate protected state   

5. finally {   

6. lock.unlock();   

7. }   

8. else {   

9. // perform alternative actions   

10. }   

 

1.3.2 实现可定时的锁请求 tryLock(long, TimeUnit)

 

当使用内部锁时,一旦开始请求,锁就不能停止了,所以内部锁给实现具有时限的活动带来了风险。为了解决这一问题,可以使用定时锁。当具有时限的活动调用了阻塞方法,定时锁能够在时间预算内设定相应的超时。如果活动在期待的时间内没能获得结果,定时锁能使程序提前返回。可定时的锁获取模式,由tryLock(long, TimeUnit)方法实现。 

 

1.3.3 实现可中断的锁获取请求 lockInterruptibly

可中断的锁获取操作允许在可取消的活动中使用。lockInterruptibly()方法能够使你获得锁的时候响应中断。

1.3.4 公平锁reentrantLock(boolean fair)

Public reentrantLock(boolean fair):默认是非公平,如果是公平锁,要求系统维护一个有序队列,因为公平锁维护成本高,性能低下。

1.4 ReentrantLock不好与需要注意的地方

(1) lock 必须在 finally 块中释放。否则,如果受保护的代码将抛出异常,锁就有可能永远得不到释放!这一点区别看起来可能没什么,但是实际上,它极为重要。忘记在 finally 块中释放锁,可能会在程序中留下一个×××,当有一天×××爆炸时,您要花费很大力气才有找到源头在哪。而使用同步,JVM 将确保锁会获得自动释放

(2) 当 JVM 用 synchronized 管理锁定请求和释放时,JVM 在生成线程转储时能够包括锁定信息。这些对调试非常有价值,因为它们能标识死锁或者其他异常行为的来源。 Lock 类只是普通的类,JVM 不知道具体哪个线程拥有 Lock 对象。

1.5 条件变量Condition

条件变量很大一个程度上是为了解决Object.wait/notify/notifyAll难以使用的问题。

条件(也称为条件队列 或条件变量)为线程提供了一个含义,以便在某个状态条件现在可能为 true 的另一个线程通知它之前,一直挂起该线程(即让其“等待”)。因为访问此共享状态信息发生在不同的线程中,所以它必须受保护,因此要将某种形式的锁与该条件相关联。等待提供一个条件的主要属性是:以原子方式 释放相关的锁,并挂起当前线程,就像 Object.wait 做的那样。

每一个Lock可以有任意数据的Condition对象,Condition是与Lock绑定的,所以就有Lock的公平性特性:如果是公平锁,线程为按照FIFO的顺序从Condition.await中释放,如果是非公平锁,那么后续的锁竞争就不保证FIFO顺序了。 

2.1 await* 操作

上一节中说过多次ReentrantLock是独占锁,一个线程拿到锁后如果不释放,那么另外一个线程肯定是拿不到锁,所以在lock.lock()和lock.unlock()之间可能有一次释放锁的操作(同样也必然还有一次获取锁的操作)。我们再回头看代码,不管take()还是put(),在进入lock.lock()后唯一可能释放锁的操作就是await()了。也就是说await()操作实际上就是释放锁,然后挂起线程,一旦条件满足就被唤醒,再次获取锁!

这里再回头介绍Condition的数据结构。我们知道一个Condition可以在多个地方被await*(),那么就需要一个FIFO的结构将这些Condition串联起来,然后根据需要唤醒一个或者多个(通常是所有)。所以在Condition内部就需要一个FIFO的队列。

2.2 signal/signalAll 操作

await*()清楚了,现在再来看signal/signalAll就容易多了。按照signal/signalAll的需求,就是要将Condition.await*()FIFO队列中第一个Node唤醒(或者全部Node)唤醒。尽管所有Node可能都被唤醒,但是要知道的是仍然只有一个线程能够拿到锁,其它没有拿到锁的线程仍然需要自旋等待,就上上面提到的第4步(acquireQueued)。

 

1.6 允许多个线程同时访问:信号量(semaphore)

使用信号量可以允许多个线程同时访问某一个资源(需要指定线程数量)

1.7 readwritelock

读写分离锁,可以有效减少锁竞争,提升系统性能。

1.8 倒计时器:countdownlatch

,可以用来控制线程等待,控制线程等待到倒计时结束。

 

1.9循环栅栏(增强倒计时器)

1.10 线程阻塞工具类locksupport

可以在线程内任意位置让线程阻塞,比起Thread.suspend(),弥补了由于resume()在前发生的危险;和wait相比,不需要先获得某个对象的锁,也不会抛出interruptedException

3-2 常用的线程池模式以及不同线程池的使用场景

定义:线程池根据系统自身的环境情况,有效的限制执行线程的数量,使运行效果达到最佳。提高性能,控制响应数量。

风险同步错误、死锁、线程泄露

线程池的注意事项

虽然线程池是构建多线程应用程序的强大机制,但使用它并不是没有风险的。

(1)线程池的大小。多线程应用并非线程越多越好,需要根据系统运行的软硬件环境以及应用本身的特点决定线程池的大小。一般来说,如果代码结构合理的话,线程数目与 CPU数量相适合即可。如果线程运行时可能出现阻塞现象,可相应增加池的大小;如有必要可采用自适应算法来动态调整线程池的大小,以提高 CPU 的有效利用率和系统的整体性能。

    线程池线程数量

    Ncpu = cpu数量

    Ucpu = 目标CPU的使用率

    W/C = 等待时间与计算时间的比率

    Nthreads = ncpu * ucpu * (1 + W/C)


(2)并发错误。多线程应用要特别注意并发错误,要从逻辑上保证程序的正确性,注意避免死锁现象的发生。

(3)线程泄漏。这是线程池应用中一个严重的问题,当任务执行完毕而线程没能返回池中就会发生线程泄漏现象。比如线程放在集合类中,当使用完毕,集合类对象没有清理,但无法回收线程。

 

线程池常用的接口和类

分类:Java通过Executors提供四种线程池

newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。

两种提交任务的方法

    ExecutorService 提供了两种提交任务的方法:

        execute():提交不需要返回值的任务

        submit():提交需要返回值的任务

execute

    void execute(Runnable command);

execute() 的参数是一个 Runnable,也没有返回值。因此提交后无法判断该任务是否被线程池执行成功。

ExecutorService executor = Executors.newCachedThreadPool();
executor.execute(new Runnable() {
    @Override
    public void run() {
        //do something
    }
});

submit

 Future submit(Callable task);
 Future submit(Runnable task, T result);
Future submit(Runnable task);

submit() 有三种重载,参数可以是 Callable 也可以是 Runnable。

同时它会返回一个 Funture 对象,通过它我们可以判断任务是否执行成功。

Callable 和Runnable区别:

    相同点:

    两者都是接口;

    两者都可用来编写多线程程序;

    两者都需要调用Thread.start()启动线程;

    不同点:

    两者最大的不同点是:实现Callable接口的任务线程能返回执行结果;而实现Runnable接口的任务线程不能返回结果;

    Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛;

    注意点:

    Callable接口支持返回执行结果,此时需要调用FutureTask.get()方法实现,此方法会阻塞主线程直到获取‘将来’结果;当不调用此方法时,主线程不会阻塞!


关闭线程池

    线程池即使不执行任务也会占用一些资源,所以在我们要退出任务时最好关闭线程池。


有两个方法关闭线程池:

    shutdown() 

        将线程池的状态设置为 SHUTDOWN,然后中断所有没有正在执行的线程

    shutdownNow() 

        将线程池设置为 STOP,然后尝试停止所有线程,并返回等待执行任务的列表

    它们的共同点是:都是通过遍历线程池中的工作线程,逐个调用 Thread.interrup() 来中断线程,所以一些无法响应中断的任务可能永远无法停止(比如 Runnable)。

3.1 线程池内部实现

可以发现,下面几种线程池内部均使用了ThreadPoolExecutor。

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue());
}
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue()));
}
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue());
}


内部均使用了ThreadPoolExecutor:这个的构造函数里面有

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)


1. corePoolSize:(线程池中的线程数量)

2. Maximumpoolsize:线程最大数量,

3. long keepAliveTime:当线程池线程数量超过corePoolSize时,多余的空闲线程的存活时间。

4. TimeUnit unit:keepAliveTime的单位

5. workQueue:一个重要的就是用来存放线程的队列BlockingQueue

常见的队列有:直接提交的队列(SychronousQueue)、有界队列(ArrayBlockingQueue)、×××的任务队列(LinkedBlockingQueue)、优先任务队列(priorityBlockingQueue

6. threadFactory:线程工厂(用来创建线程)

7. Handler(拒绝策略)

当线程池和等待队列都满了以后,可以使用拒绝策略:abortpolicy(抛出异常)、callerrunspolicy(会直接在调用者线程中,运行当前被丢弃的任务)、丢弃最老请求,也就是即将被执行的任务、丢弃无法处理的任务

当上面四种不满足需求,可以通过rejectExecutionHandler接口自己定义


自定义线程:

    public static void main(String[] args) {
		MyTask task = new MyTask();
		ExecutorService es = new ThreadPoolExecutor(5,5,
				0L,TimeUnit.SECONDS,
                new SynchronousQueue(),  
                new ThreadFactory() {
					public Thread newThread(Runnable r) {
						Thread t = new Thread(r);
						return t;
					}
				},
                new ThreadPoolExecutor.DiscardOldestPolicy()
         );  
  
        for (int i = 1; i < 5 ;i++){  
            es.submit(task);
        }  
    }


3.2 扩展线程池

ThreadpollExecutor可以扩展,提供了beforeExecutor()、afterExecutor()、terminated()三个接口对线程池进行控制。


3.3 fork/join框架

Linux中Fork()用来创建子进程,使得系统进程可以多一个执行分支。

3.4 newFixedThreadPool此种线程池如果线程数达到最大值后会怎么办

创建一个可重用固定线程数的线程池,以共享的×××队列方式来运行这些线程。在任意点,在大多数 nThreads 线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之前,池中的线程将一直存在。


3.5 线程池使用案例


定长线程池案例:

import kafka.consumer.Consumer;
import kafka.consumer.ConsumerConfig;
import kafka.consumer.ConsumerIterator;
import kafka.consumer.KafkaStream;
import kafka.javaapi.consumer.ConsumerConnector;
import kafka.message.MessageAndMetadata;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class KafkaConsumerSimple implements Runnable {
    public String title;
    public KafkaStream stream;
    public KafkaConsumerSimple(String title, KafkaStream stream) {
        this.title = title;
        this.stream = stream;
    }
    @Override
    public void run() {
        System.out.println("开始运行 " + title);
        ConsumerIterator it = stream.iterator();
        /**
         * 不停地从stream读取新到来的消息,在等待新的消息时,hasNext()会阻塞
         * 如果调用 `ConsumerConnector#shutdown`,那么`hasNext`会返回false
         * */
        while (it.hasNext()) {
            MessageAndMetadata data = it.next();
            String topic = data.topic();
            int partition = data.partition();
            long offset = data.offset();
            String msg = new String(data.message());
            System.out.println(String.format(
                    "Consumer: [%s],  Topic: [%s],  PartitionId: [%d], Offset: [%d], msg: [%s]",
                    title, topic, partition, offset, msg));
        }
        System.out.println(String.format("Consumer: [%s] exiting ...", title));
    }

    public static void main(String[] args) throws Exception{
        Properties props = new Properties();
        props.put("group.id", "dashujujiagoushi");
        props.put("zookeeper.connect", "zk01:2181,zk02:2181,zk03:2181");
        props.put("auto.offset.reset", "largest");
        props.put("auto.commit.interval.ms", "1000");
        props.put("partition.assignment.strategy", "roundrobin");
        ConsumerConfig config = new ConsumerConfig(props);
        String topic1 = "orderMq";
        String topic2 = "paymentMq";
        //只要ConsumerConnector还在的话,consumer会一直等待新消息,不会自己退出
        ConsumerConnector consumerConn = Consumer.createJavaConsumerConnector(config);
        //定义一个map
        Map topicCountMap = new HashMap<>(); 
        topicCountMap.put(topic1, 3);
        //Map> 中String是topic, List是对应的流
        Map>> topicStreamsMap = consumerConn.createMessageStreams(topicCountMap);
        //取出 `kafkaTest` 对应的 streams
        List> streams = topicStreamsMap.get(topic1);
        //创建一个容量为4的线程池
        ExecutorService executor = Executors.newFixedThreadPool(3);
        //创建20个consumer threads
        for (int i = 0; i < streams.size(); i++)
            executor.execute(new KafkaConsumerSimple("消费者" + (i + 1), streams.get(i)));
    }
}



周期调度任务

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

class ScheduledTask1 implements Runnable{
    AtomicInteger index = new AtomicInteger(0);
    public void run() {
        int tmp_index = index.addAndGet(1);
        System.out.println("Thread name:"+Thread.currentThread().getName()+". Task1 begin running. index:"+tmp_index+" Current time:"+System.currentTimeMillis());
        try {
            Thread.sleep(10*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Task1 end running. index:"+tmp_index+" Current time:"+System.currentTimeMillis());
    }
}

class ScheduledTask2 implements Runnable{
    AtomicInteger index = new AtomicInteger(100);
    public void run() {
        int tmp_index = index.addAndGet(1);
        System.out.println("Thread name:"+Thread.currentThread().getName()+". Task2 begin running. index:"+tmp_index+" Current time:"+System.currentTimeMillis());
        try {
            Thread.sleep(10*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Task2 end running. index:"+tmp_index+" Current time:"+System.currentTimeMillis());
    }
}

class ScheduledTask3 implements Runnable{
    AtomicInteger index = new AtomicInteger(1000);
    public void run() {
        int tmp_index = index.addAndGet(1);
        System.out.println("Thread name:"+Thread.currentThread().getName()+". Task3 begin running. index:"+tmp_index+" Current time:"+System.currentTimeMillis());
        try {
            Thread.sleep(10*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Task3 end running. index:"+tmp_index+" Current time:"+System.currentTimeMillis());
    }
}

public class ScheduledPool {
    static public void addTask() throws InterruptedException {
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
        executor.schedule(new ScheduledTask1(), 20, TimeUnit.SECONDS);
        executor.scheduleAtFixedRate(new ScheduledTask2(), 0, 5, TimeUnit.SECONDS);

        ScheduledExecutorService executor2 = Executors.newScheduledThreadPool(1);
        executor2.scheduleWithFixedDelay(new ScheduledTask3(), 0, 5, TimeUnit.SECONDS);

        Thread.sleep(50*1000);
        executor.shutdown();
        executor.shutdown();
    }

    public static void main( String[] args ) throws InterruptedException {
        addTask();
    }
}



定长线程池以及Future接口和Callable接口的代码示例

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

class Job1 implements Callable {
    int id = 0;
    public Job1(int id ){
        this.id = id;
    }
    public Boolean call() throws Exception {
        System.out.println("Job1 running.id:"+id+" Current time:"+System.currentTimeMillis());
        return false;
    }
}

class Job2 implements Callable {
    int id =0;
    public Job2(int id ){
        this.id = id;
    }
    public Boolean call() throws Exception {
        System.out.println("Job2 running.id:"+id+" Current time:"+System.currentTimeMillis());
        Thread.sleep(15*1000);
        System.out.println("Job2 end.id:"+id+" Current time:"+System.currentTimeMillis());
        return true;
    }
}

class Job3 implements Callable {
    int id =0;
    public Job3(int id ){
        this.id = id;
    }
    public Boolean call() throws Exception {
        System.out.println("Job3 running.id:"+id+" Current time:"+System.currentTimeMillis());
        throw new RuntimeException("Job3 throw exception.");
    }
}

public class ThreadPool {
    static void addTask() throws InterruptedException, ExecutionException {
        int id = 0;
        ExecutorService executor = Executors.newFixedThreadPool(2);
        Future future1 = executor.submit(new Job1(++id));
        Future future2 = executor.submit(new Job2(++id));
        Future future3 = executor.submit(new Job3(++id));
        System.out.println("job1 is done:"+future1.isDone());
        System.out.println("job1 result:"+future1.get());
        System.out.println("job2 is done:"+future2.isDone());
        System.out.println("job2 result:"+future2.get());
        try {
            System.out.println("job3 result:"+future3.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
            e.getCause();
        }

        List> taskList = new ArrayList>();
        taskList.add(new Job1(++id));
        taskList.add(new Job2(++id));
        List> futures = executor.invokeAll(taskList);
        for (Future future : futures) {
            System.out.println(future.get());
        }

        executor.shutdown();
        executor.submit(new Job1(2));
    }

    public static void main( String[] args ) throws InterruptedException, ExecutionException {
        addTask();
    }
}


工作中使用线程池的案例:

关于GlobalProperties使用配置文件,可以参考:https://blog.51cto.com/qinbin/2052625

这段程序是,在kafka上拉取数据,然后将数据放在BlockingQueue队列中(最后一个方法),然后创建线程池使用线程去消费队列,

关于如何创建线程:本类也是一个线程类,在构造方法中配置了线程池参数,并且建立线程池对象;同时执行本类的start方法,开启线程去消费,在run代码块中,线程池中加入线程,去消费。


@Service
public class AdLogsTemplete extends Thread {
	private Logger logger = LoggerFactory.getLogger(AdLogsTemplete.class);
	private AdBlockQueue adqueue;

	@Resource(type = AdlogRealTime.class)
	private AdlogRealTime adlogRealTime;
   
	// 主发送http请求的线程池参数
    private ExecutorService threadPoolMain;
    private int corePoolSize_main;
    private int maximumPoolSize_main;
    private long threadKeepAliveTime_main;
    private int threadPoolQueueSize_main;
    private  SimpleDateFormat sf = new SimpleDateFormat("yyyyMMdd");
    // ---------------------------
   
    public AdLogsTemplete() {
        // ----------------------
        // 主发送线程池参数
        corePoolSize_main = GlobalProperties.getInteger("thread.main.pool.corePoolSize");
        maximumPoolSize_main = GlobalProperties.getInteger("thread.main.pool.maxPoolSize");
        threadKeepAliveTime_main = GlobalProperties.getInteger("thread.main.pool.keepAliveTime");
        threadPoolQueueSize_main = GlobalProperties.getInteger("thread.main.pool.queueSize");
        threadPoolMain = new ThreadPoolExecutor(corePoolSize_main, maximumPoolSize_main, threadKeepAliveTime_main,
                TimeUnit.MILLISECONDS, new LimitedBlockingQueue(threadPoolQueueSize_main),
                new ThreadFactoryBuilder().setNameFormat("Sending Pool").build());
        
        adqueue = new AdBlockQueue();
        this.start();
    }
	
	//在spring配置文件配置程序启动就执行该类的init方法,同时开始多个线程消费BlockingQueue中的数据
	public void run() {
		while (true) {
            this.handler();
        }
	}
	
	public void handler() {
		threadPoolMain.execute(new Runnable() {
            @Override
            public void run() {
            	try {
            		//JSONObject adlog = new JSONObject();
            		JSONObject adlog = adqueue.getData();
            		//JSONObject adlog2= JSONObject.fromObject(adlog.toString());
            		//JSONObject adlog = adqueue.getData();
	    			if (adlog != null) {
	    				//System.out.println("------------------------------------");
	    				adlogRealTime.statistics(adlog);
	    			}
            	}catch (Exception e) {
                    logger.error("read queue data exception", e);
                }
            	
            }
        });
    }
	
	//将数据以list集合形式放在BlockingQueue中
	public void statistics(String json) {
		//logger.info("megJson:"+json);
		JSONObject adlog = JSONObject.fromObject(json);
		try {
			adqueue.putqueue(adlog);
			//System.out.println("adQueue.size**** = " + adqueue.getSize());
        } catch (InterruptedException e) {
            logger.error("put adUrl To queue exception,data=" + e);
        }
	}
	
}


4-1 提高锁性能的建议

1.减少锁持有时间

2.减小锁粒度(concurrenthashmap

3.读写分离锁来替换独占锁(ReadWriteLock

4.锁分离(LinkedBlockingQueue

5.锁粗化:有一连串不断请求的时候,就会合并请求

4-2 Thread local

线程局部变量,就是为每个使用该变量的线程提供一个变量的副本,是Java中的一种较为特殊的线程绑定机制,是每个线程都可以独立改变自己的副本,而不会与其他线程的副本冲突。为线程的并发问题提供了一种隔离机制。

Threadlocal通常用在一个类的成员上,多个线程访问它时,每个线程都有自己的副本,互不干扰:private static ThreadLocal t1=new ThreadLocal();

内部实现原理:

ThreadLocal内部其实是一个ThreadLocalMap(可以当做map,但是不是map)来保存数据。虽然在使用ThreadLocal时只给出了值,没有给出键,其实内部使用当前线程作为键。

Get方法:

/**

     * Returns the value in the current thread's copy of this

     * thread-local variable.  If the variable has no value for the

     * current thread, it is first initialized to the value returned

     * by an invocation of the {@link #initialValue} method.

     *

     * @return the current thread's value of this thread-local

     */

    public T get() {

        Thread t = Thread.currentThread();

        ThreadLocalMap map = getMap(t);

        if (map != null) {

            ThreadLocalMap.Entry e = map.getEntry(this);

            if (e != null) {

                @SuppressWarnings("unchecked")

                T result = (T)e.value;

                return result;

            }

        }

        return setInitialValue();

    }

当线程退出时,Thread类会进行一些清理工作,包括清理ThreadLocalMap ,由系统执行exit()方法。

 

线程泄露,当使用连接池的时候,里面线程数量固定,将大量的对象设置到ThreadLocal中,但是没有清理,线程就回不去线程池,导致线程泄露。

 

想要及时回收对象,最好使用ThreadLocal.remove()方法。或者JDK允许像释放普通变量释放ThreadLocal。比如直接obj= null.

 

适用场景:如果共享对象对于竞争的处理容易引起性能损失,就可以,考虑使用ThreadLocal为每个线程分配单独的对象。


5-1 CAS

与锁相比,使用比较交换(CAS)会使程序看起来更加复杂一些。但由于其非阻塞性,它对死锁问题天生免疫,并且,线程间的相互影响也远远比基于锁的方式要小。更为重要的是,使用无锁的方式完全没有锁竞争带来的系统开销,也没有线程间频繁调度带来的开销,因此,它要基于锁的方式拥有更优越的性能。

CAS算法的过程:



CAS缺点

     CAS虽然很高效的解决原子操作,但是CAS仍然存在三大问题。ABA问题,循环时间长开销大和只能保证一个共享变量的原子操作

    1.  ABA问题。因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。

    从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

    关于ABA问题参考文档: http://blog.hesey.net/2011/09/resolve-aba-by-atomicstampedreference.html

    

    2. 循环时间长开销大。自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。

    

    3. 只能保证一个共享变量的原子操作。当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。