JAVA基础(JAVA SE)学习笔记(十)多线程

 前言 

1. 学习视频:

尚硅谷Java零基础全套视频教程(宋红康2023版,java入门自学必备)_哔哩哔哩_bilibili

2023最新Java学习路线 - 哔哩哔哩

第三阶段:Java高级应用

9.异常处理

10.多线程

11.常用类和基础API

12.集合框架

13.泛型

14.数据结构与集合源码

15.File类与IO流

16.网络编程

17.反射机制

18.JDK8、17 新特性

正文

10.多线程

10.1 多线程的理解

程序、进程、线程区分:

JAVA基础(JAVA SE)学习笔记(十)多线程_第1张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第2张图片

举例:

线程调度:

JAVA基础(JAVA SE)学习笔记(十)多线程_第3张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第4张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第5张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第6张图片

10.2 多线程的创建方式

JAVA基础(JAVA SE)学习笔记(十)多线程_第7张图片

10.2.1 多线程的创建方式一:继承Thread类

JAVA基础(JAVA SE)学习笔记(十)多线程_第8张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第9张图片

举例:创建一个分线程1,用于遍历100以内的偶数。

JAVA基础(JAVA SE)学习笔记(十)多线程_第10张图片JAVA基础(JAVA SE)学习笔记(十)多线程_第11张图片

可以看到创建的线程和main的线程在交互执行。还可以看到线程名如上。

 System.out.println(Thread.currentThread().getName()+":"+i+"***********");
 System.out.println(Thread.currentThread().getName()+":"+i);

JAVA基础(JAVA SE)学习笔记(十)多线程_第12张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第13张图片

例二:

JAVA基础(JAVA SE)学习笔记(十)多线程_第14张图片

创建Thread类的匿名子类的匿名对象:

JAVA基础(JAVA SE)学习笔记(十)多线程_第15张图片

10.2.2 多线程的创建方式二:实现Runnable接口(建议使用方式)

JAVA基础(JAVA SE)学习笔记(十)多线程_第16张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第17张图片

或:JAVA基础(JAVA SE)学习笔记(十)多线程_第18张图片

声明了两个线程,但是只声明了一个EvenNumberPrint类,相当于共享数据。

相当于 代码逻辑(新建线程执行任务)  和 数据(一个EvenNumberPrint类)的分离

JAVA基础(JAVA SE)学习笔记(十)多线程_第19张图片

两种创建线程的方式对比:

JAVA基础(JAVA SE)学习笔记(十)多线程_第20张图片

练习题:

JAVA基础(JAVA SE)学习笔记(十)多线程_第21张图片

10.3 继承Thread类的常用方法

10.3.1 构造器

JAVA基础(JAVA SE)学习笔记(十)多线程_第22张图片

Thread中:

JAVA基础(JAVA SE)学习笔记(十)多线程_第23张图片

使用举例:

JAVA基础(JAVA SE)学习笔记(十)多线程_第24张图片JAVA基础(JAVA SE)学习笔记(十)多线程_第25张图片

10.3.2 常用方法

JAVA基础(JAVA SE)学习笔记(十)多线程_第26张图片

使用举例:

  JAVA基础(JAVA SE)学习笔记(十)多线程_第27张图片JAVA基础(JAVA SE)学习笔记(十)多线程_第28张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第29张图片

10.3.3 优先级(Priority)

JAVA基础(JAVA SE)学习笔记(十)多线程_第30张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第31张图片

10.3.4 多线程的优点

JAVA基础(JAVA SE)学习笔记(十)多线程_第32张图片

10.4 生命周期

JAVA基础(JAVA SE)学习笔记(十)多线程_第33张图片

现在JDK17,线程的状态如下:

新建、准备、运行、阻塞(锁阻塞、无限等待、计时等待)、死亡。

public enum State {
        NEW,
        RUNNABLE,
        BLOCKED,WAITING,TIMED_WAITING,
        TERMINATED;
    }

运行(Runnable):Ready、Running。

阻塞分为三种状态:

        锁阻塞(BLOCKED)、无限等待(WAITING)、计时等待(TIMED_WAITING)。

死亡(terminated)。

JAVA基础(JAVA SE)学习笔记(十)多线程_第34张图片

10.5 买票案例,引出线程安全问题,引出同步机制

JAVA基础(JAVA SE)学习笔记(十)多线程_第35张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第36张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第37张图片

线程不安全,已卖过的票,还会被再次售卖。加上static共享变量,也不行。

