今日目标:
|-- 原子性相关类(java.util.concurrent.atomic包)
|-- AtomicInteger
|-- AtomicLong
……
|-- ThreadLocal对象
|-- 使用
|-- 底层实现
|-- 注意事项
|-- 使用场景
|-- 同步问题
|--(生产者和消费者问题)
|-- 唤醒机制
|-- 线程池
|-- 高并发下的几种常见容器的使用
|-- ArrayList和CopyOnWriteArrayList
|-- HashSet和CopyOnWriteArraySet
|-- HashMap和ConrrentHashMap
高并发三元素
|-- 可见性:
多线程情况下,多个线程修改共享变量时,
是不可见的(修改数据,CPU缓存或者寄存器)
volatile关键字修改后,每一次值被其他线程修改后,
会立刻刷新内存,保证每一个线程都知道值的变化
|-- 有序性
内存指令的重排序(效率)
多线程情况下,禁止指令重排序
|-- 原子性
最小的,不具备分割的,是一个整体的
volatile关键字不具备原子性,它修饰的变量,不是最小!!
原子性相关类(java.util.concurrent.atomic包)
java.util.concurrent.atomic这个包下的类,都是具有原子性的!!!
运算符:
自加 // i++ ==> t = i+1; i = t;
自减
+= // i += 3 ==> t = i + 3; i = t;
-=
不具备原子性,在多线程情况下,会出现线程安全问题
AtomicInteger这些类,所有方法都是使用自旋锁实现的具备原子性的方法
java.util.concurrent.atomic包下的基于原子性的类
AtomicInteger
AtomicLong
AtomicBoolean
package com.openlab.day25.atomic;
import java.util.concurrent.atomic.AtomicInteger;
public class TestAtomic {
public static void main(String[] args) {
MyThread01 mt = new MyThread01();
new Thread(mt).start();
new Thread(mt).start();
}
}
class MyThread01 implements Runnable {
// 一旦使用volatile修饰,虽然是可见,但是因为volatile不具备原子性
// 因此还是会出现线程安全问题
// private volatile int count ;
private AtomicInteger count = new AtomicInteger(0);
@Override
public void run() {
for (int i = 0; i < 100000; i++) {
// count++;
// 这个操作是使用自旋锁,具备原子性
count.incrementAndGet(); // ++count
// count.getAndIncrement(); // count++
// count.getAndAdd(10); // += 10
}
System.out.println(count);
}
}
ThreadLocal对象:
线程对象提供了副本对象,特点是,每一个线程都独立拥有ThreadLocal对象
多线程情况下,一般比较喜欢使用ThreadLocal,多线程情况下,将值保存到
ThreadLocal中,多线程之间都是各自拥有各自的值,不会打架的
set() // 在自己线程中添加值
get() // 获取自己线程中存储在ThreadLocal中的值
remove() // 最后一定要移除值!!!,否则很容易出现内存溢出
package com.openlab.day25.atomic;
public class TestThreadLocal {
// public static String name = "子线程";
public static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
// 该线程的threadLocal
threadLocal.set("第一个线程的名字:张三");
printMsg(threadLocal);
// threadLocal添加的值,最后一定要移除掉,否则容易出现内存溢出
threadLocal.remove();
}
}).start();
// 使用lambda表达式完成
new Thread(() -> {
// 该线程的threadLocal
threadLocal.set("另外一个线程的名字:李四");
printMsg(threadLocal);
// threadLocal添加的值,最后一定要移除掉,否则容易出现内存溢出
threadLocal.remove();
}).start();
}
private static void printMsg(ThreadLocal<String> threadLocal) {
System.out.println(Thread.currentThread().getName() + "---->"+ threadLocal.get());
}
}
java中存在着四种对象引用:
强引用:
Object o = new Object();
o就是一个强引用,不会被gc回收,即便是调用System.gc();
// o = null;
|-- java的对象引用
|-- 强引用
|-- 软引用
|-- 弱引用
|-- 虚引用
同步(协同步调)
多个线程开始运行时,受到操作系统、硬件等等一系列的因素的制约,运行时顺序是不可控的!!!
通过程序控制多线程的运行顺序或者步骤,就是同步。
|-- 加锁 // 加锁是一种实现同步的手段,但是这种效率不高
|-- 唤醒机制
wait() // 让线程进入等待状态
notify() // 唤醒线程
notifyAll() // 唤醒所有线程
生产者和消费者模型:
|-- 生产者 消费者两种对象
|-- 生产者生成的商品和消费者消费商品的平衡
|-- 供不应求
|-- 供大于求
唤醒机制:
一般是使用在两个线程之间,运行时顺序是不可控的!!!
使用唤醒机制实现同步可控
wait() // 让线程进入等待状态
notify() // 唤醒其他线程
需要注意的是:
1、使用共有的对象做为唤醒的对象存在
2、必须要加同步锁(synchronized)
3、同步锁的钥匙必须是共有的对象
目的就是为了实现线程之间的通信
上代码:
生产者代码:
package com.openlab.day25.syc;
import java.util.Random;
public class Productor implements Runnable{
private String[] foods;
private Disk disk;
private Random random;
public String[] getFoods() {
return foods;
}
public void setFoods(String[] foods) {
this.foods = foods;
}
public Disk getDisk() {
return disk;
}
public void setDisk(Disk disk) {
this.disk = disk;
}
public Productor(Disk disk) {
this.foods = new String[]{"牛肉面", "油泼面", "拉面", "刀削面", "烩面", "蛋炒饭", "拍黄瓜", "红烧肉"};
this.disk = disk;
this.random = new Random();
}
@Override
public void run() {
cookie();
}
private void cookie(){
for (int i = 0; i < 10; i++) {
synchronized (this.disk) {
if (!this.disk.isFull()) {
// 随机做一种食物
int index = this.random.nextInt(this.foods.length);
String food = this.foods[index];
System.out.println(Thread.currentThread().getName() +"制作了一种食物:"+ food);
// 将食物放上去
this.disk.setFoodName(food);
// 表示盘子上存在了食物
this.disk.setFull(true);
this.disk.notify();
try {
this.disk.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else{
try {
this.disk.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
消费者代码:
package com.openlab.day25.syc;
public class Customer implements Runnable{
private Disk disk;
public Disk getDisk() {
return disk;
}
public void setDisk(Disk disk) {
this.disk = disk;
}
public Customer(Disk disk) {
this.disk = disk;
}
public Customer() {
}
@Override
public void run() {
eat();
}
private void eat() {
while (true) {
synchronized (this.disk) {
// 只要盘子上有食物,我们就直接吃食物
if (this.disk.isFull()) {
String food = this.disk.getFoodName();
System.out.println(Thread.currentThread().getName() +"开始吃食物:"+ food);
this.disk.setFull(false);
this.disk.notify();
try {
this.disk.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
try {
this.disk.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
盘子代码:
package com.openlab.day25.syc;
public class Disk {
// 盘子上面的食物
private String foodName;
private boolean full;
public String getFoodName() {
return foodName;
}
public void setFoodName(String foodName) {
this.foodName = foodName;
}
public boolean isFull() {
return full;
}
public void setFull(boolean full) {
this.full = full;
}
public Disk() {
}
public Disk(String foodName, boolean full) {
this.foodName = foodName;
this.full = full;
}
}
测试代码:
package com.openlab.day25.syc;
public class TestCP {
public static void main(String[] args) {
Disk disk = new Disk();
Productor productor = new Productor(disk);
Customer customer = new Customer(disk);
Thread task1 = new Thread(productor);
Thread task2 = new Thread(customer);
// 将消费方做成守护线程,让它依赖于生产者
task2.setDaemon(true);
task1.start();
task2.start();
}
}
通过以上方式就实现了线程之间的通信。
线程池:
池化模式:将大量我们需要的对象提前创建好,放在一个池(概念),
对象提前创建完成,也不需要销毁对象,所以说使用效率比较好
优点:使用效率比较好,避免对象的重复创建和销毁
缺点:内存占有较高,池的数量难以把控
池的数量的把控问题才是最关键的
java线程池是 1.5提供 juc包中,底层实现其实就是Callable和Future接口
|-- 根据情况,我们创建很多种不同场景的线程池
package com.openlab.day25.threadpool;
import org.junit.Test;
import java.util.concurrent.*;
public class TestThreadPool {
@Test
void test01() {
// 创建一个单例的线程池
// 单例线程池:只有一个线程,固定不会变化点一个线程
// 使用场景:只有一个线程时,建议使用这种线程池
// ExecutorService executor = Executors.newSingleThreadExecutor();
ExecutorService executor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
executor.execute(() -> System.out.println(Thread.currentThread().getName() +"-->"));
}
}
@Test
void test02() {
// 创建一个固定大小的线程池
// 适用场景:并发量不会发生变化(并发量的变化非常小)
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
// 如果想要将外部的变量,传递到匿名内部类中,就需要将该值做成常量
final int index = i;
executorService.submit(() -> System.out.println(Thread.currentThread().getName() +"--->"+ index));
}
}
@Test
void test03() {
// 创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程
// 并发量变化比较明显的,建议使用这种线程池
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 20; i++) {
final int index = i;
executorService.execute(()-> {
System.out.println(Thread.currentThread().getName() +"--->"+ index);
});
}
}
@Test
void test04() {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
for (int i = 0; i < 20; i++) {
final int index = i;
// 延时线程池,创建的线程,就有延时执行的特点
// 第一个参数:需要执行的任务
// 第二个参数:延时多长
// 延时的时间单位:秒、小时、毫米、微秒、纳秒、分钟
scheduledExecutorService.schedule(()-> {
System.out.println(Thread.currentThread().getName() +"--->"+ index);
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
},
5, TimeUnit.SECONDS);
}
}
@Test
void test05() {
// 建议使用自己构建的线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(500, 2000, 1, TimeUnit.MINUTES, new LinkedBlockingDeque<>());
// threadPoolExecutor.execute();
}
}
在intellij idea中使用Junit单元测试
1、需要安装一个插件 Junit插件(setting——>plugins),搜索 Junit
2、根据提示一步一步的安装
3、重启就生效
高并发下的几种常见容器的使用
ArrayList
Vector
LinkedList
HashMap
HashSet
Hashtable
……
强调一个是:容器都存在着线程安全
|-- 线程安全的:
Vector:加了锁,好处:不存在线程安全问题,坏处:效率比较差
|-- juc包:
|-- ConrrentHashMap
|-- CopyOnWriteArrayList
|-- CopyOnWriteArraySet
|-- ConrrentHashMap
1、线程安全
2、读写分离