java多线程学习笔记

什么是多线程?

进程就是一个运行的软件,线程就相当于是一个软件中的多个功能,他们相互独立,但是又可以同时运行

image-20240420095719345

在进行一些耗时操作的时候,cpu会切换到其他方法上进行操作,这样可以节约运行时间,这样的操作就是多线程的优点

并发,并行

并发:在同一时刻,有多个操作在cpu上交替进行

并行:在同一时刻,有多个操作在cpu上同时进行

并发和并行可以同时存在,假设线程数超出计算机可执行的线程数,那么就会有多个线程同时运行,并且多个线程同时切换

多线程的实现方式

继承Thread类的方式进行实现

 package Thread;
 ​
 public class MyThread extends Thread{
     //继承Thread,重写run方法
     @Override
     public void run() {
         for(int i = 0 ; i < 100 ; i++){
             System.out.println(getName()+"Hello World");
         }
     }
 }

 package Thread;
 ​
 public class Main {
     public static void main(String[] args){
         MyThread myThread1 = new MyThread();
         MyThread myThread2 = new MyThread();
         //创建实现类,start开始线程
         myThread1.setName("线程1:");
         myThread2.setName("线程2:");
 ​
         myThread1.start();
         myThread2.start();
     }
 }

实现Runnable接口的方式实现

 package Thread1;
 ​
 public class MyRun implements Runnable{
     @Override
     public void run() {
         for(int i = 0 ; i < 100 ;i++){
             System.out.println(Thread.currentThread().getName()+"Hello World");
             //Thread.currentThread()就是获取当前执行线程的名字
         }
     }
 }


 package Thread1;
 ​
 public class Main {
     public static void main(String[] args){
         //表示多线程要执行的任务
         MyRun myRun = new MyRun();
         //创建线程的对象
         Thread thread1 = new Thread(myRun);
         Thread thread2 = new Thread(myRun);
         //给线程命名
         thread1.setName("线程1:");
         thread2.setName("线程2:");
         //让线程开始
         thread1.start();
         thread2.start();
 ​
     }
 }

利用Callable接口和Future接口实现

 
package Thread2;
 ​
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.FutureTask;
 ​
 public class Main {
     public static void main(String[] args) throws ExecutionException, InterruptedException {
          //创建MyCallable的对象,表示多线程要执行的任务
         MyCallable myCallable = new MyCallable();
        //创建FutureTask的对象,管理多线程运行的结果
         FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
          //创建线程对象
         Thread thread = new Thread(futureTask);
         //启动线程
         thread.start();
         //获取线程执行的结果
         Integer result = futureTask.get();
         System.out.println(result);
     }
 }


 package Thread2;
 ​
 import java.util.concurrent.Callable;
 ​
 public class MyCallable implements Callable {
     @Override
     public Integer call() throws Exception {
         int sum = 0;
         for(int i = 0 ; i < 100 ; i++){
             sum += i;
         }
         return sum;
     }
     //这个返回值的类型是可以修改的
 }

image-20240420110113512

多线程常用方法

image-20240420110340574

守护线程又叫备胎线程!

  • 细节1:

setName没有操作时,线程也会有原始的名字就是Thread-X

X就是序号,从零开始,然后创建一个对象序号就+1

  • 细节2:

构造方法无法被继承虽然Thread类里面有有参构造的方法,但是我们创建的子类,并不会继承他的方法,所以我们要想使用就要自己书写构造方法

 package Thread;
 ​
 public class Main {
     public static void main(String[] args){
         MyThread myThread1 = new MyThread("线程1:");
         MyThread myThread2 = new MyThread("线程2:");
         myThread1.start();
         myThread2.start();
     }
 }


 package Thread;
 ​
 public class MyThread extends Thread{
     public MyThread() {
     }
 ​
     public MyThread(String name) {
         super(name);
     }
 ​
     @Override
     public void run() {
         for(int i = 0 ; i < 100 ; i++){
             System.out.println(getName()+"Hello World");
         }
     }
 }

 package Thread;
 ​
 public class Main {
     public static void main(String[] args) throws InterruptedException {
         MyThread myThread1 = new MyThread("线程1:");
         MyThread myThread2 = new MyThread("线程2:");
         myThread1.start();
         myThread2.start();
         //那条线程执行到这个方法,获取的就是对应线程的对象
         Thread thread = Thread.currentThread();
         //获取线程的名字
         String name = thread.getName();
         //设置睡眠时间,其实就是设置停止运行时间
         System.out.println(name);
         Thread.sleep(5000);
         System.out.println(name);
     }
 }


 package Thread;
 ​
 public class MyThread extends Thread{
     public MyThread() {
     }
 ​
     public MyThread(String name) {
         super(name);
     }
 ​
     @Override
     public void run() {
         for(int i = 0 ; i < 100 ; i++){
             System.out.println(getName()+"Hello World");
         }
     }
 }

