首页 > 编程语言 >Java EE(多线程)

Java EE(多线程)

时间:2024-09-18 14:52:40浏览次数:3  
标签:Java Thread EE start run 线程 new 多线程 方法

1.认识线程(Thread)

    1️⃣每个线程都是一个独立的执行流,都可以单独参与cpu的调度.

    2️⃣每个进程里至少包含一个线程时(及为主线程)或多个线程,同一个进程创建多个线程时,线程会共享同一份资源(内存➕     文件描述符.)

         ⚠️:多个进程之间并不会共享同一份资源

    3️⃣ 线程是系统调度执行的基本单位

          进程是系统分配资源的基本单位

   4️⃣(同一个进程)多线程时,每个线程执行先后的顺序并不确定  因为在操作系统内核中有个“调度器的模块”,实现方式时‘随机调度’,‘抢占式调度’

2.线程与进程的优劣势.

   1️⃣.创建线程比创建进程更快

   2️⃣ .销毁线程比销毁进程更快

   3️⃣.调度线程比调度进程更快

3.创建线程(Thread)

   方法一:继承Thread类

   1️⃣继承Thread来创建一个线程类

       重写run方法,run表示线程的入口,而mian表示主线程入口

//) 继承 Thread 来创建一个线程类.
class MyThread extends Thread{
    @Override // @Override表示机器会在执行时检查一遍是否重写run方法
    public void run() {
       
            System.out.println("正在运行,run方法");
    }
}

2️⃣ 创建MyThread的实例

//调用 start 方法启动线程  才是真正的调用系统的API
  t.start();

  根据MyThread类,来创建的实例(才是真正的线程)

​
Thread t = new MyThread();

​

3️⃣调用MyThread父类Thread中成员方法 start(作用启动线程)

//调用 start 方法启动线程  才是真正的调用系统的API
       t.start();
​

4️⃣ sleep()方法是一个静态方法,属于Thread类,用于让当前正在执行的线程暂停执行一段时间

   在重写run方法中创建sleep()方法时会报错    可以选try/catch解决此问题

 在main中使用sleep方法时 也会出现报错   但是这里有两种解决方式try/catchthrows InterruptedException

引出一个问题为什么run只有一种解决方式呢?

    解答:因为父类Thread中没有throws这个异常,  如果加上throws,则修改了方法签名,够不成重写了  

              子类run是重写的,就不可以使throws异常

//括号里面数字(毫秒单位)代表休眠10000ms==1s
Thread.sleep(10000);

 整体代码:

//) 继承 Thread 来创建一个线程类.
class MyThread extends Thread{
    @Override // @Override表示机器会在执行时检查一遍是否重写run方法
    public void run() {
        while(true) {
            try {
                //Thread类中sleep方法表示
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("正在运行,run方法");
        }
    }
}
public class ThreadDome1 {
    public static void main(String[] args) throws InterruptedException {
        //2.根据Mythread类,来创建实列(才是真正的线程)
         //根据
        Thread t =new MyThread();
        Thread.sleep(1000);
        //3) 调用 start 方法启动线程  才是真正的调用系统的API
       t.start();
       while(true){
           try {
               Thread.sleep(1000);
           } catch (InterruptedException e) {
               throw new RuntimeException(e);
           }
           System.out.println("正在运行,main方法");
       }
    }
}

方法二:实现Runnable接口(可执行的)

   需要搭配Thread类,才能在系统中真正创建出线程

//实现Runnable接口
class MyRunnable implements Runnable{
    @Override
    public void run() {
        while(true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("正在运行,Thread/run");
        }
    }
}
public class ThreadDome2 {
    public static void main(String[] args) throws InterruptedException {
        //2) 创建 Thread 类实例, 调用 Thread 的构造方法时将 Runnable 对象作为 target 参数.
        //Runnable  runnable= new MyRunnable();
        Thread t = new Thread(new MyRunnable());
        //3.调用Thread类中的start方法
        //表示线程开始
        System.out.println("要开始创建线程了");
        t.start();
        while (true) {
           Thread.sleep(1000);
            System.out.println("正在运行,main");
        }
    }
}

方法三:实现匿名内部类,创建Thread的子类对象

