• JAVA高级(一)


    IDEA的使用

    创建模块(Module)

    在Eclipse中又WorkSpace和Project,在IDEA中只有Project和Module,对应关系为

            Eclipse中的WorkSpace        对应        IDEA中的Project

            Eclipse中的Project               对应        IDEA中的Module

    相关配置

    设置主题

     

    这里默认提供了四套主题:IntelliJ,Darcula,Windows等。这里可以根据自己的喜好进行选择。

    设置窗体及菜单的字体及字体大小 (可忽略)

    设置编辑区主题 (可忽略)

     也可自行下载或通过插件更换主题

     设置鼠标滚轮修改字体大小(可忽略)

    设置自动导包功能

     Add unambiguous imports on the fly:自动导入不明确的结构

    Optimize imports on the fly:自动帮我们优化导入的包

    设置显示行号和方法间的分隔符

     忽略大小写提示

     IntelliJ IDEA 的代码提示和补充功能有一个特性:区分大小写。如上图标注所 示,默认就是 First letter 区分大小写的

    区分大小写的情况是这样的:比如我们在 Java 代码文件中输入 stringBuffer, IntelliJ IDEA 默认是不会帮我们提示或是代码补充的,但是如果我们输入 StringBuffer 就可以进行代码提示和补充。

    如果想不区分大小写的话,取消勾选即可。

    设置取消单行显示 tabs 的操作

     如上图标注所示,在打开很多文件的时候,IntelliJ IDEA 默认是把所有打开的文 件名 Tab 单行显示的。但是我个人现在的习惯是使用多行,多行效率比单行高, 因为单行会隐藏超过界面部分 Tab,这样找文件不方便。

    设置默认的字体、字体大小、字体行间距

    修改当前主题的控制台输出的字体及字体大小(可忽略)

     修改代码中注释的字体颜色

     Doc Comment – Text:修改文档注释的字体颜色

    Block comment:修改多行注释的字体颜色

    Line comment:修改单行注释的字体颜色

    设置超过指定 import 个数,改为* (可忽略)

     修改文档注释信息

    设置项目文件编码

     说明:Transparent native-to-ascii conversion 主要用于转换 ascii,一般都要勾选, 不然 Properties 文件中的注释显示的都不会是中文。

    设置当前源文件的编码(可忽略)

    对单独文件的编码修改还可以点击右下角的编码设置区。如果代码内容中包含中 文,则会弹出如上的操作选择。其中:

    ①Reload 表示使用新编码重新加载,新编码不会保存到文件中,重新打开此文 件,旧编码是什么依旧还是什么。

    ②Convert 表示使用新编码进行转换,新编码会保存到文件中,重新打开此文件, 新编码是什么则是什么。

    ③含有中文的代码文件,Convert 之后可能会使中文变成乱码,所以在转换成请 做好备份,不然可能出现转换过程变成乱码,无法还原。

     设置自动编译

     构建就是以我们编写的 java 代码、框架配置文件、国际化等其他资源文件、 JSP 页面和图片等资源作为“原材料”,去“生产”出一个可以运行的项目的 过程。

    Intellij Idea 默认状态为不自动编译状态,Eclipse 默认为自动编译: 很多朋友都是从 Eclipse 转到 Intellij 的,这常常 导致我们在需要操作 class 文件时忘记对修改后 的 java 类文件进行重新编译,从而对旧文件进 行了操作。

    误操作:省电模式(不要勾选)

     设置快捷键

     直接修改该项可以做到大部分快捷键与所选项一致

    导入已有的设置

     选择文件路径重启导入即可

    快捷键补充

            生成构造器/get/set/toString                 alt +shift + s

            生成 try-catch 等(surround with)          alt+shift+z

    关于模板(Templates) 

    .Live Templates(实时代码模板)功能介绍

    它的原理就是配置一些常用代码字母缩写,在输入简写时可以出现你预定义的固 IntelliJ IDEA 的安装、配置与使用 定模式的代码,使得开发效率大大提高,同时也可以增加个性化。最简单的例子 就是在 Java 中输入 sout 会出现 System.out.println();

     和

    都修改即可使用

    多线程

    程序、进程、线程

    程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象

    进程(process)是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期

            程序是静态的,进程是动态的

            进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域

    线程(thread),进程可进一步细化为线程,是一个程序内部的一 条执行路径

            若一个进程同一时间并行执行多个线程,就是支持多线程的

            线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开 销小

            一个进程中的多个线程共享相同的内存单元/内存地址空间它们从同一堆中分配对象,可以 访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资 源可能就会带来安全的隐患。

    进程可以细化为多个线程。

    每个线程,拥有自己独立的:栈、程序计算器

    多个进程,共享同一个进程中的结构:方法区、堆

    单核CPU和多核CPU的理解

            单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程 的任务。例如:虽然有多车道,但是收费站只有一个工作人员在收费,只有收了费 才能通过,那么CPU就好比收费人员。如果有某个人不想交钱,那么收费人员可以 把他“挂起”(晾着他,等他想通了,准备好了钱,再去收费)。但是因为CPU时 间单元特别短,因此感觉不出来。

            如果是多核的话,才能更好的发挥多线程的效率。(现在的服务器都是多核的)

            一个Java应用程序java.exe,其实至少有三个线程:main()主线程,gc() 垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。

    并行与并发

            并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事

            并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。

    使用多线程的优点

    以单核CPU为例,只使用单个线程先后完成多个任务(调用多个方 法),肯定比用多个线程来完成用的时间更短,为何仍需多线程呢?

    多线程程序的优点:

            1. 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。

             2. 提高计算机系统CPU的利用率

            3. 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和 修改

    何时需要多线程

            程序需要同时执行两个或多个任务。

            程序需要实现一些需要等待的任务时,如用户输入、文件读写 操作、网络操作、搜索等。

            需要一些后台运行的程序时。

    补充:线程的分类

    一种是守护线程,一种是用户线程

    线程的创建和使用

    Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread 类来体现。

    多线程的创建

    方式一:继承于Thread类

            1、创建一个继承于Thread的子类

            2、重写Thread类的run()——》将此线程执行的操作声明在run()中

            3、创建Thread类的子类

            4、通过此对象调用start()

    start()的作用:

            启动当前线程

            调用当前线程的run()

    常见问题

            不能通过直接调用run()的方式启动线程

            再启动一个线程,需要再创建一个对象调用它的start,一个对象只能调用一次start

    练习:创建两个分线程,其中一个线程遍历100以内的偶数,另一个吸纳从遍历100以内的奇数
    1. package com.xxx.exer;
    2. /**
    3. * 练习:创建两个分线程,其中一个线程遍历100以内的偶数,另一个吸纳从遍历100以内的奇数
    4. *
    5. * @author Alkaid
    6. * @create 2022-08-10 14:49
    7. */
    8. public class ThreadDemo {
    9. public static void main(String[] args) {
    10. // MyThread1 m1 = new MyThread1();
    11. // MyThread2 m2 = new MyThread2();
    12. // m1.start();
    13. // m2.start();
    14. //创建Thread类的匿名子类
    15. new Thread() {
    16. @Override
    17. public void run() {
    18. for (int i = 0; i < 100; i++) {
    19. if (i % 2 == 0) {
    20. System.out.println(i);
    21. }
    22. }
    23. }
    24. }.start();
    25. new Thread() {
    26. @Override
    27. public void run() {
    28. for (int i = 0; i < 100; i++) {
    29. if (i % 2 != 0) {
    30. System.out.println(i);
    31. }
    32. }
    33. }
    34. }.start();
    35. }
    36. }
    37. class MyThread1 extends Thread {
    38. @Override
    39. public void run() {
    40. for (int i = 0; i < 100; i++) {
    41. if (i % 2 == 0) {
    42. System.out.println(i);
    43. }
    44. }
    45. }
    46. }
    47. class MyThread2 extends Thread {
    48. @Override
    49. public void run() {
    50. for (int i = 0; i < 100; i++) {
    51. if (i % 2 != 0) {
    52. System.out.println(i);
    53. }
    54. }
    55. }
    56. }

    Thread类中的常用方法

            start():启动当前线程,调用当前线程的run();

            run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中

            currentThread:静态方法,返回执行当前代码的进程;

            getName():获取当前线程的名字;

            setName():设置当前线程的名字;

            yiled():释放当前cpu的执行权

            join():在线程A中调用线程B的join(),此时线程A就进入阻塞状态,直到线程B完全执行完成,线程A才结束阻塞状态

            stop():已弃用,强制结束当前线程

            sleep(Long milltime):让当前线程“睡眠”指定的milltime毫秒,在指定的milltime毫秒时间内,当前线程是阻塞状态的

            isAlive():判断当前线程是否存活。

    线程的调度

    调度策略

     Java的调度方法

            同优先级线程组成先进先出队列(先到先服务),使用时间片策略

            对高优先级,使用优先调度的抢占式策略,但是至少从概率上讲,高优先级的线程高概率的情况下被执行,但并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行       

    线程的优先级

    线程的优先级等级

            MAX_PRIORITY:10

            MIN _PRIORITY:1

            NORM_PRIORITY:5        默认优先级

    涉及的方法

             getPriority() :返回线程优先值

             setPriority(int newPriority) :改变线程的优先级

    例子:创建3个窗口买票,总票数为100,使用继承Thread方式

    1. package com.xxx.java;
    2. /**
    3. * 例子:创建3个窗口买票,总票数为100,使用继承Thread方式
    4. * 存在线程安全的问题!
    5. * @author Alkaid
    6. * @create 2022-08-10 18:31
    7. */
    8. public class WindowTest {
    9. public static void main(String[] args) {
    10. Window w1 = new Window();
    11. Window w2 = new Window();
    12. Window w3 = new Window();
    13. w1.setName("窗口1");
    14. w2.setName("窗口2");
    15. w3.setName("窗口3");
    16. w1.start();
    17. w2.start();
    18. w3.start();
    19. }
    20. }
    21. class Window extends Thread{
    22. private static int ticket = 300;
    23. @Override
    24. public void run() {
    25. while(true){
    26. if(ticket > 0){
    27. System.out.println(getName() + ":卖票,票号为" + (300 - ticket));
    28. ticket--;
    29. }else {
    30. break;
    31. }
    32. }
    33. }
    34. }

    创建多线程的方式2:实现Runable接口

    1.创建一个实现Runable接口的类

    2.实现类去实现Runable中的抽象方法:run()

    3.创建实现类的实例对象

    4.将此对象作为参数传递到Thread类的构造器中,创建Thread的对象

    5.通过Thread类的对象调用start()

    1. package com.xxx.java;
    2. /**
    3. * @author Alkaid
    4. * @create 2022-08-10 18:47
    5. */
    6. public class ThreadTest1 {
    7. public static void main(String[] args) {
    8. //创建实现类的实例对象
    9. MThread mThread = new MThread();
    10. //将此对象作为参数传递到Thread类的构造器中,创建Thread的对象
    11. //通过Thread类的对象调用start()--调用run————run()检查当前对象是否有实现Runable接口的target,有就i调用target类的run()方法
    12. Thread t1 = new Thread(mThread);
    13. Thread t2 = new Thread(mThread);
    14. t1.setName("线程一");
    15. t1.start();
    16. t2.setName("线程二");
    17. t2.start();
    18. }
    19. }
    20. //创建一个实现Runable接口的类
    21. class MThread implements Runnable{
    22. //实现类去实现Runable中的抽象方法:run()
    23. @Override
    24. public void run() {
    25. for (int i = 0; i < 100; i++) {
    26. if (i % 2 == 0){
    27. System.out.println(Thread.currentThread().getName() + ":" + i);
    28. }
    29. }
    30. }
    31. }

    例子:创建3个窗口买票,总票数为100,使用实现Runable接口的方式

    1. package com.xxx.java;
    2. /**
    3. * @author Alkaid
    4. * @create 2022-08-10 19:01
    5. */
    6. public class WindowsTest1 {
    7. public static void main(String[] args) {
    8. Window1 w1 = new Window1();
    9. Thread t1 = new Thread(w1);
    10. Thread t2 = new Thread(w1);
    11. Thread t3 = new Thread(w1);
    12. t1.setName("窗口1");
    13. t2.setName("窗口2");
    14. t3.setName("窗口3");
    15. t1.start();
    16. t2.start();
    17. t3.start();
    18. }
    19. }
    20. class Window1 implements Runnable{
    21. private int ticket = 300;
    22. @Override
    23. public void run() {
    24. while (true){
    25. if (ticket > 0){
    26. System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + (300-ticket));
    27. ticket--;
    28. }else {
    29. break;
    30. }
    31. }
    32. }
    33. }

    比较创建线程的两种方式

            优先选择实现Runable接口的方式

    原因:没有类的单继承的局限性

               更适合处理多个线程有公有数据的情况

    联系:public class Thread implements Runable

    相同点:两种方式都需要重写run(),将线程需要的逻辑声明在run()中 

    线程的生命周期

    JDK中用Thread.State类定义了线程的几种状态

    要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类 及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:

            新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态

            就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源

            运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线 程的操作和功能

            阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态

            死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束      

     线程的同步

    问题的提出

            多个线程执行的不确定性引起执行结果的不稳定

            多个线程对账本的共享,会造成操作的不完整性,会破坏数据。

     

     例题:窗口卖票

    问题:卖票过程中出现了重票、错票——线程安全问题

    原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也对车票进行操作

    如何解决:当一个线程在操作ticket的时候,其他线程不能参与进来,直到线程A操作完ticket时,其他线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能改变

    在JAVA中,我们通过同步机制,来解决线程的安全问题

    方式一:同步代码块

    1. synchrolized(同步监视器){
    2. //需要被同步的代码
    3. }

    说明:操作共享数据的代码,即为需要被同步的代码        ——不能包含代码多了,也不能包含少了

    共享数据:多个线程共同操作的变量,比如本例中的ticket

    同步监视器,俗称:锁。任何一个类的对象,都可以充当锁

            要求:多个线程必须要公用同一把锁

    补充:

            在实现Runable接口的创建的多线程的方式中,我们可以考虑使用this充当同步监视器        

            在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器。

    好处:同步的方式,解决了线程的安全问题

    局限性:操作同步代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低

    解决实现类的问题

    1. package com.xxx.java;
    2. /**
    3. * @author Alkaid
    4. * @create 2022-08-10 19:01
    5. */
    6. public class WindowsTest1 {
    7. public static void main(String[] args) {
    8. Window1 w1 = new Window1();
    9. Thread t1 = new Thread(w1);
    10. Thread t2 = new Thread(w1);
    11. Thread t3 = new Thread(w1);
    12. t1.setName("窗口1");
    13. t2.setName("窗口2");
    14. t3.setName("窗口3");
    15. t1.start();
    16. t2.start();
    17. t3.start();
    18. }
    19. }
    20. class Window1 implements Runnable{
    21. private int ticket = 300;
    22. // Object obj = new Object();
    23. Dog d = new Dog();
    24. @Override
    25. public void run() {
    26. //锁在此不行,每人都有一个锁
    27. //Object obj = new Object();
    28. while (true){
    29. //实现类可以直接使用当前对象作为锁
    30. synchronized (this){
    31. if (ticket > 0){
    32. System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + (301-ticket));
    33. ticket--;
    34. }else {
    35. break;
    36. }
    37. }
    38. }
    39. }
    40. }
    41. class Dog{
    42. }

    解决继承类的问题

    1. package com.xxx.java;
    2. /**
    3. * 例子:创建3个窗口买票,总票数为100.
    4. * 存在线程安全的问题!
    5. * @author Alkaid
    6. * @create 2022-08-10 18:31
    7. */
    8. public class WindowTest {
    9. public static void main(String[] args) {
    10. Window w1 = new Window();
    11. Window w2 = new Window();
    12. Window w3 = new Window();
    13. w1.setName("窗口1");
    14. w2.setName("窗口2");
    15. w3.setName("窗口3");
    16. w1.start();
    17. w2.start();
    18. w3.start();
    19. }
    20. }
    21. class Window extends Thread{
    22. private static int ticket = 300;
    23. static Object obj = new Object();
    24. @Override
    25. public void run() {
    26. //synchronized不能多包代码,如这里将while包住会导致只有一个售票口售票
    27. while(true){
    28. //继承类不能使用this
    29. // synchronized (this){
    30. // synchronized (obj){
    31. //但可以使用当前类来充当锁
    32. synchronized (Window.class){
    33. if(ticket > 0){
    34. System.out.println(getName() + ":卖票,票号为" + (301 - ticket));
    35. ticket--;
    36. }else {
    37. break;
    38. }
    39. }
    40. }
    41. }
    42. }

    方式二:同步方法

            如果操作共享数据的代码完整的声明在同一个方法中,我们不妨将此方法声明为同步的

    同步方法仍然涉及到同步监视器,只是不需要我们显式的声明

            非静态的同步方法:同步监视器是this

            静态的同步方法:同步监视器是:当前类本身

    解决继承类的问题

    1. package com.xxx.java;
    2. /**
    3. * 例子:创建3个窗口买票,总票数为100.
    4. * 存在线程安全的问题!
    5. *
    6. * @author Alkaid
    7. * @create 2022-08-10 18:31
    8. */
    9. public class WindowTest2 {
    10. public static void main(String[] args) {
    11. Window2 w1 = new Window2();
    12. Window2 w2 = new Window2();
    13. Window2 w3 = new Window2();
    14. w1.setName("窗口1");
    15. w2.setName("窗口2");
    16. w3.setName("窗口3");
    17. w1.start();
    18. w2.start();
    19. w3.start();
    20. }
    21. }
    22. class Window2 extends Thread {
    23. private static int ticket = 300;
    24. @Override
    25. public void run() {
    26. while (true) {
    27. show();
    28. }
    29. }
    30. private static synchronized void show() { //同步监视器为当前类
    31. //private synchronized void show() { 同步监视器 w1 w2 w3此种解决方案是错误的
    32. if (ticket > 0) {
    33. System.out.println(Thread.currentThread().getName() + ":卖票,票号为" + (301 - ticket));
    34. ticket--;
    35. }
    36. }
    37. }

    解决实现类的问题

    1. package com.xxx.java;
    2. /**
    3. * @author Alkaid
    4. * @create 2022-08-10 19:01
    5. */
    6. public class WindowsTest3 {
    7. public static void main(String[] args) {
    8. Window3 w1 = new Window3();
    9. Thread t1 = new Thread(w1);
    10. Thread t2 = new Thread(w1);
    11. Thread t3 = new Thread(w1);
    12. t1.setName("窗口1");
    13. t2.setName("窗口2");
    14. t3.setName("窗口3");
    15. t1.start();
    16. t2.start();
    17. t3.start();
    18. }
    19. }
    20. class Window3 implements Runnable {
    21. private int ticket = 300;
    22. @Override
    23. public void run() {
    24. while (true) {
    25. show();
    26. }
    27. }
    28. private synchronized void show() { //同步监视器:this
    29. if (ticket > 0) {
    30. System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + (301 - ticket));
    31. ticket--;
    32. }
    33. }
    34. }

    解决饿汉式安全问题

    1. package com.xxx.java1;
    2. /**
    3. * @author Alkaid
    4. * @create 2022-08-11 2:04
    5. */
    6. public class BankTest {
    7. }
    8. class Bank{
    9. private Bank(){
    10. }
    11. private static Bank instance = null;
    12. public static Bank getInstance(){
    13. //方式一 效率稍差
    14. // synchronized (Bank.class){
    15. // if (instance == null) {
    16. // instance = new Bank();
    17. // }
    18. // return instance;
    19. // }
    20. //方式二
    21. if (instance == null) {
    22. synchronized (Bank.class){
    23. if (instance == null) {
    24. instance = new Bank();
    25. }
    26. }
    27. }
    28. return instance;
    29. }
    30. }

    线程的死锁问题

    死锁

            不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁

             出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续

            我们使用同步时,要避免出现死锁

    演示死锁1

    1. package com.xxx.java1;
    2. /**
    3. * @author Alkaid
    4. * @create 2022-08-11 19:05
    5. */
    6. public class ThreadTest {
    7. public static void main(String[] args) {
    8. StringBuffer s1 = new StringBuffer();
    9. StringBuffer s2 = new StringBuffer();
    10. new Thread() {
    11. @Override
    12. public void run() {
    13. synchronized (s1) {
    14. s1.append("a");
    15. s2.append("1");
    16. try {
    17. sleep(100);
    18. } catch (InterruptedException e) {
    19. throw new RuntimeException(e);
    20. }
    21. synchronized (s2) {
    22. s1.append("b");
    23. s2.append("2");
    24. System.out.println(s1);
    25. System.out.println(s2);
    26. }
    27. }
    28. }
    29. }.start();
    30. new Thread(new Runnable() {
    31. @Override
    32. public void run() {
    33. synchronized (s2) {
    34. s1.append("c");
    35. s2.append("3");
    36. try {
    37. Thread.currentThread().sleep(100);
    38. } catch (InterruptedException e) {
    39. throw new RuntimeException(e);
    40. }
    41. synchronized (s1) {
    42. s1.append("d");
    43. s2.append("4");
    44. System.out.println(s1);
    45. System.out.println(s2);
    46. }
    47. }
    48. }
    49. }).start();
    50. }
    51. }

    演示死锁2

    1. package com.xxx.java1;
    2. class A {
    3. public synchronized void foo(B b) {
    4. System.out.println("当前线程名: " + Thread.currentThread().getName()
    5. + " 进入了A实例的foo方法"); // ①
    6. try {
    7. Thread.sleep(200);
    8. } catch (InterruptedException ex) {
    9. ex.printStackTrace();
    10. }
    11. System.out.println("当前线程名: " + Thread.currentThread().getName()
    12. + " 企图调用B实例的last方法"); // ③
    13. b.last();
    14. }
    15. public synchronized void last() {
    16. System.out.println("进入了A类的last方法内部");
    17. }
    18. }
    19. class B {
    20. public synchronized void bar(A a) {
    21. System.out.println("当前线程名: " + Thread.currentThread().getName()
    22. + " 进入了B实例的bar方法"); // ②
    23. try {
    24. Thread.sleep(200);
    25. } catch (InterruptedException ex) {
    26. ex.printStackTrace();
    27. }
    28. System.out.println("当前线程名: " + Thread.currentThread().getName()
    29. + " 企图调用A实例的last方法"); // ④
    30. a.last();
    31. }
    32. public synchronized void last() {
    33. System.out.println("进入了B类的last方法内部");
    34. }
    35. }
    36. public class DeadLock implements Runnable {
    37. A a = new A();
    38. B b = new B();
    39. public void init() {
    40. Thread.currentThread().setName("主线程");
    41. // 调用a对象的foo方法
    42. a.foo(b);
    43. System.out.println("进入了主线程之后");
    44. }
    45. public void run() {
    46. Thread.currentThread().setName("副线程");
    47. // 调用b对象的bar方法
    48. b.bar(a);
    49. System.out.println("进入了副线程之后");
    50. }
    51. public static void main(String[] args) {
    52. DeadLock dl = new DeadLock();
    53. new Thread(dl).start();
    54. dl.init();
    55. }
    56. }

    解决方法

             专门的算法、原则

            尽量减少同步资源的定义 

            尽量避免嵌套同步

    释放锁的操作

            当前线程的同步方法、同步代码块执行结束。

            当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、 该方法的继续执行。

            当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束。

            当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线 程暂停,并释放锁。

    不会释放锁的操作

            线程执行同步代码块或同步方法时,程序调用Thread.sleep()、 Thread.yield()方法暂停当前线程的执行

            线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程 挂起,该线程不会释放锁(同步监视器)。

                    应尽量避免使用suspend()和resume()来控制线程

    解决线程安全问题的方式三

    Lock(锁)

    从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同 步锁对象来实现同步。同步锁使用Lock对象充当。

    java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的 工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象 加锁,线程开始访问共享资源之前应先获得Lock对象。

    ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和 内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以 显式加锁、释放锁。

    lock于synchronized的区别

    相同:二者都可以解决线程安全问题

    不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器

            Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())

    优先顺序:

    Lock —— 同步代码块(已经进入了方法体,分配了相应资源) ——同步方法 (在方法体之外)

    1. package com.xxx.java1;
    2. import java.util.concurrent.locks.ReentrantLock;
    3. /**
    4. * @author Alkaid
    5. * @create 2022-08-11 20:41
    6. */
    7. class Window implements Runnable {
    8. private int ticket = 10000;
    9. //实例化ReentrantLock
    10. private ReentrantLock lock = new ReentrantLock();
    11. @Override
    12. public void run() {
    13. while (true) {
    14. try {
    15. //调用锁定lock方法
    16. lock.lock();
    17. if (ticket > 0) {
    18. try {
    19. Thread.sleep(10);
    20. } catch (InterruptedException e) {
    21. throw new RuntimeException(e);
    22. }
    23. System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);
    24. ticket--;
    25. }else {
    26. break;
    27. }
    28. }finally {
    29. //调用解锁unlock方法
    30. lock.unlock();
    31. }
    32. }
    33. }
    34. }
    35. public class LockTest {
    36. public static void main(String[] args) {
    37. Window w = new Window();
    38. Thread t1 = new Thread(w);
    39. Thread t2 = new Thread(w);
    40. Thread t3 = new Thread(w);
    41. t1.setName("窗口一");
    42. t2.setName("窗口二");
    43. t3.setName("窗口三");
    44. t1.start();
    45. t2.start();
    46. t3.start();
    47. }
    48. }

    练习

    银行有一个账户。

    有两个储户分别向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额。

    问题:该程序是否有安全问题,如果有,如何解决?

    【提示】

    1,明确哪些代码是多线程运行代码,须写入run()方法

    2,明确什么是共享数据。

    3,明确多线程运行代码中哪些语句是操作共享数据的。

    1. package com.xxx.exer;
    2. /**
    3. * @author Alkaid
    4. * @create 2022-08-12 1:03
    5. */
    6. class Account{
    7. private double balance;
    8. public Account(double balance) {
    9. this.balance = balance;
    10. }
    11. public synchronized void deposit(double amt){
    12. if(amt > 0){
    13. balance += amt;
    14. try {
    15. Thread.sleep(1000);
    16. } catch (InterruptedException e) {
    17. throw new RuntimeException(e);
    18. }
    19. System.out.println(Thread.currentThread().getName() + "存钱成功,余额为:" + balance);
    20. }
    21. }
    22. }
    23. class Customer extends Thread{
    24. private Account acct;
    25. public Customer(Account acct){
    26. this.acct = acct;
    27. }
    28. @Override
    29. public void run() {
    30. for (int i = 0; i < 3; i++) {
    31. acct.deposit(1000);
    32. }
    33. }
    34. }
    35. public class AccountTest {
    36. public static void main(String[] args) {
    37. Account acct = new Account(0);
    38. Customer c1 = new Customer(acct);
    39. Customer c2 = new Customer(acct);
    40. c1.setName("甲");
    41. c2.setName("乙");
    42. c1.start();
    43. c2.start();
    44. }
    45. }

    线程的通信

     例题

    使用两个线程打印 1-100。线程1, 线程2 交替打印

    涉及到的三个方法

    wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器

    notify():一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程被wait,就唤醒优先级高的线程

    notifiyAll():一旦执行此方法,就会唤醒所有被wait的线程

    说明:

           wait()、notify()、notifiyAll() 三个方法必须使用在同步代码块或同步方法中

            wait()、notify()、notifiyAll() 三个方法的调用者必须是同步代码块或同步方法中的同步监视器

            wait()、notify()、notifiyAll() 三个方法是定义在java.lang.Object类中

    sleep()和wait()的异同:

            相同点:一旦执行方法,都可以使得当前线程进入阻塞状态

            不同点:

                    两个方法声明的位置不同:Thread类中声明sleep(),Object类中声明wait()

                    调用的要求不同:sleep()可以在任何需要的场景下调用,wait()必须使用在同步代码块或同步方法中

                    关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁

    1. package com.xxx.java2;
    2. /**
    3. * @author Alkaid
    4. * @create 2022-08-12 2:18
    5. */
    6. class Number implements Runnable {
    7. private int number = 1;
    8. @Override
    9. public void run() {
    10. while (true){
    11. synchronized (this){
    12. notify();
    13. if (number <= 100){
    14. System.out.println(Thread.currentThread().getName() + ":" + number);
    15. number++;
    16. try {
    17. wait();
    18. } catch (InterruptedException e) {
    19. throw new RuntimeException(e);
    20. }
    21. }else{
    22. break;
    23. }
    24. }
    25. }
    26. }
    27. }
    28. public class ComunicationTest {
    29. public static void main(String[] args) {
    30. Number n = new Number();
    31. Thread t1 = new Thread(n);
    32. Thread t2 = new Thread(n);
    33. t1.setName("线程1");
    34. t2.setName("线程2");
    35. t1.start();
    36. t2.start();
    37. }
    38. }

    经典例题:生产者/消费者问题

    生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处 取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者试图 生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通 知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如 果店中有产品了再通知消费者来取走产品。

    这里可能出现两个问题:

             生产者比消费者快时,消费者会漏掉一些数据没有取到。

             消费者比生产者快时,消费者会取相同的数据。

    1.是否是多线程问题?

            是,生产者线程,消费者线程

    2.是否有共享数据?

            是,店员(或产品)

    3.如何解决线程的安全问题?

            同步机制,有三种方法

    4.是否涉及线程的通信?

            是

    JDK5.0新增线程创建方式

    新增方式一:实现Callable接口

    与使用Runnable相比, Callable功能更强大些

            相比run()方法,可以有返回值

             方法可以抛出异常

             支持泛型的返回值

            需要借助FutureTask类,比如获取返回结果

    Future接口

            可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。

            FutrueTask是Futrue接口的唯一的实现类

            FutureTask 同时实现了Runnable, Future接口。它既可以作为 Runnable被线程执行,又可以作为Future得到Callable的返回值

    1.创建一个实现Callable的实现类
    2.实现call方法,将此线程需要执行的操作声明在call()中
    3.创建Callable接口实现类的对象
    4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
    5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()

    如何理解实现Callable接口的创建多线程比实现Runable接口创建多线程方式强大?

    call()可以有返回值

    call()可以抛出异常,被外面的操作捕获,获取异常的信息

    Callable是支持泛型的

    1. package com.xxx.java2;
    2. import java.util.concurrent.Callable;
    3. import java.util.concurrent.ExecutionException;
    4. import java.util.concurrent.FutureTask;
    5. /**
    6. * @author Alkaid
    7. * @create 2022-08-12 14:05
    8. */
    9. //1.创建一个实现Callable的实现类
    10. class NumThread implements Callable {
    11. //2.实现call方法,将此线程需要执行的操作声明在call()中
    12. @Override
    13. public Object call() throws Exception {
    14. int sum = 0;
    15. for (int i = 0; i < 100; i++) {
    16. if (i % 2 == 0) {
    17. System.out.println(i);
    18. sum += i;
    19. }
    20. }
    21. return sum;
    22. }
    23. }
    24. public class ThreadNew {
    25. public static void main(String[] args) {
    26. //3.创建Callable接口实现类的对象
    27. NumThread numThread = new NumThread();
    28. //4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
    29. FutureTask futureTask = new FutureTask(numThread);
    30. //5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
    31. new Thread(futureTask).start();
    32. try {
    33. //6.获取Callable中call方法的返回值
    34. //get()返回值即为futureTask构造器参数Callable实现类重写的call()的返回值
    35. Object sum = futureTask.get();
    36. System.out.println("总和为" + sum);
    37. } catch (InterruptedException e) {
    38. throw new RuntimeException(e);
    39. } catch (ExecutionException e) {
    40. throw new RuntimeException(e);
    41. }
    42. }
    43. }

    新增方式二:使用线程池

    背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程, 对性能影响很大

    思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具

    好处:

             提高响应速度(减少了创建新线程的时间)

            降低资源消耗(重复利用线程池中线程,不需要每次都创建)

            便于线程管理

                    corePoolSize:核心池的大小

                    maximumPoolSize:最大线程数

                    keepAliveTime:线程没有任务时最多保持多长时间后会终止

    线程池相关API

    JDK 5.0起提供了线程池相关API:ExecutorService 和 Executors

    ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor

            void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行 Runnable

           Future submit(Callable task):执行任务,有返回值,一般又来执行 Callable

           void shutdown() :关闭连接池

    Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

            Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池   

            Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池

            Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池

            Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

    面试题:创建多线程有几种方式?四种

           

    1. package com.xxx.java2;
    2. import java.util.concurrent.*;
    3. /**
    4. * @author Alkaid
    5. * @create 2022-08-12 14:40
    6. */
    7. class NumberThread implements Runnable {
    8. @Override
    9. public void run() {
    10. for (int i = 0; i <= 100; i++) {
    11. if (i % 2 == 0) {
    12. System.out.println(Thread.currentThread().getName() + ":" + i);
    13. }
    14. }
    15. }
    16. }
    17. class NumberThread2 implements Runnable {
    18. @Override
    19. public void run() {
    20. for (int i = 0; i <= 100; i++) {
    21. if (i % 2 != 0) {
    22. System.out.println(Thread.currentThread().getName() + ":" + i);
    23. }
    24. }
    25. }
    26. }
    27. class NumberCall implements Callable {
    28. @Override
    29. public Object call() throws Exception {
    30. int sum = 0;
    31. for (int i = 0; i <= 100; i++) {
    32. if (i % 5 == 0) {
    33. System.out.println(Thread.currentThread().getName() + ":" + i);
    34. sum += i;
    35. }
    36. }
    37. return sum;
    38. }
    39. }
    40. public class ThreadPool {
    41. public static void main(String[] args) {
    42. //1.提供指定线程数量的线程池
    43. ExecutorService service = Executors.newFixedThreadPool(10);
    44. ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
    45. //设置线程池的属性
    46. service1.setCorePoolSize(15);
    47. //2.执行指定的线程的操作,需要提供实现Runable接口或Callable接口实现类的对象
    48. service.execute(new NumberThread()); //适合使用于Runable
    49. service.execute(new NumberThread2()); //适合使用于Runable
    50. FutureTask f = (FutureTask) service.submit(new NumberCall());//适合使用于Callable
    51. try {
    52. Object sum = f.get();
    53. System.out.println("总和为" + sum);
    54. } catch (InterruptedException e) {
    55. throw new RuntimeException(e);
    56. } catch (ExecutionException e) {
    57. throw new RuntimeException(e);
    58. }
    59. //3.关闭连接池
    60. service.shutdown();
    61. }
    62. }

  • 相关阅读:
    项目打包优化
    Python基础入门例程16-NP16 发送offer(列表)
    app发布动态解决图片适配问题(等比展示)
    获取 Adobe Creative Cloud 2023 创意应用软件,解锁无限创造力
    “羊驼“入侵CV,美团&浙大沈春华团队将LLaMA向CV扩展,构建全新基础模型VisionLLaMA
    对导航简单分析
    第1次 更多的bash shell命令
    K线形态识别_跳空下跌三颗星
    GoFrame 优化接口的错误码和异常的思路
    浏览器中环境判断和系统判断-js封装
  • 原文地址:https://blog.csdn.net/m0_56413004/article/details/126241886