线程的优先级

线程优先级分为抢占式调度还有非抢占式调度,抢占式调度可以设置优先级,最小是1,最大是10,优先级越高,抢占的概率越大,非抢占式调度就是轮流来实现

没有设置的时候默认值就是5

 MyThread myThread1 = new MyThread("线程1:");
 MyThread myThread2 = new MyThread("线程2:");
 myThread1.setPriority(1);
 myThread2.setPriority(10);
 myThread1.start();
 myThread2.start();

守护线程

守护线程就是当非守护线程结束后,守护线程就会陆续结束,假设守护线程要输出100次,但是非守护线程结束后,守护线程就会提前结束

 MyThread myThread1 = new MyThread("线程1:");
 MyThread myThread2 = new MyThread("线程2:");
 myThread2.setDaemon(true);
 myThread1.start();
 myThread2.start();

礼让线程

 
package Thread;
 ​
 public class MyThread extends Thread{
     public MyThread() {
     }
 ​
     public MyThread(String name) {
         super(name);
     }
 ​
     @Override
     public void run() {
         for(int i = 0 ; i < 100 ; i++){
             System.out.println(getName()+"Hello World");
             //当这个输出语句执行完之后,原本应该是继续执行,但是现在让出cpu,重新进行争夺
             Thread.yield();
         }
     }
 }

插入线程:就是让某一个线程插入,当这个线程执行完之后,再去执行下一个线程

线程的生命周期

image-20240420115318731

线程的安全问题

假设多个线程同时执行一个任务,就会出现数据重复或者超出范围的情况

image-20240420172528151

因为多个线程同时抢同一个任务,当一个线程抢到任务时,其他线程也会抢到,因此tacket++后就会出现三个线程打印相同tacket的操作,当tacket==99的时候也会出现这样的效果,因此就会出现超出范围的情况

同步代码块

为了解决线程安全,会把任务锁起来,然后一个线程执行完之后,下一个线程才会去执行

image-20240420180425580

 public class Main {
     public static void main(String[] args) {
         MyThread myThread1 = new MyThread();
         MyThread myThread2 = new MyThread();
         MyThread myThread3 = new MyThread();
         myThread1.setName("第一个窗口");
         myThread2.setName("第二个窗口");
         myThread3.setName("第三个窗口");
         myThread1.start();
         myThread2.start();
         myThread3.start();
     }
 }

 
package Thread3;
 ​
 public class MyThread extends Thread{
     static int ticket = 0 ;
     @Override
     public void run() {
         while(true){
             //细节1:synchronized这个要写在循环的里面,因为假如一个线程进来后,锁就会关闭,其他的线程就无法进入,只有循环执行完才会开锁,那么任务就会被一个线程执行完
             synchronized (MyThread.class){
                 //细节2:锁只能有一把,如果是多个锁,那么就相当于不存在,假设两个线程两把锁,那么就相当于每个锁对于他的线程都是开着的
                 if(ticket < 100){
                     try {
                         Thread.sleep(300);
                     } catch (InterruptedException e) {
                         throw new RuntimeException(e);
                     }
                     System.out.println(getName() +"卖出第" + ticket + "张票");
                     ticket++;
                 }else{
                     break;
                 }
             }
         }
     }
 }

同步方法

image-20240420182219992

 package Thread3;
 ​
 public class MyThread extends Thread{
     static int ticket = 0 ;
 ​
     @Override
     public void run() {
         while(true){
             synchronized (MyThread.class){
                 if (extracted()) break;
             }
         }
     }
 ​
     private boolean extracted() {
         if(ticket < 100){
             try {
                 Thread.sleep(300);
             } catch (InterruptedException e) {
                 throw new RuntimeException(e);
             }
             System.out.println(getName() +"卖出第" + ticket + "张票");
             ticket++;
         }else{
             return true;
         }
         return false;
     }
 }

Alt+Ctrl+m快捷键提取方法

Lock锁

image-20240420183057073