    匿名内部类表示没有名字,不能重复使用,用一次就扔了

   其中代码中变量 t并非指向的是Thread,而是Thread的子类,具体是那个子类并不知道,因为匿名隐藏了

public class ThreadDome3 {
    //1.实现匿名内部类,  创建Thread的子类对象
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(){
            @Override
            public void run() {
                System.out.println("使用匿名类创建 Thread 子类对象");
            }

        };
        //启动线程
       t.start();
       while(true){
           Thread.sleep(1000);
           System.out.println("运行maim");
       }
    }
}

方法四:实现匿名内部类,创建Runnable字类对象

public class ThreadDome4 {
    public static  boolean fla = true;
    public static void main(String[] args) throws InterruptedException {
        //实现匿名内部类,创建Runnable子类对象
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                //条件fla为真运行,
                while (fla){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println("正在运行run");
                }
            }
        });
        //启动线程
        t.start();
        for (int i = 0; i < 5; i++) {

            Thread.sleep(1000);
            System.out.println("正在运行main方法");
        }
        //主线程for循环执行完,fla=false,此时在执行匿名内部类run时循环条件为假跳出循环
        fla=false;
    }
}

方法五:lambda表达式 (推荐/常用)

 这个写法相当于Runnable重写run方法, lambda代替了Runnable位置

lambda表达式,其实本质来讲,就是⼀个匿名函数。因此在写lambda表达式的时候,不需要关心方法名是什么。

实际上,我们在写lambda表达式的时候,也不需要关心返回值类型。

我们在写lambda表达式的时候,只需要关注两部分内容即可:参数列表和方法体

初始代码: 

public class ThreadDome5 {
    public static void main(String[] args) {
        //lambda表达式这个写法代替了Runnable重新run方法.
        //
        Thread t = new Thread(() ->{
            while(true){
                System.out.println("正在运行,Thread");
                try {
                    Thread.sleep(1001);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

            }
        });
        //启动新创建的线程
        t.start();
        System.out.println("hello main");
    }
}

使用多线程可以提高运行的效率 

4.Thread的常用构造方法 

Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这是我的名字");
Thread t4 = new Thread(new MyRunnable(), "这是我的名字");

 5.Thread的常用属性

 自己创建的线程ID默认按照Thread-0,Thread-1;

  isDaemon属性作用:

守护线程也称为是否是后台线程

前台线程只要正在运行就会阻止进程结束,而后台线程并不会阻止 

咱们创建的线程,默认为前台线程,会阻止进程结束,即使main(主线程)已经执行完了,只要创建的前台线程没有结束,进程就不会结束

//在start之前设置,设置为true 成为后台线程,不设置为前台线程   (不可以在start之后设置)
//前台线程会阻止线程结束, 后台线程并不会
t.isDaemon(true);

is Alive属性

 在真正创建出线程之前为false,start()只有内核中创建出PCB之后 isAlive为true

在run线程结束后内核中的线程也就结束了(内核pcb释放)isAlive为false

6.启动线程 :

                1️⃣:start方法会调用到系统的API,到系统内核中创建线程

                2️⃣:run方法(只是描述当前线程具体执行什么内容)

       面试题:start和run的区别?                                

  • run 方法:定义了线程的执行行为,即线程启动后需要执行的具体操作。
  • start 方法:负责启动一个新的线程,并让这个新线程去执行 run 方法中定义的代码。
  • 关键区别在于 : run 方法本身并不会创建或启动任何新的线程,它只是一个普通的方法调用。 而 start 方法才是真正触发线程并发执行的关键。

 7.中断线程:

   (就是让run方法结束运行)

        目前常见的有以下两种方式:
                1. 通过共享的标记来进行沟通
                2. 调用 interrupt() 方法来通知

    方法一:自定义变量作为标志为run方法的结束条件

              在main方法中设置fla为true,来结束run循环,

 

