目录
一、多线程
进程:当一个程序进入内存运行,就变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能。 如 迅雷软件运行中
线程:线程是进程的一个执行单元,负责当前继承中的程序执行,一个进程中至少有一个线程,可以有多个线程。 如迅雷软件中 同时下载3个任务
程序运行原理
分时调度:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间。
抢占式调度:优先让优先级高的线程使用CPU,如果线程优先级相同,那么会随机选择一个,Java使用的为抢占式调度。
public static void main(String[] args) {
SayHi();
//要等待10万次执行结束后 才能执行 最下面的输出。
System.out.println("===========================单线程======================");
}
public static void SayHi() {
for (int i = 0; i < 100000; i++) {
System.out.println(i);
}
}
1.创建多线程
1. 继承Tread类
public class Main {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start(); //开一条新线程 自动调用run方法 如果.run() 则不会开启新线程
for (int i = 0; i < 10; i++) {
System.out.println("主线程输出"+i);
}
//两个线程一起执行
}
}
class MyThread extends Thread{
@Override
public void run(){
for (int i = 0; i < 10; i++) {
//程序主线程名字:main,自定义的线程:Thread-0、Thread-1数字顺延 也可以使用setName()自定义线程名称
System.out.println(Thread.currentThread().getName()); //当前线程的名字
System.out.println(getName());
System.out.println("子线程输出"+i);
try {
//sleep()方法 睡眠多少毫秒在执行
Thread.sleep(1000l);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
不要把主线程放在前面,否则一定是主线程先跑完,然后再开启子线程。
类需要继承Tread类,无法继承其他类,不利于扩展
2. 实现Runnable接口
public class Main {
public static void main(String[] args) throws InterruptedException {
MyRunnable myThread = new MyRunnable();
new Thread(myThread).start();
for (int i = 0; i < 10; i++) {
Thread.sleep(1000l);
System.out.println("主线程输出" + i);
}
//两个线程一起执行
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()); //当前线程的名字
System.out.println("子线程输出" + i);
try {
//sleep()方法 睡眠多少毫秒在执行
Thread.sleep(1000l);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
虽然继承了接口,可以继承其他类,扩展性强。但是,多了一层对象,如果线程有执行结果不可以直接返回。
Lambda优化
public class Main {
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()); //当前线程的名字
System.out.println("子线程输出" + i);
try {
//sleep()方法 睡眠多少毫秒在执行
Thread.sleep(1000l);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}).start();
for (int i = 0; i < 10; i++) {
Thread.sleep(1000l);
System.out.println("主线程输出" + i);
}
//两个线程一起执行
}
}
3. 实现Callable接口
public class Main {
public static void main(String[] args) throws InterruptedException {
Callable<Integer> callable1 = new MyCallable(100);
FutureTask<Integer> f1 = new FutureTask<>(callable1);
Thread thread1 = new Thread(f1);
thread1.start();
Callable<Integer> callable2 = new MyCallable(100);
FutureTask<Integer> f2 = new FutureTask<>(callable1);
Thread thread2 = new Thread(f2);
thread2.start();
Integer a = 0;
Integer b = 0;
try {
a = f1.get();
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
try {
b = f2.get();
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
//等拿到 a的结果 和 b的结果 才会往下执行 数据是一起拿的
System.out.println(a+b);
}
}
class MyCallable implements Callable<Integer> {
private int n;
public MyCallable(int n) {
this.n = n;
}
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName());
int sum = 0;
for (int i = 1; i <= n ; i++) {
Thread.sleep(100l);
sum += i;
}
return sum;
}
}
实现Callable可以返回结果
2.Thread类常用方法
获取当前线程名字
Thread.currentThread().getName()
睡眠多少毫秒
Thread.sleep(100l);
设置线程名
Thread thread1 = new Thread(f1);
thread1.setName("no.1");
thread1.start();
3.线程安全
当多个线程操作同时操作一个共享资源的时候可能会出现业务安全问题。
场景演示,比如两人同时去银行取钱。
多线程并发,同时访问共享资源,修改资源。
4.同步线程
让多个线程先后依次访问共享资源,对公共资源加锁。
1.方式一:同步代码块
对核心代码块进行上锁 synchronized
对于实例方法使用this作为锁对象
对于静态方法使用类名.class作为锁对象
synchronized(同步锁对象){
}
public class Account {
private String cardId;
private Double money;
public Account(){
}
public Account(String cardId, Double money) {
this.cardId = cardId;
this.money = money;
}
public void WithDraw(double money) {
String name = Thread.currentThread().getName();
//对共享资源加锁
synchronized (this){
if(this.money >= money){
System.out.println(name + "取出"+money);
this.money = this.money - money;
System.out.println(name + "余额为:"+this.money);
}else{
System.out.println(name + "来取钱,余额不足!");
}
}
}
public String getCardId() {
return cardId;
}
public void setCardId(String cardId) {
this.cardId = cardId;
}
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
}
public class WithDrawThread extends Thread{
private Account acc;
@Override
public void run(){
acc.WithDraw(100);
}
public WithDrawThread(String name, Account acc) {
super(name);
this.acc = acc;
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
Account acc = new Account("2020230023023020323",100d);
new WithDrawThread("david",acc).start();
new WithDrawThread("viki",acc).start();
}
}
david取出100.0
david余额为:0.0
viki来取钱,余额不足!
2.方式二:同步方法
对核心方法上锁 synchronized
修饰符 synchronized 返回值类型 方法名(形参列表){
}
底层也是隐藏式锁对象的,只是锁的范围是整个方法
如果是实例方法使用this作为锁对象
如果是静态方法使用类名.class作为锁对象
//方法上锁
public synchronized void WithDraw(double money) {
String name = Thread.currentThread().getName();
if (this.money >= money) {
System.out.println(name + "取出" + money);
this.money = this.money - money;
System.out.println(name + "余额为:" + this.money);
} else {
System.out.println(name + "来取钱,余额不足!");
}
}
3.方式三: Lock锁
Lock是JDK5以后新出的一个对象,比synchronized方法更加广泛。
Lock是接口,使用他的实现类ReentrantLock来构建Lock锁对象
private final Lock lock = new ReentrantLock();
//方法上锁
public void WithDraw(double money) {
String name = Thread.currentThread().getName();
lock.lock(); //上锁
if (this.money >= money) {
System.out.println(name + "取出" + money);
this.money = this.money - money;
System.out.println(name + "余额为:" + this.money);
} else {
System.out.println(name + "来取钱,余额不足!");
}
lock.unlock(); //解锁
}
5.线程同步
wait()方法可以使一个线程暂停执行,并释放它所持有的锁,从而允许其他线程获得该锁并继续执行。调用wait()方法的线程将进入等待状态,直到另一个线程调用相同对象的notify()方法为止。一旦调用notify()方法,等待线程将被唤醒,并可以继续执行。
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
Thread thread1 = new Thread(() -> {
synchronized (lock) {
System.out.println("Thread 1 is waiting...");
try {
lock.wait(); // 等待另一个线程调用notify()方法
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 1 is resumed...");
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock) {
System.out.println("Thread 2 is running...");
lock.notify(); // 唤醒等待线程
}
});
thread1.start();
Thread.sleep(1000); // 等待1秒钟以确保线程1已经开始等待
thread2.start();
}
6.线程池
线程池就是一个可复用线程的管理技术,它可以优化线程的创建和销毁,并控制线程池的数量和生命周期。
使用线程池可以提高程序性能,减少系统开销。
线程池接口:ExecutorService
得到线程池对象
方式一、使用ExecutorService实现类ThreadPoolExecutor创建
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize: 线程池的基本大小,即在没有任务执行时线程池中的线程数。
maximumPoolSize: 线程池中允许的最大线程数。
keepAliveTime: 线程池中线程空闲后的存活时间
unit: 最大存活时间单位
workQueue : 用于保存等待执行的任务的阻塞队列。
threadFactory: 创建线程的工厂
handler : 当线程池中的线程已满,且等待队列也已满时的饱和策略。
Runnable线程 执行 execute()
import java.util.concurrent.*;
public class Main {
public static void main(String[] args) throws InterruptedException {
int corePoolSize = 3; //3个核心线程数量
int maximumPoolSize =5; //5个最大线程数量
long keepAliveTime =60l; //线程空闲后60销毁
TimeUnit unit = TimeUnit.SECONDS; //秒
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(5); //阻塞队列 等待执行的任务队列
ThreadFactory threadFactory = Executors.defaultThreadFactory(); //默认的线程工厂
ThreadPoolExecutor.AbortPolicy abortPolicy = new ThreadPoolExecutor.AbortPolicy(); //饱和策略 ,线程池已满,且等待队列已满
ExecutorService executor = new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,threadFactory,abortPolicy);
for (int i = 0;i<9;i++){
executor.execute(new myThread(i)); //每次执行多少是不固定的
}
executor.shutdown(); //关闭线程池
}
}
class myThread implements Runnable{
private int task;
public myThread(int task){
this.task = task;
}
@Override
public void run(){
try {
System.out.println(Thread.currentThread().getName()+"开始执行线程"+this.task);
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// System.out.println(Thread.currentThread().getName()+"执行完毕"+this.task);
}
}
pool-1-thread-1开始执行线程0
pool-1-thread-3开始执行线程2
pool-1-thread-2开始执行线程1
pool-1-thread-4开始执行线程8
pool-1-thread-3开始执行线程3
pool-1-thread-2开始执行线程4
pool-1-thread-1开始执行线程5
pool-1-thread-4开始执行线程6
pool-1-thread-2开始执行线程7
线程池中同时执行的线程数量不是固定的,而是根据线程池中线程的数量和任务队列中任务数量的动态变化而调整的。
当线程池中的线程数较少,任务队列中的任务比较多时,线程池会尽可能地创建更多的线程来处理任务,以提高处理效率。这时线程池就会一次执行更多的线程,比如一次执行5个线程。
当线程池中的线程数较多,任务队列中的任务比较少时,线程池就会逐渐减少线程的数量,以节省系统资源。这时线程池就会一次执行较少的线程,比如一次执行3个线程。
新任务拒绝策略
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。是默认的策略。
ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。这是不推荐的做法。
ThreadPoolExecutor.DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中。
ThreadPoolExecutor.CallerRunsPolicy:由主线程负责调用任务的run()方法,从而绕过线程池直接执行。
将上面方法 策略改为CallerRunsPolicy 则可以执行超过10个的任务,比如 11结果如下:
pool-1-thread-1开始执行线程1
pool-1-thread-3开始执行线程1
pool-1-thread-2开始执行线程1
pool-1-thread-4开始执行线程1
main开始执行线程1
pool-1-thread-5开始执行线程1
pool-1-thread-4开始执行线程1
pool-1-thread-5开始执行线程1
pool-1-thread-3开始执行线程1
pool-1-thread-2开始执行线程1
pool-1-thread-1开始执行线程1
Callable线程执行 带返回值 submit()
public static void main(String[] args) throws InterruptedException {
int corePoolSize = 3;
int maximumPoolSize =5;
long keepAliveTime =60l;
TimeUnit unit = TimeUnit.SECONDS;
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(5);
ThreadFactory threadFactory = Executors.defaultThreadFactory();
ThreadPoolExecutor.CallerRunsPolicy abortPolicy = new ThreadPoolExecutor.CallerRunsPolicy(); //饱和策略 CallerRunsPolicy 放弃线程池 主线程执行
ExecutorService executor = new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,threadFactory,abortPolicy);
Future<Integer> submit1 = executor.submit(new MyCallable(1));
Future<Integer> submit2 = executor.submit(new MyCallable(1));
Future<Integer> submit3 = executor.submit(new MyCallable(1));
try{
System.out.println(submit1.get());
System.out.println(submit2.get());
System.out.println(submit3.get());
}catch (Exception e){
e.printStackTrace();
}
executor.shutdown(); //关闭线程池
}
private static class MyCallable implements Callable<Integer>{
private int task;
public MyCallable(int task){
this.task = task;
}
@Override
public Integer call(){
try {
System.out.println(Thread.currentThread().getName()+"开始执行线程"+this.task);
Thread.sleep(5000);
return 1;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
方式二、使用Executors 工具类
Executors 用于创建和管理线程池的工具类。它提供了一些静态方法,可以创建不同类型的线程池。
newFixedThreadPool(int nThreads):创建一个固定大小的线程池,其中线程数固定为nThreads。
newCachedThreadPool():创建一个缓存线程池,其中线程数根据需要自动调整。
newSingleThreadExecutor():创建一个只有一个线程的线程池,用于顺序执行一系列任务。
newScheduledThreadPool(int corePoolSize):创建一个定时执行任务的线程池,其中线程数固定为corePoolSize。
ExecutorService executor = Executors.newFixedThreadPool(3);
返回ExecutorService对象。
大型并发系统中使用Executors创建线程池不注意可能出现意外,允许请求的任务队列长度是Integer.MAX_VALUE,创建的线程数量最大上限是Integer.MAX_VALUE。
7.定时器
方式一: Timer
timer是单线程,处理多个任务按照顺序执行,存在延时与设置定时器的时间有出入。
可能因为某个任务的异常使Timer死掉,影响后续任务执行。
Timer timer = new Timer();
//参数一:定时任务
//参数二:延时
//参数三:周期时间
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("timer定时器");
}
},1000,1000);
启动后 隔1秒开始执行 每一秒执行一次
方式二: ScheduledExecutorService
jdk1.5引入的,解决了TImer的缺陷,ScheduledExecutorService内部是线程池。
基于线程池,某个任务的执行情况不会影响其他定时任务。
public static void main(String[] args) throws InterruptedException {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);
executor.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
int i = 1/0;
System.out.println("执行");
}
},1l,1l,TimeUnit.SECONDS);
//不影响后续任务
executor.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println("执行");
}
},1l,1l,TimeUnit.SECONDS);
}
8.并发和并行
并发:是指在一段时间内同时执行多个任务,这些任务之间可能会相互干扰或者相互协作。例如,一个多线程程序中,多个线程可能在同一时刻都在执行不同的任务,这些任务之间可能会产生竞争或者协作。
并行:是指同时执行多个任务,这些任务之间相互独立,不会相互干扰或者协作。例如,一个计算机集群中的多个计算节点可以同时处理不同的任务,这些任务之间不会相互干扰或者协作。
比如洗衣服, 并发就是你把所有衣服放在一个洗衣机里面洗,可能会缠绕一起。
而并行就是你把衣服分好类,分给几个洗衣机同时处理,都在同时洗衣服并不会互相干扰。
9.线程的生命周期
线程的生命周期可以分为5个阶段,分别是:
新建(New):当一个Thread对象被创建后,它就处于新建状态。此时它还没有开始运行,也没有分配系统资源(如CPU、内存等)。
就绪(Runnable):当新建的线程调用了start()方法后,它就进入就绪状态。此时它已经分配了系统资源,等待系统调度它的CPU时间片来运行。多个就绪状态的线程将竞争CPU时间片,由系统决定选择哪个线程来运行。
运行(Running):当就绪状态的线程被系统调度到CPU上执行时,它就进入了运行状态。此时线程开始执行它的run()方法中的代码。
阻塞(Blocked):线程在执行过程中,可能因为等待某个事件的发生(如输入/输出操作、锁等)而被阻塞。此时它将放弃CPU的控制权,进入阻塞状态。当被等待的事件发生后,线程会重新进入就绪状态,等待系统重新调度。
终止(Terminated):线程运行完成或者发生了异常而导致线程终止,此时它进入终止状态。线程一旦进入了终止状态,它就不能再被调度执行。线程终止后,它占用的系统资源(如内存、CPU时间等)将被释放。
总之,线程的生命周期是从新建到终止,其中就绪、运行和阻塞状态是线程在运行过程中的三种基本状态。