1.线程池的基本使用
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyPool {
public static void main(String[] args) {
//这是一个线程创建工具,他会判断线程池子里面是否有空闲的线程,有就用空闲的线程
//没有则创建,这边能创建最大的线程就是Int的最大值,这个线程池某种意义上线程数量
//无限制,这边注意一下 ExecutorService这个是一个接口
ExecutorService executorService = Executors.newCachedThreadPool();
Executors.newFixedThreadPool(10);//这种创建方式是创建一个线程上限的池子
//这边的入参是Runabled接口,所以可以lamda
executorService.submit(()->{
System.out.println(Thread.currentThread().getName()+"吃饭");
});
executorService.submit(()->{
System.out.println(Thread.currentThread().getName()+"吃饭");
});
executorService.submit(()->{
System.out.println(Thread.currentThread().getName()+"吃饭");
});
//关闭线程池
executorService.shutdown();
}
}
2.线程池的创建
import java.util.concurrent.*;
public class MyPoolThread {
public static void main(String[] args) {
int corePoolSize = 5;//核心线程数量,创建就不会被销毁
int maximumPoolSize =10;//全部的数量
long keepAliveTime=10;//非核心线程多场失效
TimeUnit unit = TimeUnit.SECONDS;//单位
//这边的队列是如果我所有的线程都有事,那么再来一个新的任务就到队列等待线程释放
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(1);
//如果队列也满了,那么就开始拒绝。
//拒绝策略一共有四种
//ThreadPoolExecutor.AbortPolicy 拒绝并跑出异常
//ThreadPoolExecutor.DiscardPolicy 拒绝不抛出异常
//ThreadPoolExecutor.CallerRunsPolicy 抛弃队列里面等待时间最长的任务
//ThreadPoolExecutor.DiscardOldestPolicy 不用线程池,直接给其他线程处理
RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy();
ThreadPoolExecutor threadPoolExecutor =
new ThreadPoolExecutor(corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueue,
Executors.defaultThreadFactory(),
handler);
threadPoolExecutor.execute(()->{
System.out.println("运行任务");
});
}
}
3.volatile关键字的用法
public class MyPoolVolatile {
//有10个汉堡
private static volatile int HanBaoBao = 10;
//下面要做一个事情,就是我有两个线程一个叫老7要去偷吃老八的汉堡,
// 但是老八每天都会数一下,发现了就会报警!
//这边的代码会陷入死循环
//有两个解决方法
//第一种就是 private static volatile int HanBaoBao = 10;
//第二种就是加把锁
//这边的机制其中一个线程如果改了共同的变量,
// 那么就提醒使用这个变量的线程,重新拿一下值
public static void main(String[] args) {
//这是老八
new Thread(()->{
while(true){
if(HanBaoBao!=10){
System.out.println("嗨嗨害!报警!");
break;
}else{
}
}
}).start();
//这边是小偷我偷汉堡
new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
HanBaoBao = 7;
}).start();
}
}
4.多线程操作共享数据(多线程重点)
这边呢也是我在工作中的时候呢遇到一个比较重要的问题,我多线程去操作某一集合对象,如果仅仅是查询其实也无所谓,但是设计到更改,那么这个共享数据也能就会产生问题,那么有问题就需要解决!
4.1数据原子性--举个反例
这边用volatile 关键字也是不行的,同步代码块是可以,但是效率较慢
public class ThreadAddNum {
//我现在有个场景,如果我有一百个线程去操作num,每个线程我加100次
//那么最后的结果是不是num就是10000?这边的结果就不是?如果是10000你多试几次
//会发现结果是飘忽不定的
//这就出现了问题
private static int num = 0;
public static void main(String[] args) {
for(int i=0;i<100;i++){
new Thread(()->{
for(int a=0;a<100;a++){
num++;
System.out.println(num);
}
}).start();
}
}
}
4.2 解决方案 自旋锁+CAS算法
CAS算法就是在每次去操作共享数据A的时候事先会把原数据进行备份B,然后我更改再创建一个新的值B2,不动这个备份数据B,那么最后需要给这个共享数据新值的时候我先看一下,目前的这个值A等于不等于我最开始的数据备份值B。如果是那,么我在把B2这个修改过的值给A。
import java.util.concurrent.atomic.AtomicInteger;
public class MyAomic {
// private static int num = 0;
private static AtomicInteger num = new AtomicInteger(0);
public static void main(String[] args) {
//这边有很多类Atomic包里面的类我这边只用到了
//integer所以就举一下这个例子
AtomicInteger atomicInteger = new AtomicInteger(10);
//下面都是一些操作这个对像的方法,和多线程并没有关系
System.out.println(atomicInteger.get());//获取这个对象里面的值
System.out.println(atomicInteger.getAndIncrement());//自增之前的值
System.out.println(atomicInteger.incrementAndGet());//自增之后的值,看名字都是有说法的规律自己找
System.out.println(atomicInteger.getAndAdd(88));
System.out.println(atomicInteger.getAndSet(0));
for(int i=0;i<100;i++){
new Thread(()->{
for(int a=0;a<100;a++){
num.incrementAndGet();
System.out.println(num.get());
}
}).start();
}
}
}
4.3 乐观锁和悲观锁
悲观锁就是synchronized代码块 ,我默认就认为别人会抢我的老婆。我每次干活我就先锁门,防止别人抢我老婆。
乐观锁就是我默认不会抢我老婆,但是我每天回家时候我检查一下,看看是不是原来的形状。
4.4 HashMap线程不安全
HashMap不安全,可以替换成HashTable ,但是效率很低,那么为了效率就会有专门解决这个问题的类对象
import java.util.HashMap;
public class MyHash {
private static HashMap<String,String> map =new HashMap<>();
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
for (int i = 0; i < 50; i++) {
map.put(i+"",i+"");
}
}).start();
new Thread(()->{
for (int i = 50; i < 100; i++) {
map.put(i+"",i+"");
}
}).start();
new Thread(()->{
for (int i = 100; i < 150; i++) {
map.put(i+"",i+"");
}
}).start();
Thread.sleep(1000);
for (int i = 0;i<200;i++)
System.out.println(map.get(i+""));
}
}
4.5 ConcurrentHashMap
这个一般在多线程的场景下使用,线程安全和效率高,这边有兴趣的同学可以看一下原理,7版本和8版本原理有所不同。这边大概提一下,7版本是用的二次hash原理,8版本用了CAS算法
4.6 CountDownLatch工具类
这个类是控制线程的执行顺序的,之前的权重虽然可以控制,但是无法百分之百。可以用这个类绝对的控制线程的执行顺序
import java.util.concurrent.CountDownLatch;
public class MyCountLatch {
//这边是控制线程的执行顺序,这边传入的数字就是唤醒等待的线程
//这边的意思就是当这个数字为0的时候就会去执行countDownLatch.await();的线程
private static CountDownLatch countDownLatch = new CountDownLatch(2);
public static void main(String[] args) {
new Thread(()->{
try {
//等待
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("嗨嗨害!");
}).start();
new Thread(()->{
try {
//等待
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("嗨嗨害!2");
}).start();
new Thread(()->{
try {
Thread.sleep(100);
System.out.println("老八秘制小汉堡!1");
countDownLatch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
try {
Thread.sleep(200);
System.out.println("老八秘制小汉堡!2");
countDownLatch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
4.7 Semaphore 线程控制类
控制一次能有几个线程可以执行
import java.util.concurrent.Semaphore;
public class MySemaphore {
//最多允许2个线程进行操作
private static Semaphore mySemaphore = new Semaphore(2);
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(()->{
try {
mySemaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我又进来啦!打我打我啊!");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我又出来啦!");
mySemaphore.release();
}).start();
}
}
}