 方法二:使用 Thread.interrupted() 或者 Thread.currentThread().isInterrupted() 代替自定
义标志位.(推荐

 

      // 终断t线程的睡眠(这种终断睡眠的方式依靠了java的异常处理机制。)
        t.interrupt();
//        t.stop(); //强行终止线程
        //缺点:容易损坏数据  线程没有保存的数据容易丢失

 

可以使用t.interrupt()方法来清除标志位  因为异常报错提前唤醒sleep

8.等待线程 :

        

 

join方法:让一个线程A,等待另一个线程B执行结束 后,线程A在执行

              在主线程执行t.join,就是让主线程等待t线程结束

9.获取当前线程的引用 

//获取当前线程的引用
Thread.currentThread()
//输出
System.out.println(Thread.currentThread().getName());


 

10.线程的状态 :

        1️⃣新建状态(NEW)Thread的对象已经有了,但还没有调用start方法

        2️⃣结束状态(terminated):Thread对象还在,内核中的线程已经结束了

        3️⃣就绪状态(Runnable):线程已经在cpu运行的,和等待cpu运行的线程

        4️⃣阻塞状态(TIMED_WAITING):可能产生于slee方法/join/用户输入等待 产生阻塞

                     等待阻塞(WAITING):产生于wait()方法,线程会释放占用资源,等待其他线程调用notify(唤醒单个线程)或isnotify(唤醒全部线程)。

                     同步阻塞(BLOCKED):产生于锁竞争导

                                

11.线程安全 :

 例子:

public class ThreadDome7 {
    //创建一个全局变量sum
    static int sum =0;
    public static void main(String[] args) throws InterruptedException {

        Thread t = new Thread(() ->{
            for (int i = 0; i < 50000; i++) {
                sum++;
            }
        });
        Thread t1 = new Thread(() ->{
            for (int i = 0; i < 50000; i++) {
                sum++;
            }
        });
        t.start();
        t1.start();
        t.join();
        t1.join();
        System.out.println(sum);

    }
}

 问题 要是单线程中执行绝对是正确的 

         但是在多线程中并发执行,因为多线程中线程的调度是随机调度的,此时逻辑就出现问题 这种情况就是bug

出现线程安全的原因:

                   1️⃣.操作系统中,线程调度是随机调度抢占式调度 (罪魁祸首)

                   2️⃣:两个线程,对同一个变量进行修改

                   3️⃣:内存可见性:当线程已经修改了变量的值,但其他线程并没看见而继续用之前的变量的值,导致出现线程安全问题

                             (可见性指, 一个线程对共享变量值的修改,能够及时地被其他线程看到

                   4️⃣:指令重排序问题

                   5️⃣:修改操作(不是原子的) 

                                原子性:指一个操作是不可分割的,不可中断的,不受其他线程影响

12.变量对线程安全的影响 

实例变量:在堆中。

静态变量:在方法区。

局部变量:在栈中。

    以上三大变量中:
        局部变量永远都不会存在线程安全问题。
        因为局部变量不共享。(一个线程一个栈。)
        局部变量在栈中。所以局部变量永远都不会共享。

     实例变量在堆中,堆只有1个。
    静态变量在方法区中,方法区只有1个。
    堆和方法区都是多线程共享的,所以可能存在线程安全问题

    局部变量+常量:不会有线程安全问题。
    成员变量:可能会有线程安全问题。
 

13.如何解决线程安全的问题 :

        synchronized关键字:  加锁—目的:把多个操作打包成一个操作具有原子性       

           进行加锁的时候,需要先准备好锁对象    (加锁和解锁都是针对锁对象进行的)

           当线程A对成员A进行加锁的操作时,线程B也想对成员A进行加锁  就会导致出现阻塞(BLOCKED),只有线程A执行完阻塞才结束,线程B才可以对成员A加锁

 

对Objeck变量进行加锁:  

public class ThreadDome7 {
    //随便创建一个Object变量
    static Object object =new Object();
  
    static int sum =0;
    public static void main(String[] args) throws InterruptedException {

       Thread t = new Thread(() ->{
            for (int i = 0; i < 50000; i++) {
           synchronized (object) {
               sum++;
           }

            }
        });
        Thread t1 = new Thread(() ->{
            for (int i = 0; i < 50000; i++) {
                synchronized (object) {
                    sum++;
                }
            }
        });
        t.start();
        t1.start();
        t.join();
        t1.join();
        //这里预期应该是100000
        System.out.println(sum);

    }
}

 synchronized中进入"{}"表示开始加锁,出"{}"表示加锁结束

synchronized的的特征:(可重入) 

 

 

还有的虽然没有加锁, 但是不涉及 "修改", 仍然是线程安全的:String 

14.死锁 

    1️⃣:加锁可以解决线程的安全问题,加锁使用不当时就会产生死锁 

    2️⃣:产生死锁的情况有以下情况:

        1.一个线程,一把锁:(如果锁不是可重入锁)同一个线程对这把锁锁两次,就会出现死锁

        2.二个线程,二把锁:这二个线程已经各锁了一把锁时,还想锁对方的锁,就会出现死锁

        3.N个线程,M锁;(哲学家就餐问题)

    3️⃣:产生死锁的必要条件:

        1.互斥使用:获得锁的过程是互斥的,一个线程拿到这把锁,另个一个线程也想拿到,就需要阻塞等待

        2.不可抢占一个线程拿到这把锁,除非该线程主动解锁,否则别的线程不能抢走

        3.请求保持一个线程拿到一把锁后,继续尝试获取下一把锁

        4.循环等待/环路等待:在发生死锁时,必然存在一个线程--资源的环形链

15.解决死锁问题

有许多中方法:

  • 资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件)
  • 只要有一个资源得不到分配,也不给这个进程分配其他的资源:(破坏请保持条件)
  • 可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件
  • 资源有序分配法(引入加锁顺序):系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件

     1️⃣: 产生死锁有四个必要条件, 我们只需破坏一个必要条件就好了

            最容易破坏的是条件四 我们只需指定加锁的顺序就好了

16.保证内存可见性:volatile关键字

​
​
import java.util.Scanner;
//没有加入volatile的代码
public class ThreadDome8 {
    static int fal=0;
    public static void main(String[] args) {
        Thread t =new Thread(() ->{
           while(fal==0){
           }
            System.out.println("循环结束");
        });
        Thread t1 =new Thread(() ->{
            Scanner scanner =new Scanner(System.in);
            System.out.println("请输入大于0得数:");
            fal= scanner.nextInt();
        });
        t.start();
        t1.start();
    }
}

​//执行效果t线程并没有获取的t1线程用户输入的值,没有跳出循环

​

t线程没有感知不到t1线程fal的变化 

1️⃣:volatile , 强制读写内存. 速度是慢了, 但是数据变的更准确了 

当我们引入volatile关键字时 给fal加入volatile   就可以跳出循环了

 

但是volatile 不保证原子性
volatile 和 synchronized 有着本质的区别. synchronized 能够保证原子性, volatile 保证的是内存可见
性.
 

synchronized 也能保证内存可见性
synchronized 既能保证原子性, 也能保证内存可见性. 

13.wait(等待) 和 notify(通知) 关键字 

        1️⃣wait需要配合synchronized来使用(前提是这俩的锁是同一个对象),否则报错

        2️⃣wait()方法是让当前线程等待的,即让线程释放了对共享对象的锁,不再继续向下执行。

        3️⃣wait(long timeout)方法可以指定一个超时时间,过了这个时间如果没有被notify()唤醒,

                则函数还是会返回。如果传递一个负数timeout会抛出IllegalArgumentException异常。

        4️⃣notify()方法会让调用了wait()系列方法的一个线程释放锁,并通知其它正在等待(调用了wait()方法)的线程得到锁。

        5️⃣notifyAll()方法会唤醒所有在共享变量上由于调用wait系列方法而被挂起的线程。

public class ThreadDome9 {
    public static void main(String[] args) {
        Object object =new Object();
        Thread t =new Thread(() ->{
            synchronized (object){
                System.out.println(Thread.currentThread().getName() + " t wait之前.");

                try {
                    object.wait();

                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName() + " t wait之后.");
            }
        });
        Thread t1 =new Thread(() ->{
            try {
                Thread.sleep(2000);
                synchronized (object){
                    System.out.println(Thread.currentThread().getName() + " t1 notify之前");
                    object.notify();
                    System.out.println(Thread.currentThread().getName() + " t1 notify之后");

                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

        });
        t.start();
        t1.start();
    }
}​

//结果
Thread-0 t wait 之前
Thread-1 t1 notify 之前
Thread-1 t1 notify 之后
Thread-0 t wait 之后

join和wait区别是:

        join是需要等待 调用此方法的线程结束后,才继续执行

        wait是需要另一个线程通notify进行通知(不要求另一个线程必须执行完)

 

标签:Java,Thread,EE,start,run,线程,new,多线程,方法
From: https://blog.csdn.net/2401_83177222/article/details/142278682

相关文章

  • 【Java集合】HashMap
    哈希表        哈希表又叫散列表,或者映射、字典都是指哈希表,哈希表是通过关键码映射到数组的某个位置来访问的数据结构,实现这个映射的函数就是哈希函数,哈希表结合了数组和链表的优点,查找和插入操作的时间复杂度都是O(1)。        哈希表基于数组实现,哈希函数......
  • java_day2_常量,变量,数据类型,运算符
    一、常量常量:在Java程序运行过程中其值不能发生改变的量分类:1、字面值常量:整数常量表示所有的整数,包括负数10-8小数常量表示所有的小数1.23-3.14布尔常量truefalse空常量null......
  • java计算机毕业设计就业信息服务网站(开题+程序+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着高等教育的普及与就业市场的日益复杂化,毕业生面临的就业挑战愈发严峻。信息不对称成为制约高效就业的一大瓶颈,求职者难以迅速获取全面、准确的招......
  • java计算机毕业设计课后辅导(开题+程序+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景在信息化高速发展的今天,教育领域正经历着前所未有的变革。传统的教学模式已难以满足个性化学习、高效管理的需求。随着互联网技术的普及,在线教育平台......
  • java计算机毕业设计快递信息管理(开题+程序+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景在信息化高速发展的今天,快递行业作为连接生产与消费的重要桥梁,其运作效率与服务质量直接关系到消费者的满意度及市场的繁荣程度。随着电子商务的蓬勃......
  • java计算机毕业设计剧本杀交流分享平台(开题+程序+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景:在数字化娱乐浪潮的推动下,剧本杀作为一种集角色扮演、逻辑推理与社交互动于一体的新兴游戏形式,近年来迅速风靡全球。它不仅为玩家提供了沉浸式的游戏......
  • Nature Genetics | Rajeev K. Varshney综述:解锁植物遗传学的端粒到端粒(T2T)基因组组装
    近期,RajeevK.Varshney团队在Naturegenetics发表综述文章:Unlockingplantgeneticswithtelomere-to-telomeregenomeassemblies。摘要连续基因组序列组装将帮助我们实现作物转化基因组学的全面潜力。最近在测序技术方面的进步,尤其是长读长测序策略,使得构建无间隙的端粒到端粒(T......
  • Java语言程序设计基础篇_编程练习题*18.28 (非递归目录大小)
    目录题目:*18.28(非递归目录大小)习题思路代码示例输出结果题目:*18.28(非递归目录大小)不使用递归改写程序清单18-7习题思路(getSize方法)创建一个变量表示总共的大小。传入路径,创建File文件。创建ArrayList<File>列表,并添加传入的文件。如果列表不为空,则进......
  • 使用Java实现高效用户行为监控系统
    引言背景介绍:随着Web应用的日益复杂和用户体验成为产品成功的关键因素,用户行为监控(UserBehaviorMonitoring,UBM)变得越来越重要。UBM不仅帮助开发者理解用户如何与应用程序交互,还能用于性能优化、错误追踪、用户体验改进等方面。目标读者:本文面向Java开发者、系统架构师以及对用......
  • 【每日一题】LeetCode 2332.坐上公交的最晚时间(数组、双指针、二分查找、排序)
    【每日一题】LeetCode2332.坐上公交的最晚时间(数组、双指针、二分查找、排序)题目描述给你一个下标从0开始长度为n的整数数组buses,其中buses[i]表示第i辆公交车的出发时间。同时给你一个下标从0开始长度为m的整数数组passengers,其中passengers[j]表示第......