2022-08-04 吉林化工学院 第五组 韩嘉宁

Java多线程


学习线程心路历程:对新知识的新奇 ——> 感觉乏味,逐渐烦躁 ——> 崩溃边缘反复横跳 ——> 发现我不配 ——> 害!淦吧!!

image

一、创建线程

线程是操作系统能够进行运行调用的最小单位同时一条线程是进程实际运行单位,可以说是轻量型进程

1.创建线程 3 种方式
(1)继承Thread类

继承Thread类并重写run方法,Thread类中run方法不是抽象方法,Thread不是抽象类,一旦继承Thread类后,就是一个独立的线程。

使用 start() 启动线程会执行重写run()方法

class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("重写的run方法。。。。");
    }
}
public class Study01 {
    public static void main(String[] args) {
        MyThread myThread=new MyThread();
        
        //当调用start方法启动线程时,会执行重写run方法
        myThread.start();
        
        //普通对象调方法并未启动线程
        myThread.run();
    }
}
(2)实现Runnable接口(函数式接口FunctionalInterface)

若想让线程启动,必须调用Thread类中start方法

通过调用Thread构造器启动线程

package com.study.morning;


class MyThread1 implements Runnable{
    @Override
    public void run() {
        System.out.println(1);
    }
}



public class Study02 {
    public static void main(String[] args) {
        System.out.println(2);
        MyThread1 myThread1=new MyThread1();
		
        //实现多态
        Thread thread=new Thread(myThread1);   
        
        //启动线程
        thread.start();
        //main方法优先级高
        System.out.println(3);
        System.out.println(4);
    }
}
(3)实现Callable接口
package com.study.morning;


import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

class MyThread2 implements Callable<String>{
    @Override
    public String call() throws Exception {
        System.out.println(1);
        return "call返回值";
    }
}


public class Study03 {
    public static void main(String[] args) {
        System.out.println(2);

        //callable --> FutureTask --> RunnableFuture --> Runnable --> Thread
        FutureTask<String> futureTask=new FutureTask<>(new MyThread2());
        new Thread(futureTask).start();
    }
}

二、Java提供的两种线程

(1)用户线程(一般指主方法)

(2)守护线程(为用户线程服务,仅在用户运行时才需要)

(3)大多数JVM线程为守护线程

三、线程生命周期

(1)整个线程从开始到结束过程:

NEW(新建)

​ 此时线程未被 start()调用执行

RUNNABLE(可运行或准备就绪)

​ 线程正在JVM中执行,等待来自操作系统调度。

BLOCKED(阻塞)

​ 因为某些原因不能立即执行需要挂起等待。(主要是因为外部原因未执行)

WAITING(无限期等待)

​ Object类。若未被唤醒,则一直等待。(主动调用方法,发起主动等待)

TIMED_WAITING(有限期等待)

​ 给线程一个指定等待时间。

TERMINATED(终止线程)

​ 终止线程状态,线程已经执行完毕。

整个流程如下:

image

四、线程安全

1.CPU 多核缓存结构

CPU缓存为了提高程序运行的性能

CPU处理速度最快,内容次之,硬盘处理速度最慢

CPU分为三级缓存:每个CPU都有L1,L2缓存,但是L3缓存是多核公用的。

2. MESI协议

(1)修改态,此缓存被动过,内容与主内存中不同,为此缓存专有

(2)专有态,此缓存与主内存一致,但CPU没有

(3)共享态,此缓存与主内存一致,其他的缓存也有

(4)无效态,此缓存无效,需要从主内存中重新读取

3. 指令重排

使用volatile 关键字来保证一个变量再一次读写操作是避免指令重排

4.线程安全可见性
5.解决线程争抢问题最有效方法:加锁(synchronized ())

悲观锁:有安全性无效率性,强制加锁

乐观锁:有效率性但安全性差

6.线程安全的实现方法

(1)数据不可变

一切不可变的对象一定是线程安全的

对象方法的实现方法的调用者,不需要再进行任何的线程安全的保障

用final关键字修饰的基本数据类型,字符串一定安全。

(2)互斥同步(加锁)【悲观锁】