JAVA基础(JAVA SE)学习笔记(十)多线程_第38张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第39张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第40张图片

10.5.1 使用synchronized同步机制处理线程安全问题

同步代码块:

JAVA基础(JAVA SE)学习笔记(十)多线程_第41张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第42张图片

同步监视器用在循环内部,保证了每次售票时,三个线程随机排队进入监视器内部,执行买票,模拟了三个窗口售票。如果把循环体整个加入监视器,则三个窗口只随机排队一次,然后售空才能结束循环,进入下一次排队,不符合实际。

JAVA基础(JAVA SE)学习笔记(十)多线程_第43张图片

使用快捷键,生成环绕方式synchronized监视器,然后直接在小括号内写this。

JAVA基础(JAVA SE)学习笔记(十)多线程_第44张图片

在继承Thread类后,假如声明三个子类对象,要用static修饰作为同步监视器的Object类,使三个子类共享数据。保证唯一性,使用当前类.class,即Window.class

JAVA基础(JAVA SE)学习笔记(十)多线程_第45张图片

同步方法:

JAVA基础(JAVA SE)学习笔记(十)多线程_第46张图片

 public static synchronized void xxx(){ //操作共享数据的代码  }

此时的同步监视器,默认的就是this(调用者)。在下列代码中,this是唯一的,因为只新建了一个SaleTicket2类的对象。三个线程的共享对象是同一个。

JAVA基础(JAVA SE)学习笔记(十)多线程_第47张图片

继承:JAVA基础(JAVA SE)学习笔记(十)多线程_第48张图片

如下:三个线程,没有共享对象,是三个不同对象。this有三个。

JAVA基础(JAVA SE)学习笔记(十)多线程_第49张图片

synchronized的优缺点:

习题:

sleep在哪执行,哪个线程就进入阻塞。(主线程内执行sleep方法,主线程阻塞5秒)

JAVA基础(JAVA SE)学习笔记(十)多线程_第50张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第51张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第52张图片

10.5.2 线程安全的懒汉式

JAVA基础(JAVA SE)学习笔记(十)多线程_第53张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第54张图片

两个线程获取Get方法创建单例的实例对象,线程不安全,创建了两个实例对象。需要添加Synchronized,得到同步方法。

JAVA基础(JAVA SE)学习笔记(十)多线程_第55张图片JAVA基础(JAVA SE)学习笔记(十)多线程_第56张图片

方法二:同步代码块   和   方式三:同步代码块 优化(进监视器之前多加一层判断,效率更高)

JAVA基础(JAVA SE)学习笔记(十)多线程_第57张图片JAVA基础(JAVA SE)学习笔记(十)多线程_第58张图片

使用volatile避免指令重排:

JAVA基础(JAVA SE)学习笔记(十)多线程_第59张图片

将instance声明为volatile,可以避免在第一个线程正在创建的时候,还没有地址,第二个线程判断第一个线程已经非空,就返回instance,造成返回空值。

JAVA基础(JAVA SE)学习笔记(十)多线程_第60张图片

10.5.3 线程的同步机制带来 死锁问题

JAVA基础(JAVA SE)学习笔记(十)多线程_第61张图片

死锁示例:

JAVA基础(JAVA SE)学习笔记(十)多线程_第62张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第63张图片

不睡眠,则很快执行,没有死锁。睡眠诱发死锁概率提高,发现有死锁问题,不能运行出结果。

JAVA基础(JAVA SE)学习笔记(十)多线程_第64张图片

10.5.4 使用jdk5.提供的Lock锁的方式 处理线程安全问题

JAVA基础(JAVA SE)学习笔记(十)多线程_第65张图片

目前创建ReentrantLock(重新re进入entrance 锁lock)的对象:

   //private:外部不可访问;
   //static:几个线程共用同一个锁;
   //final:赋值之后不可修改。
   private static final ReentrantLock lock = new ReentrantLock();

JAVA基础(JAVA SE)学习笔记(十)多线程_第66张图片JAVA基础(JAVA SE)学习笔记(十)多线程_第67张图片

检查'在使用阻塞等待获取锁的方式中,必须在try代码块之外,并且在加锁方法与try代码块之间没有任何可能抛出异常的方法调用,避免加锁成功后,在finally中无法解锁
说明一:如果在lock方法与try代码块之间的方法调用抛出异常,那么无法解锁,造成其它线程无法成功获取锁。
说明二:如果lock方法在try代码块之内,可能由于其它方法抛出异常,导致在finally代码块中,unlock对未加锁的对象解锁,它会调用AQS的tryRelease方法(取决于具体实现类),抛出IllegalMonitorStateException异常。
说明三:在Lock对象的lock方法实现中可能抛出unchecked异常,产生的后果与说明二相同。 java.concurrent.LockShouldWithTryFinallyRule.rule.desc

Positive example:
    Lock lock = new XxxLock();
    // ...
    lock.lock();
    try {
        doSomething();
        doOthers();
    } finally {
        lock.unlock();
    }

JAVA基础(JAVA SE)学习笔记(十)多线程_第68张图片

synchronized不管是同步代码块还是同步方法,都需要在结束一对{}之后,释放对同步监视器的调用。Lock是通过两个方法控制需要被同步的代码,更灵活一些。

Lock作为接口,提供了多种实现类,适合更多更复杂的场景,效率更高。

在JUC会详细讲解LOCK。

10.6 线程的通信

JAVA基础(JAVA SE)学习笔记(十)多线程_第69张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第70张图片

wait()和notify():

能不能使下面两个线程交替打印1~100之间的数:

JAVA基础(JAVA SE)学习笔记(十)多线程_第71张图片

线程一执行到notify(),不需要唤醒,继续执行,然后打印数字,执行wait()。

线程二执行到notify(),本身不需要唤醒,但唤醒线程一,线程二继续执行,打印数字,执行wait()。线程一被唤醒后继续执行wait()后面的代码else,然后重新执行到notify(),本身不需要唤醒,但唤醒线程二。

JAVA基础(JAVA SE)学习笔记(十)多线程_第72张图片

wait()和sleep()的区别:

JAVA基础(JAVA SE)学习笔记(十)多线程_第73张图片

习题:

JAVA基础(JAVA SE)学习笔记(十)多线程_第74张图片

10.7 多线程的创建方式三:实现Callable接口(JDK5.0新增)(了解)

JAVA基础(JAVA SE)学习笔记(十)多线程_第75张图片JAVA基础(JAVA SE)学习笔记(十)多线程_第76张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第77张图片

t1.start()执行之后,分线程开始打印1~100以内的偶数,主线程开始去调用futureTask.get()方法,打印总和。若分线程阻塞(例如加上:Thread.sleep(1000);),则主线程获取的总和结果就会不正确。

其实不是这样的,t1.start()执行之后,分线程开始打印1~100以内的偶数,主线程开始去调用futureTask.get()方法会暂时阻塞,等待return操作的结果。而return操作需要等分线程打印1~100以内的偶数执行完毕,然后才return,主线程才会打印总和。

JAVA基础(JAVA SE)学习笔记(十)多线程_第78张图片

10.8 多线程的创建方式四:使用线程池(开发中使用)

JAVA基础(JAVA SE)学习笔记(十)多线程_第79张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第80张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第81张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第82张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第83张图片

总结:

栈管运行,堆管存储。所以线程在栈里。

JAVA基础(JAVA SE)学习笔记(十)多线程_第84张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第85张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第86张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第87张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第88张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第89张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第90张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第91张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第92张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第93张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第94张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第95张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第96张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第97张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第98张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第99张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第100张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第101张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第102张图片

非静态方法同步监视器,默认的就是this(调用者对象)。

JAVA基础(JAVA SE)学习笔记(十)多线程_第103张图片JAVA基础(JAVA SE)学习笔记(十)多线程_第104张图片JAVA基础(JAVA SE)学习笔记(十)多线程_第105张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第106张图片

非静态方法同步监视器,默认的就是this(调用者对象)。如下,t1线程进入了一个对象SaleTicket1的synchronized方法之后,其他线程例如t2就还能进入对象SaleTicket1的其他方法,因为两个同步监视器的this分别是不同的实例对象s和s1。而且都是唯一的。但是t3和t1的this是同一个,所以不能访问此对象实例s的其他方法。发挥了同步代码块的作用,避免了线程安全问题。

JAVA基础(JAVA SE)学习笔记(十)多线程_第107张图片

阻塞还可能是sleep()、wait()、join()、suspend()了。

JAVA基础(JAVA SE)学习笔记(十)多线程_第108张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第109张图片

JAVA基础(JAVA SE)学习笔记(十)多线程_第110张图片

自己练习一下。

你可能感兴趣的:(Java基础,java)