Lock提供了更多的方法,可以人为的控制锁的开关

 package Thread3;
 ​
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 ​
 public class MyThread extends Thread{
     static int ticket = 0 ;
     static Lock lock = new ReentrantLock();
     @Override
     public void run() {
         while(true){
             lock.lock();
             try {
                 if(ticket < 100){
                     try {
                         Thread.sleep(300);
                     } catch (InterruptedException e) {
                             throw new RuntimeException(e);
                     }
                     System.out.println(getName() +"卖出第" + ticket + "张票");
                         ticket++;
                 }else{
                     break;
                 }
             } catch (RuntimeException e) {
                 throw new RuntimeException(e);
             } finally {
                 lock.unlock();
             }
         }
     }
 }

ctrl+alt+t生成try catch方法

死锁

image-20240420184334061

就是AB锁都被占用,等对方释放,但是都没有释放,所以就会产生死锁

写的代码不要把锁嵌套起来

多线程等待唤醒机制

image-20240420202328683

 package Thread4;
 ​
 public class Main {
     public static void main(String[] args) {
         Food f = new Food();
         Cook c = new Cook();
         f.start();
         c.start();
     }
 }


 package Thread4;
 ​
 public class Cook extends Thread{
     @Override
     public void run() {
         while(true){
             synchronized (Desk.lock){
                 if(Desk.count == 0){
                     break;
                 }else{
                     if(Desk.number == 1){
                         try {
                             Desk.lock.wait();
                         } catch (InterruptedException e) {
                             throw new RuntimeException(e);
                         }
                     }else{
                         Desk.number = 1;
                         System.out.println("qqq");
                         Desk.lock.notifyAll();
                     }
                 }
             }
         }
     }
 }


 package Thread4;
 ​
 public class Desk extends Thread{
     public static int number = 0;
     public static int count = 10;
 ​
     public static Object lock = new Object();
 ​
 }


 package Thread4;
 ​
 public class Food extends Thread{
     @Override
     public void run() {
         while(true){
             synchronized (Desk.lock){
                 if(Desk.count == 0){
                     break;
                 }else{
                     if(Desk.number == 0){
                         try {
                             Desk.lock.wait();
                         } catch (InterruptedException e) {
                             throw new RuntimeException(e);
                         }
                     }else{
                         Desk.count--;
                         System.out.println(Desk.count);
                         Desk.lock.notifyAll();
                         Desk.number = 0;
                     }
                 }
             }
         }
     }
 }

image-20240420213527285

阻塞队列实现多线程唤醒

 package Thread5;
 ​
 import java.util.concurrent.ArrayBlockingQueue;
 ​
 public class Cook extends Thread{
     ArrayBlockingQueue<String> queue;
 ​
     public Cook(ArrayBlockingQueue<String> queue){
         this.queue = queue;
     }
 ​
     @Override
     public void run() {
         while(true){
             try {
                 //放入阻塞队列中
                 queue.put("面条");
                 System.out.println("厨师放入了一份面条");
             } catch (InterruptedException e) {
                 throw new RuntimeException(e);
             }
         }
     }
 }


 package Thread5;
 ​
 import java.util.concurrent.ArrayBlockingQueue;
 ​
 public class Food extends Thread{
     ArrayBlockingQueue<String> queue;
 ​
     public Food(ArrayBlockingQueue<String> queue){
         this.queue = queue;
     }
 ​
     @Override
     public void run() {
         while(true){
             String food = null;
             try {
                 //从阻塞队列中获取
                 food = queue.take();
             } catch (InterruptedException e) {
                 throw new RuntimeException(e);
             }
             System.out.println(food);
         }
     }
 }


 package Thread5;
 ​
 import Thread4.Cook;
 import Thread4.Food;
 ​
 import java.util.concurrent.ArrayBlockingQueue;
 ​
 public class Main {
     public static void main(String[] args) {
         //创建阻塞队列
         ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
         //创建对象
         Cook cook = new Cook();
         Food food = new Food();
         //开启线程
         cook.start();
         food.start();
     }
 }

多线程的六种状态

image-20240420220311849

image-20240420220600592

当线程抢夺到cpu的时候就会将控制权交出去

