1. 线程的状态
线程对象在不同的时期有不同的状态。Java中的线程状态被定义在了java.lang.Thread.State枚举类中。
线程状态 | 具体含义 |
---|---|
NEW(新建) | 创建线程对象后,线程处于新建状态 |
RUNNABLE(就绪) | 线程对象调用start方法进入就绪状态,此时还没有抢到CPU,没有执行权 |
BLOCKED(阻塞) | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。 |
WAITING(等待) | 造成线程等待的原因有两种,分别是调用Object.wait()、join()方法。当另一个线程去调用notify()或notifyAll(),线程进入就绪状态;一个因为join()而等待的线程正在等待另一个线程结束。 |
TIMED_WAITING(计时等待) | 一个在限定时间内等待的线程的状态。也称之为限时等待状态。造成线程限时等待状态的原因有三种,分别是:Thread.sleep(long),Object.wait(long)、join(long)。 |
TERMINATED(结束) | 一个完全运行完成的线程的状态。也称之为终止状态、结束状态 |
2. 线程池
系统创建一个线程的成本是比较高的,因为它涉及到与操作系统交互,当程序中需要创建大量生存期很短暂的线程时,频繁的创建和销毁线程对系统的资源消耗有可能大于业务处理是对系统资源的消耗,这样就有点"舍本逐末"了。针对这一种情况,为了提高性能,可以采用线程池。
线程池在启动的时,会创建大量空闲线程,当我们向线程池提交任务的时,线程池就会启动一个线程来执行该任务。等待任务执行完毕以后,线程并不会死亡,而是再次返回到线程池中称为空闲状态。等待下一次任务的执行。
2.1 静态方法创建线程池:
- static ExecutorService newCachedThreadPool() 创建一个默认的线程池
- static ExecutorService newFixedThreadPool(int nThreads) 创建一个指定最多线程数量的线程池
newCachedThreadPool代码实现:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyThreadPoolDemo {
public static void main(String[] args) throws InterruptedException {
//1,创建一个默认的线程池对象.池子中默认是空的.默认最多可以容纳int类型的最大值.
ExecutorService executorService = Executors.newCachedThreadPool();
//Executors --- 可以帮助我们创建线程池对象
//ExecutorService --- 可以帮助我们控制线程池
executorService.submit(()->{
System.out.println(Thread.currentThread().getName() + "在执行了");
});
//Thread.sleep(2000);
executorService.submit(()->{
System.out.println(Thread.currentThread().getName() + "在执行了");
});
executorService.shutdown();
}
}
newFixedThreadPool代码实现:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
public class MyThreadPoolDemo2 {
public static void main(String[] args) {
//参数不是初始值而是最大值
ExecutorService executorService = Executors.newFixedThreadPool(10);
ThreadPoolExecutor pool = (ThreadPoolExecutor) executorService;
System.out.println(pool.getPoolSize());//0
executorService.submit(()->{
System.out.println(Thread.currentThread().getName() + "在执行了");
});
executorService.submit(()->{
System.out.println(Thread.currentThread().getName() + "在执行了");
});
System.out.println(pool.getPoolSize());//2
executorService.shutdown();
}
}
2.2 自定义线程池对象
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(核心线程数量,最大线程数量,空闲线程最大存活时间,任务队列,创建线程工厂,任务的拒绝策略);
代码实现:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class SelfPool {
public static void main(String[] args) {
// 参数一:核心线程数量
// 参数二:最大线程数
// 参数三:空闲线程最大存活时间
// 参数四:时间单位
// 参数五:任务队列
// 参数六:创建线程工厂
// 参数七:任务的拒绝策略
ThreadPoolExecutor tpe = new ThreadPoolExecutor(
1,
5,
2,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
tpe.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"开始执行");
}
});
tpe.submit(()->{
System.out.println(Thread.currentThread().getName()+"开始执行");
});
tpe.shutdown();
}
}
任务拒绝策略:
ThreadPoolExecutor.AbortPolicy: 丢弃任务并抛出RejectedExecutionException异常。是默认的策略。
ThreadPoolExecutor.DiscardPolicy: 丢弃任务,但是不抛出异常 这是不推荐的做法。
ThreadPoolExecutor.DiscardOldestPolicy: 抛弃队列中等待最久的任务 然后把当前任务加入队列中。
ThreadPoolExecutor.CallerRunsPolicy: 调用任务的run()方法绕过线程池直接执行。
3. 关键字volatile
当A线程修改了共享数据时,B线程没有及时获取到最新的值,如果还在使用原先的值,就会出现问题
- 每一个线程都有自己的线程栈。
- 每一个线程在使用共享变量的时候,都会先拷贝一份到线程栈的变量副本中。
- 在线程中,每一次使用共享变量时,直接使用的是线程栈副本变量的值。
Volatile关键字 : 强制线程每次在使用共享变量的时候,都会将共享区域最新的值拷贝到线程栈的副本中。
public class Demo {
public static void main(String[] args) {
MyThread1 t1 = new MyThread1();
t1.setName("小璐同学");
t1.start();
MyThread2 t2 = new MyThread2();
t2.setName("小邹同学");
t2.start();
}
}
public class Money {
public static volatile int money = 100000;
}
//线程A
public class MyThread1 extends Thread {
@Override
public void run() {
while(Money.money == 100000){
}
System.out.println("基金已经不是十万了");
}
}
//线程B,修改了共享变量
public class MyThread2 extends Thread {
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
Money.money = 90000;
}
}
4. 原子性
所谓的原子性是指在一次操作或者多次操作中,要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行,多个操作是一个不可以分割的整体。
java从JDK1.5开始提供了java.util.concurrent.atomic包,一个小型工具包,支持单个变量上的无锁线程安全编程。因为变量的类型有很多种,所以在Atomic包里一共提供了13个类,4种类型的原子更新方式,分别是原子更新基本类型、原子更新数组、原子更新引用和原子更新属性(字段)。
AtomicInteger的常用方法如下:
public AtomicInteger(): 初始化一个默认值为0的原子型Integer
public AtomicInteger(int initialValue): 初始化一个指定值的原子型Integerint get(): 获取值
int getAndIncrement(): 以原子方式将当前值加1,注意,这里返回的是自增前的值。
int incrementAndGet(): 以原子方式将当前值加1,注意,这里返回的是自增后的值。
int addAndGet(int data): 以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果。
int getAndSet(int value): 以原子方式设置为newValue的值,并返回旧值。
import java.util.concurrent.atomic.AtomicInteger;
public class Demo01 {
// 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);
int i = ac1.get();
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 i1 = ac3.incrementAndGet();
System.out.println(i1);//自增后的值
System.out.println(ac3.get());
AtomicInteger ac4 = new AtomicInteger(10);
int i2 = ac4.addAndGet(20);
System.out.println(i2);
System.out.println(ac4.get());
AtomicInteger ac5 = new AtomicInteger(100);
int andSet = ac5.getAndSet(20);
System.out.println(andSet);
System.out.println(ac5.get());
}
}
AtomicInteger原理 : 自旋锁 + CAS 算法
CAS算法:
有3个操作数(内存值V, 旧的预期值A,要修改的值B)
当旧的预期值A == 内存值 此时修改成功,将V改为B
当旧的预期值A != 内存值 此时修改失败,不做任何操作
并重新获取现在的最新值(这个重新获取的动作就是自旋)
import java.util.concurrent.atomic.AtomicInteger;
public class MyAtomThread implements Runnable {
AtomicInteger ac = new AtomicInteger(0);
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//1,从共享数据中读取数据到本线程栈中.
//2,修改本线程栈中变量副本的值
//3,会把本线程栈中变量副本的值赋值给共享数据.
int count = ac.incrementAndGet();
System.out.println("已经送了" + count + "个冰淇淋");
}
}
}
5. 并发工具类
5.1 并发工具类ConcurrentHashMap基本使用
- HashMap是线程不安全的。多线程环境下会有数据安全问题
- Hashtable是线程安全的,但是会将整张表锁起来,效率低下
- ConcurrentHashMap也是线程安全的,效率较高。 在JDK7和JDK8中,底层原理不一样。
import java.util.Hashtable;
import java.util.concurrent.ConcurrentHashMap;
public class MyConcurrentHashMapDemo {
public static void main(String[] args) throws InterruptedException {
ConcurrentHashMap<String, String> hm = new ConcurrentHashMap<>(100);
Thread t1 = new Thread(() -> {
for (int i = 0; i < 25; i++) {
hm.put(i + "", i + "");
}
});
Thread t2 = new Thread(() -> {
for (int i = 25; i < 51; i++) {
hm.put(i + "", i + "");
}
});
t1.start();
t2.start();
System.out.println("----------------------------");
//为了t1和t2能把数据全部添加完毕
Thread.sleep(1000);
//0-0 1-1 ..... 50- 50
for (int i = 0; i < 51; i++) {
System.out.println(hm.get(i + ""));
}//0 1 2 3 .... 50
}
}
5.2 并发工具类CountDownLatch基本使用
使用场景: 让某一条线程等待其他线程执行完毕之后再执行
方法 | 解释 |
---|---|
public CountDownLatch(int count) | 构造方法,参数传递线程数,表示等待线程数量 |
public void await() | 让线程等待 |
public void countDown() | 当前线程执行完毕后,说一声 |
案例:等待三个孩子线程吃完饺子后,妈妈线程才开始洗碗筷
主方法开启线程:
import java.util.concurrent.CountDownLatch;
public class MyCountDownLatchDemo {
public static void main(String[] args) {
//1.创建CountDownLatch的对象,需要传递给四个线程。
//在底层就定义了一个计数器,此时计数器的值就是3
CountDownLatch countDownLatch = new CountDownLatch(3);
//2.创建四个线程对象并开启他们。
MotherThread motherThread = new MotherThread(countDownLatch);
motherThread.start();
ChileThread1 t1 = new ChileThread1(countDownLatch);
t1.setName("小明");
ChileThread2 t2 = new ChileThread2(countDownLatch);
t2.setName("小红");
ChileThread3 t3 = new ChileThread3(countDownLatch);
t3.setName("小刚");
t1.start();
t2.start();
t3.start();
}
}
孩子线程:
import java.util.concurrent.CountDownLatch;
//孩子1
public class ChileThread1 extends Thread {
private CountDownLatch countDownLatch;
public ChileThread1(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
//1.吃饺子
for (int i = 1; i <= 10; i++) {
System.out.println(getName() + "在吃第" + i + "个饺子");
}
//2.吃完说一声
//每一次countDown方法的时候,就让计数器-1
countDownLatch.countDown();
}
}
import java.util.concurrent.CountDownLatch;
//孩子2
public class ChileThread2 extends Thread {
private CountDownLatch countDownLatch;
public ChileThread2(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
//1.吃饺子
for (int i = 1; i <= 15; i++) {
System.out.println(getName() + "在吃第" + i + "个饺子");
}
//2.吃完说一声
//每一次countDown方法的时候,就让计数器-1
countDownLatch.countDown();
}
}
import java.util.concurrent.CountDownLatch;
//孩子3
public class ChileThread3 extends Thread {
private CountDownLatch countDownLatch;
public ChileThread3(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
//1.吃饺子
for (int i = 1; i <= 20; i++) {
System.out.println(getName() + "在吃第" + i + "个饺子");
}
//2.吃完说一声
//每一次countDown方法的时候,就让计数器-1
countDownLatch.countDown();
}
}
妈妈线程:
import java.util.concurrent.CountDownLatch;
public class MotherThread extends Thread {
private CountDownLatch countDownLatch;
public MotherThread(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
//1.等待
try {
//当计数器变成0的时候,会自动唤醒这里等待的线程。
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
//2.收拾碗筷
System.out.println("妈妈在收拾碗筷");
}
}
5.3 并发工具类Semaphore基本使用
使用场景 : 可以控制访问特定资源的线程数量
案例:某个通道最多允许2辆车辆同时通过。
实现步骤 :
- 需要有人管理这个通道 Semaphore对象
- 当有车进来了,发通行许可证 acquire方法
- 当车出去了,收回通行许可证 release方法
- 如果通行许可证发完了,那么其他车辆只能等着
代码实现:
import java.util.concurrent.Semaphore;
public class MyRunnable implements Runnable {
//1.获得管理员对象,
private Semaphore semaphore = new Semaphore(2);
@Override
public void run() {
//2.获得通行证
try {
semaphore.acquire();
//3.开始行驶
System.out.println("获得了通行证开始行驶");
Thread.sleep(2000);
System.out.println("归还通行证");
//4.归还通行证
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class MySemaphoreDemo {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
for (int i = 0; i < 100; i++) {
new Thread(mr).start();
}
}
}
如有错误欢迎留言评论,及时更正。2021年6月15日 羽露风