(3)非阻塞同步。【无锁编程】

(4)无同步方案。多个线程共享数据,但这些数据可以在单独的线程中计算,可以把共享的数据可见范围缩小至一个线程内,无需同步从而保证线程安全。

五、锁(synchronized)

synchronized加锁的三种方式

(1)修饰实例方法,作用于当前实例枷锁,进入同步代码前要获得当前实例的锁

(2)修饰静态方法,作用与当前类对象加锁,进入同步代码前要获得的当前类对象的锁

(3)代码块,指定枷锁对象,对给定对象枷锁,进入同步代码块之前要获得给定对象的锁

涉及名词解释
实例方法:调用该方法的实例
静态方法:类对象
this:调用该方法的实例对象
类对象:类对象
当使用同步方法时,synchronized所的东西是this(默认的)
关于同步方法
  1. 同步方法依然涉及到同步锁对象,synchronized锁的是对象

  2. 非静态的同步方法,同步锁就是this

Lock 与 synchronized 区别

(1)Lock是是接口而synchronized是关键字
(2)二者在发生异常时,synchronized 会释放锁以免发生死锁,而Lock需要手动释放锁
(3)Lock可以让等待锁的线程中断,使用synchronized只会等待的线程一直等待下去,不能响应线程中断
(4)Lock可以提高多个线程进行读操作的效率
同步代码块:

1.选好同步监视器(锁)推荐实用类对象,引入第三方对象,this

2.在实现接口创建的线程类中同步代码块不可以用this来充当同步锁

操作同步代码时,只有一个线程可以参与,其他进程等待。相当于单线程,效率低

synchronized不可以跨JVM解决问题

sleep 与 wait区别:

1.sleep属于Thread类,wait属于Object

2.sleep不能释放锁,在执行期间其他线程无法进入;wait释放锁(即只有在其等待时间过后才可执行进程,等待期间其他线程可以进入)

2.死锁

产生死锁四个必要条件:

(1)互斥使用,当资源被线程占用时,别的线程不能使用

(2)不可抢占,资源请求这不能强制从占有者抢夺资源,资源只能从只能从占有者手动释放

(3)请求和保持

(4)循环等待,存在一个等待队列.p1占有 p2的资源,p2占有p3资源,p3占有p1资源形成等待环路

六、线程重入

在任意线程在拿到锁之后,再次获取锁不会被该锁阻碍。

线程不会被自己锁死

Thread方法:

run

start

currentThread:静态方法获取当前正在执行线程

getId()

setName()

getName()

getPriority(): 获取优先级

setPriority(int)

getState:获取当前进程的生命周期

interrupted():判断是否被终止,返回值boolean

线程相关的基本方法

线程睡眠(sleep)

使现有运行线程暂停运行,等待片刻后运行但不释放锁。

线程等待(wait)

调用该方法的线程进入 WAITING 状态,只有等待另外线程的通知或被中断才会返回,
需要注意的是调用 wait()方法后,会释放对象的锁。
因此,wait 方法一般用在同步方法或同步代码块中。

线程让步(yield)

yield 会使当前线程让出 CPU 执行时间片,与其他线程一起重新竞争 CPU 时间片。
一般情况下, 优先级高的线程有更大的可能性成功竞争得到 CPU 时间片,
但这又不是绝对的,有的操作系统对线程优先级并不敏感。

线程中断(interrupt)

Join 等待其他线程终止
join() 方法,等待其他线程终止,在当前线程中调用一个线程的 join() 方法,
则当前线程转为阻塞状态,回到另一个线程结束,当前线程再由阻塞状态变为就绪状态,
等待 cpu 的宠幸。

线程唤醒(notify)

Object 类中的 notify() 方法,唤醒在此对象监视器上等待的单个线程,
如果所有线程都在此对象上等待,则会选择唤醒其中一个线程,
选择是任意的,并在对实现做出决定时发生,线程通过调用其中一个 wait() 方法,
在对象的监视器上等待,直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程,
被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争。
类似的方法还有 notifyAll() ,唤醒再次监视器上等待的所有线程。

posted @   WorkerHevien  阅读(17)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示