多线程基础
文章目录
前言
本文主要介绍线程基础问题
提示:以下是本篇文章正文内容,下面案例可供参考
一、进程和线程?
进程: 所有线程的集合
线程: 进程中的一个执行单元
二、同步和异步?
同步: 顺序执行,需要等待结果
异步: 一起执行,不需要等待结果
三、多线程的好处?
一句话:提高程序的执行效率
形象理解:有给一条公路画黄线的任务
单线程==>从起点开始画,路况堵的情况下就画慢一点,等堵车结束,最后画到终点
多线程==>把这条公里分成多段,单核情况下就好比只有一个刷子,哪里堵了,就把刷子拿到不堵的路段先画(cpu调度),最后画完.多核好比多个刷子,多路段一起画,最后完工,提高效率,不用堵那里.
四、多线程创建方法?
1.继承Thread类
class MyThread extends Thread{
@Override
public void run() {
System.out.println("用户线程");
}
}
public class Test01 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
System.out.println("main线程");
}
}
2.实现Runable接口
class MyRunable implements Runnable{
@Override
public void run() {
System.out.println("用户线程");
}
}
public class Test01 {
public static void main(String[] args) {
Thread thread = new Thread(new MyRunable());
thread.start();
System.out.println("main线程");
}
}
3.实现Callable接口(有返回值)
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("用户线程");
return 1;
}
}
public class Test01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 根据task,获取结果
System.out.println("main线程");
FutureTask<Integer> task = new FutureTask<>(new MyCallable());
Thread thread = new Thread(task);
thread.start();
System.out.println("task结果:"+task.get());
}
}
五、多线程状态?
六、守护线程和用户线程?
守护线程: 主线程结束,守护线程直接结束,setDaemon(true)设置成守护线程
用户线程: 主线程结束,用户线程继续执行
七、线程安全?
1. 演示安全问题
public class Test01 implements Runnable {
private Integer count = 100;
public static void main(String[] args) throws ExecutionException, InterruptedException {
Test01 test01 = new Test01();
Thread t1 = new Thread(test01, "窗口1");
Thread t2 = new Thread(test01, "窗口2");
t1.start();
t2.start();
}
@Override
public void run() {
while (count > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
sale();
}
}
public void sale() {
System.out.println(Thread.currentThread().getName() + ",在出售第" + (100 - count + 1) + "张票");
count--;
}
}
2. 出现原因
- JMM内存模型
JMM定义了线程和主内存的抽象关系: 线程共享变量存在主内存,每个线程有自己的本地内存,本地内存存储了共享变量的副本,如图
- 线程A和线程B通讯过程
首先线程A把修改过的本地内存副本刷新到主内存,然后,线程B到主内存中读取A改过的结果. - 出现两个窗口都卖第一张票的原因
线程A卖出第一张票,剩余99还没来得及刷新到主内存,线程B开始卖票,B不知道A改了(因为B只能通过主内存知道,而A恰好没来的急刷新到主内存),所有也卖第一张票.
3. 解决安全问题
// 加 synchronized 关键字,线程同步,一次只能一个线程执行(刷新完毕后其他线程执行)
public synchronized void sale() {
// 这个判断是最后一张票,防止出现负数.
if (count>0){
System.out.println(Thread.currentThread().getName() + ",在出售第" + (100 - count + 1) + "张票");
count--;
}
}
八、线程死锁?
互相需要对方持有的资源,造成死锁
public class Test02 implements Runnable {
private Integer count = 100;
private Boolean flag = true;
private Object obj = new Object();
public static void main(String[] args) throws ExecutionException, InterruptedException {
Test02 test02 = new Test02();
Thread t1 = new Thread(test02, "窗口1");
Thread t2 = new Thread(test02, "窗口2");
t1.start();
Thread.sleep(50);
test02.flag=false;
t2.start();
}
@Override
public void run() {
if (flag) {
synchronized (obj) {
while (count > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
sale();
}
}
} else {
sale();
}
}
// 加 synchronized 关键字
public synchronized void sale() {
// 这个判断是最后一张票,防止出现负数.
synchronized (obj) {
if (count > 0) {
System.out.println(Thread.currentThread().getName() + ",在出售第" + (100 - count + 1) + "张票");
count--;
}
}
}
}
原因
窗口1持有obj锁,需要this锁
窗口2持有this锁,需要obj锁 造成互相等待,死锁
九、多线程特性?
1. 原子性
一个操作或者多个操作,要么说都只执行,要么说都不执行
例如: A账户向B账户转账100元,A账户减100元,B账户增加100元.原子性就是A减少和B增加必须都成功,不然都失败.
2. 可见性
即内存模型中,某个线程修改了自己本地内存副本必须立刻刷新到主内存,主内存修改对其他线程即为可见性
Volatile可以实现可见性,禁止指令重排
- 不可见演示
public class Test04 implements Runnable {
private Boolean flag = true;
@Override
public void run() {
System.out.println("子线程开始");
while (flag) {
};
System.out.println("子线程结束");
}
public void setFlag(){
this.flag=false;
}
public static void main(String[] args) throws InterruptedException {
Test04 test04 = new Test04();
Thread thread = new Thread(test04);
thread.start();
System.out.println("main线程开始");
Thread.sleep(50);
test04.setFlag();
System.out.println("main线程结束");
}
}
- Volatile解决可见性
// 加上volatile
private volatile Boolean flag = true;
3. 有序性
十、线程通讯?
wait 和notify
public class Test05 {
public static void main(String[] args) {
Student student = new Student();
MyInput myInput = new MyInput(student);
MyOut myOut = new MyOut(student);
myInput.start();
myOut.start();
}
}
class Student {
public String name;
public String sex;
// false 写 true 读
public boolean flag = false;
}
class MyInput extends Thread {
private Student student;
public MyInput(Student student) {
this.student = student;
}
@Override
public void run() {
int count = 0;
while (true) {
synchronized (student) {
if (student.flag) {
try {
student.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (count == 0) {
student.name = "张三";
student.sex = "男";
} else {
student.name = "李四";
student.sex = "女";
}
count = (count + 1) % 2;
student.flag = true;
student.notify();
}
}
}
}
class MyOut extends Thread {
private Student student;
public MyOut(Student student) {
this.student = student;
}
@Override
public void run() {
while (true) {
synchronized (student) {
if (!student.flag) {
try {
student.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(student.name + "--" + student.sex);
student.flag = false;
student.notify();
}
}
}
}
保证写一个读一个(生产消费模式)
十一、线程池?
1.常见线程池
- newCachedThreadPool 缓存线程池,有就复用,没有就创建
public class NewCachedThreadPoolTest {
public static void main(String[] args) {
// 创建一个可缓存线程池
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
try {
// sleep可明显看到使用的是线程池里面以前的线程,没有创建新的线程
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
// 打印正在执行的缓存线程信息
System.out.println(Thread.currentThread().getName()
+ "正在被执行");
try {
// sleep可明显看到使用的是线程池里面以前的线程,没有创建新的线程
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
- newFixedThreadPool 固定数量线程池,线程数量固定
public class Test06 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"正在执行");
}
});
}
}
}
- newScheduledThreadPool 定时线程池,可执行一下定时任务
@Slf4j
public class Test06 {
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
log.info("开始");
scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
log.info("延迟1s执行");
}
},1, TimeUnit.SECONDS);
}
}
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
log.info("每隔1s执行");
}
},2,1, TimeUnit.SECONDS);
- newSingleThreadExecutor 单线程线程池,只有一个线程
public class Test06 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"正在执行");
}
});
}
}
}
- 自定义
@Slf4j
public class Test06 {
public static void main(String[] args) {
MyTask myTask = new MyTask();
MyTask myTask1 = new MyTask();
MyTask myTask2 = new MyTask();
MyTask myTask3 = new MyTask();
//初始化队列数量
ArrayBlockingQueue queue = new ArrayBlockingQueue<Runnable>(10);
//3 --核心线程数
// 6 --最大线程数
// 500s --清除时间
//queue --阻塞队列
ThreadPoolExecutor myExcutor = new ThreadPoolExecutor(3, 6, 500, TimeUnit.SECONDS, queue);
myExcutor.execute(myTask);
myExcutor.execute(myTask1);
myExcutor.execute(myTask2);
myExcutor.execute(myTask3);
}
}
class MyTask implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"正在执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2. 线程池参数
3. 线程池流程
1>如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会创建一个线程去执行这个任务;
2>如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务;
3>如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理;
4>如果线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止。
4. 拒绝策略
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。 ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务
ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务
十二、countDownLatch?
countDownLatch是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就 -1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。
@Slf4j
public class CountDownLatchTest {
public static void main(String[] args) {
final CountDownLatch latch = new CountDownLatch(2);
log.debug("主线程开始执行…… ……");
//第一个子线程执行
ExecutorService es1 = Executors.newSingleThreadExecutor();
es1.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
log.debug("子线程:"+Thread.currentThread().getName()+"执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
latch.countDown();
}
});
es1.shutdown();
//第二个子线程执行
ExecutorService es2 = Executors.newSingleThreadExecutor();
es2.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("子线程:"+Thread.currentThread().getName()+"执行");
latch.countDown();
}
});
es2.shutdown();
log.debug("等待两个线程执行完毕…… ……");
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("两个子线程都执行完毕,继续执行主线程");
}
}