多线程
线程与进程区别
进程是所有线程的集合,每一个线程是进程中的一条执行路径。
优点:使用多线程可以提高程序的执行效率。
创建线程的三种方式
-
第一种,继承Thread类,重写run方法
package com.cjj.thread; /** * @ClassName ThreadStudy_1 * @Date 2021/06/27 23:11:25 * @Created by ccc-j * @email ccc-ju@outlook.com */ /** * 什么是进程? * 进程其实就是一个应用程序,进程是所有线程的集合. * 什么是线程? * 线程就是一条执行路径. * main 主线程 自己创建的是子线程 * gc线程 专门回收垃圾 */ public class ThreadStudy_1 { public static void main(String[] args) { System.out.println("开始创建线程:"); // 1.继承Thread类,重写run方法 // 2.调用继承的类的start方法,执行线程 CreateThread createThread = new CreateThread(); // 启动线程,调用start方法,不是run方法,调用run方法相当于主线程执行 createThread.start(); System.out.println("线程已经开始启动,main"); for(int i = 0; i < 100; i++){ System.out.println("main() i: " + i); } } } class CreateThread extends Thread{ /** * run方法 需要线程的执行代码 */ @Override public void run() { for(int i = 0; i < 100; i++){ System.out.println("run() i: " + i); } } }
-
第二种,实现Runable接口,重写run方法
package com.cjj.thread; /** * @ClassName ThreadStudy_2 * @Date 2021/06/27 23:45:14 * @Created by ccc-j * @email ccc-ju@outlook.com */ public class ThreadStudy_2 { public static void main(String[] args) { System.out.println("创建线程开始!main"); // 1.定义一个类,实现Runnable接口,重写run方法 CreateRunnable createRunnable = new CreateRunnable(); // 2.实现Runnable方法时候与继承Thread的区别 Thread thread = new Thread(createRunnable); thread.start(); System.out.println("创建线程结束!main"); for(int i = 0; i < 100; i++){ System.out.println("main() i: " + i); } } } class CreateRunnable implements Runnable{ @Override public void run() { for(int i = 0; i < 100; i++){ System.out.println("run() i: " + i); } } }
-
第三种,使用匿名内部类
package com.cjj.thread; /** * @ClassName ThreadStudy_3 * @Date 2021/06/27 23:58:49 * @Created by ccc-j * @email ccc-ju@outlook.com */ public class ThreadStudy_3 { public static void main(String[] args) { System.out.println("创建线程成功!"); // 匿名内部类方式创建线程 new Thread(new Runnable() { @Override public void run() { for(int i = 0; i < 100; i++){ System.out.println("run() i: " + i); } } }).start(); for(int i = 0; i < 100; i++){ System.out.println("main() i: " + i); } System.out.println("创建线程结束!"); } }
使用继承Thread类还是使用实现Runnable接口好?
实现接口更好,
java是单继承,多实现,实现了接口还可以继续使用继承,继承了一个类之后就不能再次继承其它类。
启动线程是使用调用start方法还是run方法?
开始执行线程,开启线程不是调用run方法,而是start方法。
多线程运行状态
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QdcAIpV4-1626624724858)(C:\Users\ccc-j\AppData\Roaming\Typora\typora-user-images\image-20210628224338923.png)]
- 新建状态:
- 当new 操作符创建一个线程时,线程还没开始运行,此时线程处于新建状态。
- 就绪状态:
- 一个新创建的线程并不会立即执行,要执行线程,需要调用start()方法。当线程调用了start方法即启动了线程,start()方法创建线程运行的系统资源并调度线程运行run()方法。当start()方法返回后线程就处于就绪状态。
- 运行状态:
- 当线程获得cpu时间后,它才进入运行状态,真正开始执行run()方法。
- 阻塞状态:
- 线程运行过程中,可能由于各种原因进入阻塞状态
- 线程调用sleep方法进入阻塞状态
- 线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者。
- 线程试图得到一个锁,而该锁正被其它线程持有。
- 线程在等待某个出发条件。
- 线程运行过程中,可能由于各种原因进入阻塞状态
- 死亡状态:
- 有两个原因会导致线程死亡:
- run方法正常退出而自然死亡
- 一个未捕获异常终止了run方法而使线程死亡。
- 为了确定线程在当前是否存活者(要么是可运行的,要么是阻塞状态中的),需要使用isAlive方法。如果是可运行或被阻塞,这个方法返回true,如果线程是new状态且是不可运行的,或者线程死亡了,则返回false。
- 有两个原因会导致线程死亡:
多线程分批处理数据
需求:目前有10w个用户,现在需要做活动,给每个用户发送祝福短信。
为了提高程序的效率,使用多线程技术分批发数据。没开一个线程,都会占用CPU资源
package com.cjj.thread.sms_send;
/**
* @ClassName BatachThread
* @Date 2021/06/28 23:38:38
* @Created by ccc-j
* @email ccc-ju@outlook.com
*/
import com.cjj.thread.ListUtils;
import com.cjj.thread.entity.UserEntity;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 多线程处理数据
*/
public class BatachThread {
public static void main(String[] args) {
// 1.初始化用户
List<UserEntity> userEntities = initUser();
// 2.定义每个线程最多跑多少数据
int userCount = 200;
// 3.计算线程数并且计算每个线程跑多少数据
List<List<UserEntity>> lists = ListUtils.splitList(initUser(), userCount);
for(int i = 0; i < lists.size(); i++){
List<UserEntity> entityList = lists.get(i);
// 4.让子线程分批异步处理数据
new UserThread(entityList).start();
//System.out.println("i:" + i + "--" + entityList.toString());
}
}
public static List<UserEntity> initUser(){
List<UserEntity> userEntities = new ArrayList<>();
for(int i = 1; i <= 1000; i++){
userEntities.add(UserEntity.builder().userId("id:" + i).userName("name:" + i).build());
}
return userEntities;
}
}
@Data
class UserThread extends Thread {
List<UserEntity> list;
public UserThread(List<UserEntity> list){
this.list = list;
}
@Override
public void run() {
for(UserEntity entity : list){
/*try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// none
}*/
System.out.println("name:" + getName() + "--" + entity.toString());
}
}
}
多线程同步
什么是线程安全?
- 当多个线程同时共享同一个全局变量或静态变量,做写的操作的时,可能会引发数据冲突问题,也就是线程安全问题。读操作不会发生数据冲突问题。
线程安全问题如何解决?
- 使用同步代码块
- 同步函数
- 静态同步函数
Synchronized
使用synchronized关键字包裹起来的代码能保证一次只有一个线程执行
同步函数使用的是什么锁?同步函数使用this锁
package com.cjj.thread_safe;class ThreadTrain3 implements Runnable { // 车票总数 private int count = 100; private Object object = new Object(); public boolean flag = true; @Override public void run() { // 线程1 flag为true if (flag) { while (count > 0) { synchronized (this) { try { Thread.sleep(40); } catch (Exception e) { } if (count > 0) { System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "张票"); count--; } } } } else { while (count > 0) { show(); } } } public synchronized void show() { if (count > 0) { try { Thread.sleep(40); } catch (Exception e) { } System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "张票"); count--; } }}/** * 证明同步方法使用this锁 * * @ClassName Thread_01 * @Date 2021/07/17 11:23:43 * @Created by ccc-j * @email ccc-ju@outlook.com */public class Thread_03 { public static void main(String[] args) throws InterruptedException { // 创建两个线程, 线程类一定要是一个实例 ThreadTrain3 threadTrain = new ThreadTrain3(); Thread thread_1 = new Thread(threadTrain); Thread thread_2 = new Thread(threadTrain); thread_1.start(); Thread.sleep(100); threadTrain.flag = false; thread_2.start(); }}
静态同步函数
package com.cjj.thread_safe;class ThreadTrain4 implements Runnable { // 车票总数 private static int count = 100; private Object object = new Object(); public boolean flag = true; @Override public void run() { // 线程1 flag为true if (flag) { while (count > 0) { synchronized (ThreadTrain4.class) { try { Thread.sleep(40); } catch (Exception e) { } if (count > 0) { System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "张票"); count--; } } } } else { while (count > 0) { show(); } } } public static synchronized void show() { if (count > 0) { try { Thread.sleep(40); } catch (Exception e) { } System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "张票"); count--; } }}/** * 静态同步函数 * * @ClassName Thread_01 * @Date 2021/07/17 11:23:43 * @Created by ccc-j * @email ccc-ju@outlook.com */public class Thread_04 { public static void main(String[] args) throws InterruptedException { // 创建两个线程, 线程类一定要是一个实例 ThreadTrain4 threadTrain = new ThreadTrain4(); Thread thread_1 = new Thread(threadTrain); Thread thread_2 = new Thread(threadTrain); thread_1.start(); Thread.sleep(40); threadTrain.flag = false; thread_2.start(); }}
多线程死锁
什么是多线程死锁?
同步嵌套同步,导致锁无法释放
package com.cjj.thread_safe;class ThreadTrain5 implements Runnable { // 车票总数 private static int count = 1000; private Object object = new Object(); public boolean flag = true; @Override public void run() { // 如果flag 为true 先拿到object这把锁,再拿this锁 if (flag) { while (count > 0) { synchronized (object) { show(); } } } else { while (count > 0) { show(); } } } public synchronized void show() { synchronized (object){ if (count > 0) { try { Thread.sleep(40); } catch (Exception e) { } System.out.println(Thread.currentThread().getName() + "出售第" + (1000 - count + 1) + "张票"); count--; } } }}/** * 死锁模拟 * * @ClassName Thread_01 * @Date 2021/07/17 11:23:43 * @Created by ccc-j * @email ccc-ju@outlook.com */public class Thread_05 { public static void main(String[] args) throws InterruptedException { // 创建两个线程, 线程类一定要是一个实例 ThreadTrain5 threadTrain = new ThreadTrain5(); Thread thread_1 = new Thread(threadTrain); Thread thread_2 = new Thread(threadTrain); thread_1.start(); Thread.sleep(40); threadTrain.flag = false; thread_2.start(); }}
多线程之间通讯
什么是多线程之间通讯?多线程之间通讯,其实就是多个线程在操作同一个资源,但是操作的动作不同
sleep与wait的区别?
sleep()属于Thread中,而wait()方法,是在Object中的sleep()虽然导致了程序暂停执行指定的时间,让出cpu给其它线程,但是它的监控依然保持,到达指定时间会自动恢复运行状态。在调用sleep方法过程中,线程不会释放对象锁。调用wait方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,对此对象调用noify()方法后本线程才会进入对象锁定池准备。为什么wait,notify定义在object类中?在特定场景下,我们要用多线程进行同步,同步的时候是我们自定义,所以把wait,notify放在object中,让任何对象都可以使用。
Synchroized缺点
不能手动开锁解锁
守护线程(后台线程)
主线程或jvm进程挂了,守护线程也会被停止掉。gc其实也是守护线程。
Join
让其它线程等待,只有当前线程执行完毕才会让出资源。
Java内存模型
线程三大特性
- 原子性:要么全部执行成功,要么全部执行失败(数据库的事务提交即有原子性),保证数据一致,是线程安全的一部分
- 可见性:当多个线程访问同一个变量时,一个线程修改了这个变量的值,其它线程能够立即看到修改的值
- 有序性:
Java内存模型,共享内存模型指的就是java内存模型(JVM),JMM决定一个线程对共享变量的写入时,能对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系,线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓存区,寄存器以及其它的硬件和编译器优化。
总结:Java内存模型,简称JMM,定义了一个线程对另一个线程可见。共享变量存放在主内存中,每个线程都有自己的本地内存,当多个线程同时访问同一个数据的时候,可能本地内存没有及时刷新到主内存,所以就会发生线程安全问题。
Volatile
- Volatile关键字的作用是变量在多个线程之间可见
- Volatile不能保证数据的原子性
package com.cjj.volatile_thread;
class VolatileDemo extends Thread{
public volatile boolean flag = true;
@Override
public void run() {
System.out.println("子线程开始执行----");
while (flag){
}
System.out.println("子线程结束执行----");
}
public void isRun(boolean flag){
this.flag = flag;
}
}
/**
* @ClassName VolatileThread
* @Date 2021/07/18 19:21:00
* @Created by ccc-j
* @email ccc-ju@outlook.com
*/
public class VolatileThread {
public static void main(String[] args) throws InterruptedException {
VolatileDemo t1 = new VolatileDemo();
t1.start();
Thread.sleep(300);
t1.isRun(false);
System.out.println(t1.flag);
}
}
AtomicInteger
原子类
volatile与synchronized区别
仅靠volatile不能保证线程的安全性。(原子性)
1.volatile轻量级,只能修饰变量。synchronized重量级,还可以修饰方法。
2.volatile只能保证数据的可见性,不能用来同步,因为多个线程并发访问volatile修饰的变量不会阻塞
synchronized不仅保证可见性,还能保证原子性,因为,只有获得了锁的线程才能进临界区,从而保证临界区中的所有语句都全部执行,多个线程争抢synchronized锁时,会阻塞
线程安全性
线程安全性包括两方面:可见性,原子性
仅仅使用volatile关键字并不能保证线程的安全性,而synchronized可以实现线程的安全性
ThreadLocal
提高一个线程的局部变量,访问某个线程拥有自己局部变量。
实现原理,Map.put(Thead.getCurrentThread(), xxx);
线程池
为什么要使用线程池?
通过线程池来管理线程,因为启动或停止线程,非常耗资源,所以将线程交给线程池来管理,节约内存。
- newCachedThreadPool:可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
- newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待,
- newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行。
- newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务指定顺序(FIFO,LIFO,优先级)执行
gitee地址:https://gitee.com/ccc-ju/java-study