多线程
多线程实现方式
-
继承Thread
public class MyThread extends Thread{ @Override public void run() { for (int i = 1; i <= 10; i++) { System.out.println(Thread.currentThread().getName() + " - - - " + i); } } }
-
实现runnable
public class MyRunnable implements Runnable{ @Override public void run() { for (int i = 1; i <= 10; i++) { System.out.println(Thread.currentThread().getName() + " - - - " + i); } } }
-
实现callable
public class MyCallable implements Callable<String> { @Override public String call() throws Exception { for (int i = 1; i <= 10; i++) { System.out.println(Thread.currentThread().getName() + " - - - " + i); } return Thread.currentThread().getName() + " - - - 执行完毕!"; } }
-
设置和获取线程名称
Thread t = new Thread(); t.setName('线程名称'); Thread.currentThread().getName();
-
Thread中的方法
// 休眠1秒 Thread.sleep(1000);
-
线程优先级
Thread t = new Thread(); // min 5 -> max 10 t.setPricrity();
-
守护线程
MyThread1 t1 = new MyThread1(); MyThread2 t2 = new MyThread2(); t1.setName("nvshen"); t2.setName("beitai"); // 设置t2为守护线程 // 当t1执行完毕,t2也没有继续运行的必要了 t2.setDaemon(true); t1.start(); t2.start();
线程安全问题
-
卖票案例
class MyRunable implements Runnable{ private Integer tickets = 100; @Override public void run() { while (true){ if (tickets <= 0){ break; }else { tickets--; try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":卖出第" + (tickets + 1) + "张票,剩余" + tickets + "张"); } } } }
public static void main(String[] args) { MyRunable myRunable = new MyRunable(); Thread t1 = new Thread(myRunable); t1.setName("【窗口 1】"); Thread t2 = new Thread(myRunable); t2.setName("【窗口 2】"); Thread t3 = new Thread(myRunable); t3.setName("【窗口 3】"); t1.start(); t2.start(); t3.start(); }
...... 【窗口 2】:在卖第11张票,剩余10张 【窗口 3】:在卖第8张票,剩余7张 【窗口 1】:在卖第8张票,剩余7张 【窗口 2】:在卖第8张票,剩余7张 【窗口 3】:在卖第5张票,剩余4张 【窗口 1】:在卖第5张票,剩余4张 【窗口 2】:在卖第5张票,剩余4张 【窗口 2】:在卖第2张票,剩余1张 【窗口 1】:在卖第2张票,剩余1张 【窗口 3】:在卖第2张票,剩余1张 【窗口 2】:在卖第1张票,剩余0张 【问题分析】: 重复票: tickets = 100 线程一执行tickets--后,tickets变为99,此时执行权被线程二抢走,线程二执行完tickets--后,tickets变为98,此时线程一中的tickets值也是98,就出现了重复卖票问题 负号票: tickets = 1 线程一执行tickets--之后,执行权被线程二抢走,此时tickets数量变为0,线程二又执行tickets--之后,此时tickets变为-1,执行权又被线程三抢走,此时就出现符负号票问题
-
同步代码块 | 同步方法
synchronized(锁对象){ } 锁对象要唯一,多个线程要使用同一把锁 private synchronized void synchronizedMethod(){ }
-
Lock锁
private ReentrantLock lock = new ReentrantLock(); // 加锁 lock.lock(); // 解锁 lock.unlock();
-
死锁
public class deadLock { public static void main(String[] args) { Object o1 = new Object(); Object o2 = new Object(); new Thread(()->{ while (true){ synchronized (o1){ synchronized (o2){ System.out.println("张三在吃饭"); } } } }).start(); new Thread(()->{ while (true){ synchronized (o2){ synchronized (o1){ System.out.println("李四在洗脸"); } } } }).start(); } } 【第一个线程o1上锁后执行权被第二个线程抢走,o2上锁,此时线程一中拿不到o2锁,线程二中拿不到o1锁,产生死锁】
线程池
虚拟机中线程六种状态: | |
---|---|
新建(NEW) | 创建线程对象 |
就绪(RUNNABLE) | start方法 |
阻塞(BLOCKED) | 无法获得锁对象 |
等待(WAITING) | wait方法 |
计时(TIMED_WAITING) | sleep方法 |
结束(TERMINATED) | 全部代码运行完毕 |
// 创建默认线程池
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.submit(()->{
System.out.println(Thread.currentThread().getName());
});
executorService.submit(()->{
System.out.println(Thread.currentThread().getName());
});
executorService.shutdown();
}
// 创建指定最多线程数的线程池
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
executorService.submit(()->{
System.out.println(Thread.currentThread().getName());
});
executorService.submit(()->{
System.out.println(Thread.currentThread().getName());
});
executorService.shutdown();
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
executorService.submit(()->{
System.out.println(Thread.currentThread().getName());
});
executorService.submit(()->{
System.out.println(Thread.currentThread().getName());
});
// 获取线程池内线程数量
System.out.println(executorService.getPoolSize())
executorService.shutdown();
}
线程池对象-ThreadPoolExecutor
// 源码
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
// corePoolSize 核心线程数
// maximumPoolSize 最大线程数
// keepAliveTime 空闲线程存活时间
// unit 单位(TimeUnit.xx)
// workQueue 任务队列(阻塞队列) new ArrayBlockingQueue(10)
// Executors.defaultThreadFactory() 线程工厂
// defaultHandler 任务拒绝策略 new ThreadPoolExecutor().abortPolicy() 终止策略,超过的任务就不要了
参数 | 解释 | 示例 |
---|---|---|
corePoolSize | 核心线程数 | 5 |
maximumPoolSize | 最大线程数 | 10 |
keepAliveTimeunit | 空闲线程存活时间 | 10 |
unit | 单位 | TimeUnit.SECOND |
workQueue | 任务队列(阻塞队列) | new ArrayBlockingQueue(10) |
Executors.defaultThreadFactory() | 线程工厂 | Executors.defaultThreadFactory() |
defaultHandler | 任务拒绝策略 | new ThreadPoolExecutor().abortPolicy终止策略,超过的任务就不要了 |
任务拒绝策略 | |
---|---|
ThreadPoolExecutor().abortPolicy | 默认策略 ,丢弃任务并抛出RejectedExecutionException异常 |
ThreadPoolExecutor.DiscardPolicy | 丢弃任务,不抛出异常 不推荐的做法 |
ThreadPoolExecutor.DiscardOldestPolicy | 抛弃等待最久的任务,然后把当前任务加入队列 |
ThreadPoolExecutor.CallerRunsPolicy | 调用任务的run()方法绕过线程池直接执行 |
volatile
// volatile修饰共享数据
public static volatile int money = 100000;
原子性
所谓原子性是指在一次操作中,要么所有操作全部都得到执行且不会受任何因素的干扰而中断,要么所有操作都不执行,`多个操作是一个不可以分割的整体`
public class 原子性 {
public static void main(String[] args) {
MyAtomThread ma = new MyAtomThread();
for (int i = 0; i < 100; i++) {
new Thread(ma).start();
}
}
}
class MyAtomThread implements Runnable{
private int count = 0;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
count++;
System.out.println("已经送了" + count + "个冰淇淋");
}
}
}
已经送了9988个冰淇淋
已经送了9989个冰淇淋
已经送了9990个冰淇淋
已经送了9991个冰淇淋
已经送了9992个冰淇淋
已经送了9993个冰淇淋
已经送了9994个冰淇淋
已经送了9995个冰淇淋
已经送了9996个冰淇淋
已经送了9997个冰淇淋
已经送了9998个冰淇淋
已经送了9999个冰淇淋
// volatile不能保证原子性
// 同步代码块方式
// jdk1.5 出现atomic原子包
// AtomicInteger
public class 原子性 {
public static void main(String[] args) {
MyAtomThread ma = new MyAtomThread();
for (int i = 0; i < 100; i++) {
new Thread(ma).start();
}
}
}
class MyAtomThread implements Runnable{
AtomicInteger count = new AtomicInteger(0);
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("已经送了" + count.addAndGet(1) + "个冰淇淋");
}
}
}
AtomicInteger中的方法 | 解释 |
---|---|
int get() | 获取值 |
int getAndIncrement() | 自增1,返回自增前的值 |
int IncrementAndGet() | 自增1,返回自增后的值 |
int addAndGet(x) | 增加x,返回增加后的值 |
int getAndSet(x) | 设置为x,返回旧值 |
AtomicInteger原理:
自旋锁 + CAS算法
# CAS算法:有3个操作数(内存值V,旧的预期值A,要修改的值B)
当旧的预期值A == 内存值,此时修改成功,将V改为B
当旧的预期值A ! = 内存值,此时修改失败,不做任何操作
并从新获取现在的最新值(这个重新获取的动作就是自旋)
synchronized和CAS区别
# 相同点:
synchronized每次操作共享数据都会上锁。(悲观锁)
# 不同点:
CAS从乐观角度出发,假设每次获取数据别人都不会修改,所以不会上锁,只不过在修改共享数据的时候,会检查一下,别人有没有修改过这个数据。如果别人修改过,那么就再次获取现在最新的值,如果别人没有修改过,那么直接修改共享数据的值。(乐观锁)
并发工具类
1. HashMap 是线程不安全的,多线程环境下会有数据安全问题;
2. Hashtable 是线程安全的,但是会将整张表锁起来,效率低下;
3. ConcurrentHashMap 也是线程安全的,效率较高。
jdk1.7前ConcurrentHashMap原理解析:
创建对象:
创建一个默认长度为16,默认加载因子为0.75的数组,数组名为Segment
再创建一个长度为2的小数组,把地址值赋值给0索引,其他索引都是null
添加:
第一次会根据键的哈希值来计算出在大数组中应存入的位置,如果为null,则按照模板创建小数组,创建完毕,会二次哈希,计算出在小数组中应存入的位置,为null直接存入,如果不为null,就会根据记录的地址值找到小数组,二次哈希,计算出在小数组中应存入的位置,如果需要扩容,则将小数组扩容两倍,如果不需要扩容,则就会看小数组的这个位置有没有元素,如果没有元素,则直接存,如果有元素,旧会调用equals方法,比较属性值,如果equals为true,则不存,如果是false,形成哈希桶结构。
jdk1.8后ConcurrentHashMap原理解析:
底层结构:哈希表(数组、链表、红黑树的结合体)
结合CAS机制 + synchronized同步代码块形式保证线程安全
如果使用空参构造创建ConcurrentHashMap对象,则什么事情都不做,在第一次添加元素的时候创建哈希表;
计算当前元素应存入的索引;
如果该索引位置为null,则利用cas算法,将本届点添加到数组中;
如果该索引位置不为null,则利用volayile关键字获得当前位置最新的结点地址,挂在他下面,变成链表;
当链表的长度大于等于8,自动转换成红黑树;
以链表或者红黑树头结点为锁对象,配合悲观锁保证多线程操作集合时数据的安全性。
CountDownLatch
public class CountDownLatch_demo {
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(3);
new Thread(new Mother(countDownLatch)).start();
new Thread(new Son1(countDownLatch)).start();
new Thread(new Son2(countDownLatch)).start();
new Thread(new Son3(countDownLatch)).start();
}
}
class Mother implements Runnable{
private CountDownLatch countDownLatch;
public Mother(CountDownLatch countDownLatch){
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
// 妈妈线程在等待
countDownLatch.await();
System.out.println("妈妈收拾碗筷!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Son1 implements Runnable{
private CountDownLatch countDownLatch;
public Son1(CountDownLatch countDownLatch){
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
System.out.println("son1吃完了");
countDownLatch.countDown();
}
}
class Son2 implements Runnable{
private CountDownLatch countDownLatch;
public Son2(CountDownLatch countDownLatch){
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
System.out.println("son2吃完了");
countDownLatch.countDown();
}
}
class Son3 implements Runnable{
private CountDownLatch countDownLatch;
public Son3(CountDownLatch countDownLatch){
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
System.out.println("son3吃完了");
countDownLatch.countDown();
}
}
# 使用场景
让某一条线程等待其他线程执行完毕之后再执行。
方法 | 解释 |
---|---|
public CountDownLatch(int count) | 参数传递线程数,表示等待线程数量,并等以一个计数器 |
public void await() | 让线程等待,计数器为0,唤醒等待线程 |
public void countDown() | 当前线程执行完毕,计数器-1 |
Semaphore
public class Semaphore_demo {
public static void main(String[] args) {
MyRunnable_Semaphore myRunnable_semaphore = new MyRunnable_Semaphore();
for (int i = 0; i < 10; i++) {
new Thread(myRunnable_semaphore).start();
}
}
}
class MyRunnable_Semaphore implements Runnable{
Semaphore semaphore = new Semaphore(2);
@Override
public void run() {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "获得通行证");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "归还通行证");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Thread-2获得通行证
Thread-6获得通行证
Thread-2归还通行证
Thread-6归还通行证
Thread-4获得通行证
Thread-9获得通行证
Thread-4归还通行证
Thread-9归还通行证
Thread-5获得通行证
Thread-8获得通行证
Thread-8归还通行证
Thread-5归还通行证
Thread-1获得通行证
Thread-7获得通行证
Thread-1归还通行证
Thread-7归还通行证
Thread-3获得通行证
Thread-0获得通行证
Thread-3归还通行证
Thread-0归还通行证