一、线程的实现方式
1.1 继承Thread
1.2 实现Runable
1.3 实现CallAble
import java.util.concurrent.*;
//线程池
/
四大常用线程池策略:
newCachedThreadPool
newFixedThreadPool
ScheduledThreadPool
newSingleThreadExecutor
/
public class Demo1 {
public Demo1() {
}public static void main(String[] args) {<br /> //拿到线程池<br /> ExecutorService executorService = newCachedThreadPool();<br /> executorService.submit(new Runnable() {<br /> @Override<br /> public void run() {<br /> System.out.println("任务执行了");<br /> }<br /> });<br /> executorService.shutdown();<br /> }/*<br /> 1.是一个可以无限扩大的线程池;<br /> 2.适合处理执行时间比较小的任务;任务频率高的任务。<br /> 3.corePoolSize为0,maximumPoolSize为无限大,线程数量可以无限大;<br /> 4.keepAliveTime为60S,线程空闲时间超过60S就会被杀死;<br /> 5.采用SynchronousQueue装等待的任务,这个阻塞队列没有存储空间,这意味着只要有请求到来,就必须要找到一条工作线程处理他,<br /> 如果当前没有空闲的线程,那么就会再创建一条新的线程。<br /> */<br /> /*<br /> CachedThreadPool,可以称作可缓存线程池,它的特点在于线程数是几乎可以无限增加的(实际最大可以达到 Integer.MAX_VALUE,<br /> 为 2^31-1,这个数非常大,所以基本不可能达到),而当线程闲置时还可以对线程进行回收。也就是说该线程池的线程数量不是固定不变的,<br /> 当然它也有一个用于存储提交任务的队列,但这个队列是 SynchronousQueue,队列的容量为0,实际不存储任何任务,<br /> 它只负责对任务进行中转和传递,所以效率比较高。当我们提交一个任务后,线程池会判断已创建的线程中是否有空闲线程,<br /> 如果有空闲线程则将任务直接指派给空闲线程,如果没有空闲线程,则新建线程去执行任务,这样就做到了动态地新增线程。<br /> */<br /> public static ExecutorService newCachedThreadPool() {<br /> return new ThreadPoolExecutor(0, Integer.MAX_VALUE,<br /> 60L, TimeUnit.SECONDS,<br /> new SynchronousQueue<Runnable>());<br /> }/*<br /> 1.固定大小的线程池nThreads;<br /> 2.corePoolSize和maximunPoolSize都为用户设定的线程数量nThreads;<br /> 3.keepAliveTime为0,意味着一旦有多余的空闲线程,就会被立即停止掉;但这里keepAliveTime无效(核心线程=最大线程);<br /> 4.阻塞队列采用了LinkedBlockingQueue,一个无界队列;由于阻塞队列是一个无界队列,因此永远不可能拒绝任务;<br /> 由于采用了无界队列,实际线程数量将永远维持在nThreads,因此maximumPoolSize和keepAliveTime将无效。任务比较重要,一定要执行! 任务耗时可能比较大! 性能要稳定,任务排队久<br /> */<br /> public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {<br /> return new ThreadPoolExecutor(nThreads, nThreads,<br /> 0L, TimeUnit.MILLISECONDS,<br /> new LinkedBlockingQueue<Runnable>(),<br /> threadFactory);<br /> }//Executors.newSingleThreadExecutor()返回一个线程池(这个线程池只有一个线程),<br />// 这个线程池可以在线程死后(或发生异常时)重新启动一个线程来替代原来的线程继续执行下去<br /> /*<br /> SingleThreadExecutor,它会使用唯一的线程去执行任务,原理和 FixedThreadPool 是一样的,<br /> 只不过这里线程只有一个,如果线程在执行任务的过程中发生异常,线程池也会重新创建一个线程来执行后续的任务。<br /> 这种线程池由于只有一个线程,所以非常适合用于所有任务都需要按被提交的顺序依次执行的场景,而前几种线程池不一定能够保障任务<br /> 的执行顺序等于被提交的顺序,因为它们是多线程并行执行的。<br /> */<br /> //一直会有一个线程,保证多任务的执行顺序<br /> public static ExecutorService newSingleThreadExecutor() {return Executors.newSingleThreadExecutor();<br /> }/*<br /> 第一种方法 schedule 比较简单,表示延迟指定时间后执行一次任务,如果代码中设置参数为 10 秒,也就是 10 秒后执行一次任务后就结束。第二种方法 scheduleAtFixedRate 表示以固定的频率执行任务,它的第二个参数 initialDelay 表示第一次延时时间,<br /> 第三个参数 period 表示周期,也就是第一次延时后每次延时多长时间执行一次任务。第三种方法 scheduleWithFixedDelay 与第二种方法类似,也是周期执行任务,区别在于对周期的定义,之前的 scheduleAtFixedRate<br /> 是以任务开始的时间为时间起点开始计时,时间到就开始执行第二次任务,而不管任务需要花多久执行;而 scheduleWithFixedDelay<br /> 方法以任务结束的时间为下一次循环的时间起点开始计时。<br /> */<br /> public static ExecutorService newScheduledExecutorService() {<br /> ScheduledExecutorService service = Executors.newScheduledThreadPool(10);<br /> <br /> Runnable task = new Runnable() {<br /> int i = 0;<br /> @Override<br /> public void run() {<br /> i++;<br /> System.out.println(Thread.currentThread().getName() + ":" + i);<br /> }<br /> };<br /> //倒计时10秒后执行这个任务<br /> service.schedule(task<br /> , 10, TimeUnit.SECONDS);<br /> //从10秒后开始执行第一次任务,以后每隔10秒执行一次任务(重复执行任务)<br /> //service.scheduleAtFixedRate(task, 10, 10, TimeUnit.SECONDS);<br /> //从10秒后执行任务,在任务执行完毕后,间隔10秒开始执行后面的任务<br /> service.scheduleWithFixedDelay(task, 10, 10, TimeUnit.SECONDS);<br /> return service;<br /> }<br />}
三、CAS
1.比较并替换 Compare And Swap
2.ABA问题 解决方式:+版本号 标记修改次数
3.1 原子性_AtomicInteger
CAS+自旋
概述:java从JDK1.5开始提供了java.util.concurrent.atomic包(简称Atomic包),这个包中的原子操作类提供了一种用法简单,性能高效,线程安全地更新一个变量的方式。因为变
量的类型有很多种,所以在Atomic包里一共提供了13个类,属于4种类型的原子更新方式,分别是原子更新基本类型、原子更新数组、原子更新引用和原子更新属性(字段)。本次我们只讲解
使用原子的方式更新基本类型,使用原子的方式更新基本类型Atomic包提供了以下3个类:
AtomicBoolean: 原子更新布尔类型
AtomicInteger: 原子更新整型
AtomicLong: 原子更新长整型
以上3个类提供的方法几乎一模一样,所以本节仅以AtomicInteger为例进行讲解,AtomicInteger的常用方法如下:
public AtomicInteger(): 初始化一个默认值为0的原子型Integer
public AtomicInteger(int initialValue): 初始化一个指定值的原子型Integer
int get(): 获取值
int getAndIncrement(): 以原子方式将当前值加1,注意,这里返回的是自增前的值。
int incrementAndGet(): 以原子方式将当前值加1,注意,这里返回的是自增后的值。
int addAndGet(int data): 以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果。
int getAndSet(int value): 以原子方式设置为newValue的值,并返回旧值。
代码实现 :
package com.itheima.threadatom3;
import java.util.concurrent.atomic.AtomicInteger;
public class MyAtomIntergerDemo1 {
// public AtomicInteger(): 初始化一个默认值为0的原子型Integer
// public AtomicInteger(int initialValue): 初始化一个指定值的原子型Integer
public static void main(String[] args) {
AtomicInteger ac = new AtomicInteger();
System.out.println(ac);
AtomicInteger ac2 = new AtomicInteger(10);<br /> System.out.println(ac2);<br /> }
}
package com.itheima.threadatom3;
import java.lang.reflect.Field;
import java.util.concurrent.atomic.AtomicInteger;
public class MyAtomIntergerDemo2 {
// int get(): 获取值
// int getAndIncrement(): 以原子方式将当前值加1,注意,这里返回的是自增前的值。
// int incrementAndGet(): 以原子方式将当前值加1,注意,这里返回的是自增后的值。
// int addAndGet(int data): 以原子方式将参数与对象中的值相加,并返回结果。
// int getAndSet(int value): 以原子方式设置为newValue的值,并返回旧值。
public static void main(String[] args) {
// AtomicInteger ac1 = new AtomicInteger(10);
// System.out.println(ac1.get());
// AtomicInteger ac2 = new AtomicInteger(10);
// int andIncrement = ac2.getAndIncrement();
// System.out.println(andIncrement);
// System.out.println(ac2.get());
// AtomicInteger ac3 = new AtomicInteger(10);
// int i = ac3.incrementAndGet();
// System.out.println(i);//自增后的值
// System.out.println(ac3.get());
// AtomicInteger ac4 = new AtomicInteger(10);
// int i = ac4.addAndGet(20);
// System.out.println(i);
// System.out.println(ac4.get());
AtomicInteger ac5 = new AtomicInteger(100);<br /> int andSet = ac5.getAndSet(20);<br /> System.out.println(andSet);<br /> System.out.println(ac5.get());<br /> }<br />}
3.2 AtomicInteger-内存解析
AtomicInteger原理 : 自旋锁 + CAS 算法
CAS算法:
有3个操作数(内存值V, 旧的预期值A,要修改的值B)
当旧的预期值A == 内存值 此时修改成功,将V改为B
当旧的预期值A!=内存值 此时修改失败,不做任何操作
并重新获取现在的最新值(这个重新获取的动作就是自旋)
3.3 AtomicInteger-源码解析
代码实现 :
package com.itheima.threadatom4;
public class AtomDemo {
public static void main(String[] args) {
MyAtomThread atom = new MyAtomThread();
for (int i = 0; i < 100; i++) {<br /> new Thread(atom).start();<br /> }<br /> }<br />}<br />package com.itheima.threadatom4;
import java.util.concurrent.atomic.AtomicInteger;
public class MyAtomThread implements Runnable {
//private volatile int count = 0; //送冰淇淋的数量
//private Object lock = new Object();
AtomicInteger ac = new AtomicInteger(0);
@Override<br /> public void run() {<br /> for (int i = 0; i < 100; i++) {<br /> //1,从共享数据中读取数据到本线程栈中.<br /> //2,修改本线程栈中变量副本的值<br /> //3,会把本线程栈中变量副本的值赋值给共享数据.<br /> //synchronized (lock) {<br />// count++;<br />// ac++;<br /> int count = ac.incrementAndGet();<br /> System.out.println("已经送了" + count + "个冰淇淋");<br /> // }<br /> }<br /> }<br />}
源码解析 :
unsafe:绕开JVM直接调用底层操作系统。JVM无法管理这个关键创建出来的对象的,垃圾回收对他无用。
CAS中大量的使用了unsafe。
//CAS源码底层
//valueOffset:偏移量
//this + 偏移量 得到数据的内存位置,通过数据的内存位置就可以直接去找到数据,速度非常快,绕开jvm
//this, valueOffset = 内存最新值 比较 expect预期值。相等则替换为update修改值
unsafe.compareAndSwapInt(this, valueOffset, expect, update);
//先自增,然后获取自增后的结果
public final int incrementAndGet() {
//+ 1 自增后的结果
//this 就表示当前的atomicInteger(值)
//1 自增一次
return U.getAndAddInt(this, VALUE, 1) + 1;
}
public final int getAndAddInt(Object o, long offset, int delta) {
//v 旧值
int v;
//自旋的过程
do {
//不断的获取旧值
v = getIntVolatile(o, offset);
//如果这个方法的返回值为false,那么继续自旋
//如果这个方法的返回值为true,那么自旋结束
//o 表示的就是内存值
//v 旧值
//v + delta 修改后的值
} while (!weakCompareAndSetInt(o, offset, v, v + delta));
//作用:比较内存中的值,旧值是否相等,如果相等就把修改后的值写到内存中,返回true。表示修改成功。
// 如果不相等,无法把修改后的值写到内存中,返回false。表示修改失败。
//如果修改失败,那么继续自旋。
return v;
}
四 悲观锁和乐观锁
synchronized和CAS的区别 :
相同点:在多线程情况下,都可以保证共享数据的安全性。
不同点:synchronized总是从最坏的角度出发,认为每次获取数据的时候,别人都有可能修改。所以在每 次操作共享数据之前,都会上锁。(悲观锁)
cas是从乐观的角度出发,假设每次获取数据别人都不会修改,所以不会上锁。只不过在修改共享数据的时候,会检查一下,别人有没有修改过这个数据。
如果别人修改过,那么我再次获取现在最新的值。
如果别人没有修改过,那么我现在直接修改共享数据的值.(乐观锁)
简单一句话
乐观锁:不禁止你获取锁这件事,不保证你能获取到锁,如果失败了,就再试一次吧
悲观锁:禁止你获取锁,我用完你才能去抢。
如果并发不大,锁占用时间不长,乐观锁。
并发量比较大,锁占用时间比较长,悲观锁。
五 ReentrantLock
5.1 公平锁 非公平锁
默认是非公平锁
ReentrantLock lock = new ReentrantLock();//非公平锁
//先CAS尝试加锁,能加锁则记录当前线程,设置state为1
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
//加不了锁,进队列
acquire(1);
}
如果创建ReentrantLock对象的时候,传入true值,就是公平锁。
ReentrantLock lock = new ReentrantLock(true);//公平锁
//直接进队列 排队
final void lock() {
acquire(1);
}
公平锁:当线程来抢锁,进入队列排队,谁先进队列谁就先抢到锁。排队抢锁,食堂买饭。
一句话:先来先得,依次排队获得锁。
非公平锁:当新线程来抢锁,首先CAS去抢锁(修改state值),如果抢锁失败进队列排队。
5.2 可重入锁
- 目的:防止 死锁
public class Demo4 {
static ReentrantLock lock = new ReentrantLock();
static int a;
public static void main(String[] args) {
Runnable task = new Runnable() {
@Override
public void run() {
while (true) {
lock.lock();
try {
hehe()
if (a >= 20) {
break;
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + “ “ + a++);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
};
Thread t1 = new Thread(task, “t1”);
Thread t2 = new Thread(task, “t2”);
t1.start();
t2.start();
}
public static void hehe(){<br /> lock.lock();<br /> System.out.println("hehe");<br /> lock.unlock();<br /> }<br />}