案例1

 package Thread6;
 ​
 public class Main {
     public static void main(String[] args) {
         MyThread myThread1 = new MyThread();
         MyThread myThread2 = new MyThread();
         MyThread myThread3 = new MyThread();
         MyThread myThread4 = new MyThread();
         MyThread myThread5 = new MyThread();
 ​
         myThread1.setName("a");
         myThread2.setName("b");
         myThread3.setName("c");
         myThread4.setName("d");
         myThread5.setName("e");
 ​
         myThread1.start();
         myThread2.start();
         myThread3.start();
         myThread4.start();
         myThread5.start();
 ​
     }
 }


 package Thread6;
 ​
 import java.util.Random;
 ​
 public class MyThread extends Thread{
     //红包的数量默认是三个
     public static int count = 3;
     //红包默认金额是100元
     public static int money = 100;
     //默认最小金额是1元
     public static final int MIN = 1;
     @Override
     public void run() {
        synchronized (MyThread.class) {
            if (count == 0) {
                //判断还有没有红包了
                System.out.println(getName() + "对不起,您没有抢到红包");
            } else {
                int price = 0;
                if (count == 1) {
                    //话有一个的时候有多少抢多少
                    price = money;
                } else {
                    //随机抢到红包
                    Random r = new Random();
                    int bounds = count - 1;
                    price = r.nextInt(money - bounds);
                    if (price < 0) {
                        price = MIN;
                    }
                }
                //每次抢完都减少总金额
                money = money - price;
                //减少总数量
                count--;
                //输出qiang'da的金额
                System.out.println(getName() + "抢到了" + price);
            }
        }
     }
 }

案例2

 package Thread7;
 ​
 import java.util.ArrayList;
 import java.util.Collections;
 ​
 public class Main {
     public static void main(String[] args) {
         ArrayList<Integer> arrayList = new ArrayList<>();
         Collections.addAll(arrayList,5,10,20,30,40,50,60,70,80,90,100);
         MyThread myThread1 = new MyThread(arrayList);
         MyThread myThread2 = new MyThread(arrayList);
         myThread1.setName("aaa");
         myThread2.setName("bbb");
         myThread1.start();
         myThread2.start();
     }
 }

Collections.addAll(arrayList,5,10,20,30,40,50,60,70,80,90,100)方法是将指定的元素添加到ArrayList集合中。但是,添加的顺序并不是按照参数的顺序添加的

ArrayList是一个动态数组,它内部使用数组来存储元素。当添加元素时,ArrayList会根据当前数组的大小和容量来确定新元素的位置。如果当前数组的容量不足以容纳新元素,ArrayList会进行扩容操作,创建一个更大的数组,并将原有元素复制到新数组中。

在扩容和复制元素的过程中,ArrayList会重新排列元素的顺序,以保证元素在数组中的位置是连续的。因此,添加进去的数据在ArrayList中的顺序可能与参数的顺序不一致。

 package Thread7;
 ​
 import java.util.ArrayList;
 import java.util.Collections;
 ​
 public class MyThread extends Thread{
     ArrayList<Integer> arrayList = new ArrayList<>();
 ​
     public MyThread(ArrayList<Integer> arrayList) {
         this.arrayList = arrayList;
     }
 ​
     @Override
     public void run() {
         while(true){
             synchronized (MyThread.class){
                 //如果集合的内容为空,那么就结束进程
                 if(arrayList.size() == 0){
                     break;
                 }else{
                     Collections.shuffle(arrayList);
                     Integer remove = arrayList.remove(0);
                     System.out.println(getName()+"抽到了"+remove+"元");
                 }
             }
             try {
                 Thread.sleep(10);
             } catch (InterruptedException e) {
                 throw new RuntimeException(e);
             }
         }
     }
 }

线程栈

 package Thread7;
 ​
 import java.util.ArrayList;
 import java.util.Collections;
 ​
 public class MyThread extends Thread{
     ArrayList<Integer> arrayList;
 ​
 ​
     public MyThread(ArrayList<Integer> arrayList) {
         this.arrayList = arrayList;
     }
 ​
     @Override
     public void run() {
         //在main里面创建两个Thread对象的时候,里面的集合就会被创建作为一个部分,同时创建共用集合,生成两个栈,栈里面运行的就是Thread里面的代码,看谁抢到就会运行谁的线程
         ArrayList<Integer> list = new ArrayList<>();
         while(true){
             synchronized (MyThread.class){
                 if(arrayList.isEmpty()){
                     System.out.println(getName() + list);
                     break;
                 }else{
                     Collections.shuffle(arrayList);
                     Integer remove = arrayList.remove(0);
                     list.add(remove);
                 }
             }
             try {
                 Thread.sleep(10);
             } catch (InterruptedException e) {
                 throw new RuntimeException(e);
             }
         }
     }
 }

案例三

 package Thread7;
 ​
 ​
 ​
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.FutureTask;
 ​
 public class Main {
     public static void main(String[] args) throws ExecutionException, InterruptedException {
         ArrayList<Integer> arrayList = new ArrayList<>();
         Collections.addAll(arrayList,5,10,20,30,40,50,60,70,80,90,100);
         //1.创建多线程运行要使用的参数对象
         MyCallable1 myCallable = new MyCallable1(arrayList);
         //2.创建多线程运行要使用的返回值管理对象
         FutureTask<Integer> futureTask1 = new FutureTask<>(myCallable);
         FutureTask<Integer> futureTask2 = new FutureTask<>(myCallable);
         //3.创建多线程对象
         Thread thread1 = new Thread(futureTask1);
         Thread thread2 = new Thread(futureTask2);
         //4.修改多线程名字
         thread1.setName("多线程1");
         thread2.setName("多线程2");
         //5.开启线程
         thread1.start();
         thread2.start();
         //6.获取最大值
         Integer i = futureTask1.get();
         Integer j = futureTask2.get();
         if(i > j){
             System.out.println(i);
         }else {
             System.out.println(j);
         }
     }
 }


 package Thread7;
 ​
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.concurrent.Callable;
 ​
 public class MyCallable1 implements Callable<Integer> {
     ArrayList<Integer> arrayList;
 ​
 ​
     public MyCallable1(ArrayList<Integer> arrayList) {
         this.arrayList = arrayList;
     }
 ​
 ​
 ​
     @Override
     public Integer call() throws Exception {
         ArrayList<Integer> list = new ArrayList<>();
         while(true){
             synchronized (MyCallable1.class){
                 if(arrayList.isEmpty()){
                     System.out.println(Thread.currentThread().getName() + list);
                     break;
                 }else{
                     Collections.shuffle(arrayList);
                     Integer remove = arrayList.remove(0);
                     list.add(remove);
                 }
             }
                 Thread.sleep(10);
         }
         if(list.isEmpty()){
             //若当前线程的集合为空,那么就直接返回null
             return null;
         }else{
             //集合不为空的时候,就判断返回最大值
             return Collections.max(list);
         }
     }
 }

线程池

1.创建一个空的线程池

2.当提交任务的时候,就会创建线程,并且完成任务之后,线程会重新放回线程池,下次用的时候可以重新取出

3.当线程池内没有线程,并且无法创建新的线程时,任务就会排队等待

  • 创建线程池

 ExecutorService exe = Executors.newCachedThreadPool();
  • 提交任务

 exe.submit(new MyRunnable());
 exe.submit(new MyRunnable());
 exe.submit(new MyRunnable());
  • 当所有任务都执行完毕之后,就会关闭线程池

 exe.shutdown();


 package ThreadPool;
 ​
 public class MyRunnable implements Runnable{
     @Override
     public void run() {
         for (int i = 1; i <= 100; i++) {
             System.out.println(Thread.currentThread().getName() + "--->" + i);
         }
     }
 }

这个方法是默认数量的线程池,然后输出语句里面不加“—>”这个符号时,就会从11开始打印,在你的代码中,可能出现线程池中的某个线程先于其他线程执行run方法。假设第一个线程执行run方法时,它会打印出"Thread-1 1",然后其他线程开始执行run方法时,它们会继续打印从1到100的数字

所以可能是10条线程在抢夺cpu但是抢完之后i已经增加到10了,所以就会从11开始打印

这个方法创建的线程池,他的数量是已经规定好的,假设你提交了五次,但是仍是三条线程

 ExecutorService exe = Executors.newFixedThreadPool(3);

自定义线程池

image-20240422194029398

  • 当任务数量小于核心线程池时,就会创建任务数量的线程

image-20240422194149553

  • 当任务数量大于核心线程数量,但是小于核心线程+临时线程的总数时,就会创建核心线程数量的线程,然后其他任务排队

image-20240422194358987

  • 当任务数量大于核心线程+临时线程总数的时候,就会创建临时线程,临时线程会处理多出来的任务,所以任务执行的顺序是不固定的,上面的从11开始的问题就有解释了

image-20240422194614513

  • 当任务数量大于三者相加之和的时候。剩余的任务就被拒绝了

image-20240422194720726

  • 默认的方法是第一种,其他三种知道就行

image-20240422195546120

 package ThreadPool1;
 ​
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 ​
 public class ThreadPool {
     public static void main(String[] args) {
         ThreadPoolExecutor executor = new ThreadPoolExecutor(
                 3,//核心任务数量,不能小于0
                 6,//最大线程数,大于等于核心任务数
                 60,//空闲线程最大存活时间,不能小于0
                 TimeUnit.SECONDS,//单位
                 new ArrayBlockingQueue<>(3),//任务队列
                 Executors.defaultThreadFactory(),//创建线程工厂
                 new ThreadPoolExecutor.AbortPolicy()//创建拒绝策略
         );
     }
 }

  • 17
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

杰哥的狗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值