多线程基础篇

多线程

进程和线程

  1. 资源分配与管理
    • 进程是系统进行资源分配和调度的基本单位,每个进程都拥有独立的地址空间,即拥有自己的内存、数据栈、全局变量等,以及系统分配的其他资源(如文件描述符)。这意味着进程之间是相互隔离的,一个进程的崩溃通常不会直接影响到其他进程。
    • 线程是进程内的执行单元,多个线程共享所属进程的地址空间和资源。线程拥有自己的程序计数器、栈空间和局部变量,但不拥有单独的地址空间,这使得线程之间的通信和数据共享更为直接,同时也减少了创建和切换线程的开销。
  2. 调度与执行
    • 进程的创建、切换和销毁通常由操作系统内核完成,开销较大。进程之间的切换涉及到整个地址空间的切换,因此速度较慢。
    • 线程则属于轻量级实体,其创建、切换和销毁通常在用户态下完成(尽管某些操作系统或特定情况下可能涉及内核),开销较小,可以实现更高的并发性。
  3. 通信与同步
    • 进程之间的通信需要借助于进程间通信(IPC)机制,如管道、消息队列、信号量、套接字等,这些机制通常由操作系统提供,实现较为复杂。
    • 线程之间可以直接读写同一进程内的数据,通信更为简便高效,但也容易引发数据竞争和同步问题。为了协调线程间的执行顺序,可以使用互斥锁、信号量、条件变量等同步原语。
  4. 健壮性与稳定性
    • 一个进程的错误通常不会直接破坏其他进程的地址空间,因此系统的整体稳定性较好。
    • 如果一个线程发生错误,由于线程间共享资源,可能导致整个进程崩溃,因此需要仔细管理资源共享和异常处理以维护进程的稳定性。
  5. 性能与效率
    • 进程提供了更好的隔离性和保护,但创建和切换成本较高,不适合需要频繁切换的场景。
    • 线程由于共享资源和较低的上下文切换开销,适合于要求高并发和快速响应的场景,但在资源管理和保护方面不如进程严格。

在这里插入图片描述

创建多线程的方式

1、继承Thread类
package day01;

/**
 * 创建线程的方式:
 * 1、继承Thread类
 * 2、重写其中的run方法 run方法中编写的就是让线程做的事
 * 3、创建线程对象
 * 4、启动线程不是调用run方法,调用start方法
 *      调用start方法启动线程  然后会调用线程对象中的run方法
 */
public class MyThread {
    public static void main(String[] args){
        //创建线程对象
        M1 m1 = new M1();
        m1.start();
        M2 m2 = new M2();
        m2.start();

    }
}

package day01;
//m1这个类就是线程类   Thread 本身就是线程类 子类也是一个线程类
public class M1 extends Thread{
//    run方法中编写的就是线程做的事
    @Override
    public void run() {
        for(int i=0;i<=100;i++){
            System.out.println("第一个线程打印的数字是:"+i);
        }
    }
}

package day01;
//m2这个类就是线程类
public class M2 extends Thread{
//    run方法中编写的就是线程做的事
    @Override
    public void run() {
        for(int i=0;i<=100;i++){
            System.out.println("第二个线程打印的数字是:"+i);
        }
    }
}

缺点:一旦一个类继承了Thread类后 就不能在继承其他的类了(Java单继承)

2、实现Runnable接口
package day01;
//实现Runnable接口  实现多线程  重写其中的run方法
public class M3 implements Runnable{
    @Override
    public void run() {
        for(int i=0;i<=100;i++){
            System.out.println("实现Runnable接口实现多线程:"+i);
        }
    }
}
package day01;

/**
 * 二、实现Runnable接日
 *    1、实现Runnable接
 *    2、实现其中的run方法 run方法中编写的是让线程的做的事情
 *    3、创建线程对象 Thread类创建线程对象
 *    4、创建Runnable类型的对象 将创建Runnable类型的对象作为Thead类构造方法的实参对象传入到线程对象中
 *    5、使用start启动线程 线程启动后 会调用Runnable对象中的run方法
 *
 */
public class MyThread {
    public static void main(String[] args){
        //创建线程对象
        M1 m1 = new M1();
        m1.start();
        M2 m2 = new M2();
        m2.start();
        
        M3 m3 = new M3();
        Thread thread = new Thread(m3);
        thread.start();
    }
}

缺点:以上两种方法都没办法获取到run运行的结果的返回值 即使加上等待时间 也没办法确定

3、实现Callable接口
package day01;

import java.util.concurrent.Callable;

public class M4 implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i <= 100; i++) {
            sum = sum+i;
        }
        return sum;
    }
}

package day01;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * 三、实现Callable接口
 *    1、实现Callable接口
 *    2、创建FutureTask对象 并将Callable对象传入进来FutureTask<Integer> futureTask = new FutureTask<>(m4)
 *    3、创建线程对象 将futureTask对象作为构造方法的实参传入
 *    4、使用start方法启动线程
 *    5、调用futureTask方法获取线程运行的结果
 */
public class MyThread {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建线程对象
        M4 m4 = new M4();
        FutureTask<Integer> futureTask = new FutureTask<>(m4);
        Thread thread = new Thread(futureTask);
        thread.start();
        Integer sum = futureTask.get();
        System.out.println(sum);
    }
}

常用的方法:

1、Thread.sleep 使当前正在执行的停留[暂停执行]多少亳秒

​ sleep在哪个线程中 就让哪个线程暂停执行

2、setName 给线程设置名字

3、Thread.currentThread().getName() 获取当前正在执行的线程的名字

package day01;

/**
 *  常用的方法:
 *      1、sleep 使当前正在执行的停留[暂停执行]多少亳秒
 *          sleep在哪个线程中 就让哪个线程暂停执行
 *      2、setName 给线程设置名字
 *      3、Thread.currentThread().getName()    获取当前正在执行的线程的名字
 */
public class MyThread {
    //main方法也是一个线程  主线程
    public static void main(String[] args) throws InterruptedException {
        M1 m1 =new M1();
        m1.setName("线程一");
        m1.start();
        Thread.sleep(5000);
        System.out.println("main方法执行完毕");
    }
}

package day01;
//m1这个类就是线程类
public class M1 extends Thread{
//    run方法中编写的就是线程做的事
    @Override
    public void run() {
        for(int i=0;i<=100;i++){
            System.out.println(Thread.currentThread().getName()+"打印的数字是:"+i);
        }
    }
}

总结:

创建线程的方式 3种 继承Thread 实现Callable接口 实现Runnable接口’

1、Thread本身就是线程 需要继承 重写run方法 run方法没有返回值 run方法不能抛异常

​ 只能自己try catch 自己处理异常 外部不能获取到异常信息

2、实现Runnable接口 需要实现 实现其中的run方法 run方法没有返回值 run方法不能抛异常

​ 只能自己try catch 自己处理异常 外部不能获取到异常信息

3、实现Callable接口 实现call方法 call方法中写的是让线程做的事情 call方法有返回值

​ get方法获取到返回值 call方法可以抛出异常 外部就能知道线程中发生了什么异常

优先级

线程存在着优先级:

​ 线程如何调度:线程都会交给cpu调度

​ 1、分时调度模型:所有的线程轮流使用cpu 平均分配每个线程占用的cpu的时间

​ 2、抢占式调度模型:哪个线程抢到cpu,哪个线程执行 优先让优先级高的线程使用cpu(有很高的概率,不一定能拿到)

​ 优先级都一样 随机选择一个线程执行,优先级高的线程使用cpu时间会更长一点

​ java中使用抢古式调度模型 多线程执行时 就有随机性!只有你抢到cpu时你才有执行权,抢不到就等着

​ 创建的线程 我们使用start启动后 不一定立刻执行 只有你抢到cpu时你才能执行

优先级:1、getPriority()获取某个线程的优先级默认值是5

​ 2、setPriority()设置线程的优先级 取值方位是1-10

public class MyThread {
    //main方法也是一个线程  主线程
    public static void main(String[] args) throws InterruptedException {
        M1 m1 =new M1();
        m1.setName("线程一");
        m1.setPriority(10);
        m1.start();
        
        M1 m2 = new M1();
        m2.setPriority(1);
        m2.setName("线程二");
        m2.start();
    }
}

线程安全问题

线程安全问题:当多个线程操作同一份资源的时候会出现线程安全问题

package day01;

public class M5 extends Thread{
    private String name;

    public M5(String name) {
        this.name = name;
    }

    private static int num = 100;
    @Override
    public void run() {
        while (true){
            if(num>0){
                System.out.println(name+"卖出了第"+num+"号票");
                num--;
            }else {
                break;
            }
        }
    }
}

public class MyThread {
    //main方法也是一个线程  主线程
    public static void main(String[] args) throws InterruptedException {
        M5 m51 = new M5("窗口1");
        M5 m52 = new M5("窗口2");
//        M5 m53 = new M5("窗口3");
        m51.start();
        m52.start();
//        m53.start();
    }
}

案例看到了重票 0号票 负票······等问题

解决方案:一个一个线程去操作共享资源 排队操作

Synchronized

线程同步:加锁 同步锁
线程同步之Synchronized

1、修饰方法

当前力法只能被一个线程访问,其他线程拿不到锁 只能等待 上一个线程释放锁资源

1、继承Thread

​ 修饰的是非静态方法:谁调用这个非静态方法 谁就是锁对象不能用

​ 修饰的是静态方法: 默认情况下以当前的类,class[类 模版]作为同步监视器 整个内存中是唯一的

2、实现Runnable

​ 修饰的是非静态方法:谁调用这个非静态方法 谁就是锁对象

​ 修饰的是静态方法: 默认情况下以当前的类,class[类 模版]作为同步监视器 整个内存中是唯一的

3、实现Callable

//修饰的是非静态方法,不能用
public class M5 extends Thread{
    private String name;
    private static Object o = new Object();

    public M5(String name) {
        this.name = name;
    }

    private static int num = 100;
    @Override
    public void run() {
        while (num>0) {
            sale();
        }
    }

    public synchronized void sale(){
        if (num > 0) {
            System.out.println(name + "卖出了第" + num + "号票");
            num--;
        }
    }
}
修饰的是静态方法

public class M5 extends Thread{
    private static Object o = new Object();

    private static int num = 100;
    @Override
    public void run() {
        while (num>0) {
            sale();
        }
    }
    public synchronized static void sale(){
        if (num > 0) {
            System.out.println(Thread.currentThread().getName() + "卖出了第" + num + "号票");
            num--;
        }
    }
}

实现Runnable

public class M6 implements Runnable{
    private int num = 100;
    @Override
    public void run() {
        while (num>0){
            sale();
        }
    }
    public synchronized void sale(){
        if (num > 0) {
            System.out.println(Thread.currentThread().getName() + "卖出了第" + num + "号票");
            num--;
        }
    }
}
public class MyThread {
    //main方法也是一个线程  主线程
    public static void main(String[] args) throws InterruptedException {
        M6 m6 = new M6();
        Thread thread1 = new Thread(m6);
        Thread thread2 = new Thread(m6);
        thread1.start();
        thread2.start();
    }
}
2、修饰代码块

当前这个代码块中的代码只能有一个线程执行其他线程需要等待 上一个线程释放锁资源

​ synchronized(锁对象/同步监视器){

​ }

​ 要求多个线程监控同一个对象

​ 要求锁对象对所有操作此同步代码块的线程来说是唯一的,此时只能有一个线程来操作对应的代码块了
​ 只有当前线程释放锁了,下一个线程才能进来执行相应的代码块中的代码

好处:解决了多线程情况下 线程安全问题

弊端:当线程很多时,需要每个线程去判断这把锁是否被其他线程持有?判断过程消耗资源
由原来的并行变成了串行,效率变低了

package day01;

public class M5 extends Thread{
    private String name;
    private static Object o = new Object();

    public M5(String name) {
        this.name = name;
    }

    private static int num = 100;
    @Override
    public void run() {
        while (true) {
            synchronized (o) {
                if (num > 0) {
                    System.out.println(name + "卖出了第" + num + "号票");
                    num--;
                } else {
                    break;
                }
            }
        }
    }
}

public class MyThread {
    //main方法也是一个线程  主线程
    public static void main(String[] args) throws InterruptedException {
        M5 m51 = new M5("窗口1");
        M5 m52 = new M5("窗口2");

        m51.start();
        m52.start();
    }
}

synchronized存在的问题
 1、具体加锁和解锁的过程我们看不见

​ 2、对于多个线程来说,获取到锁是不公平的

​ 3、拿不到锁,就死等

扩充:发生异常是否会释放锁?

​ 会释放锁资源

Lock

线程同步之Lock 锁 接口 多线程监视同一把锁 共有同一个对象

​ 实现类:ReentrantLock 默认非公平锁,传入true,是公平锁

Lock lock = new ReentrantLock()

try{
lock.lock()
业务逻辑

}catch(Exception e){

​ 处理异常
}finally{
Lock.unLock();

}

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class M6 implements Runnable{
    private int num = 100;
    //默认非公平锁,传入true,是公平锁
    private Lock lock = new ReentrantLock();
    @Override
    public void run() {
        while (num>0){
            try {
                lock.lock();
                sale();
            }catch (Exception e){
                System.out.println("发生异常");
            }finally {
                lock.unlock();
            }

        }
    }
    public void sale(){
        if (num > 0) {
            System.out.println(Thread.currentThread().getName() + "卖出了第" + num + "号票");
            num--;
        }
    }
}
public class MyThread {
    //main方法也是一个线程  主线程
    public static void main(String[] args) throws InterruptedException {
        M6 m6 = new M6();
        Thread thread1 = new Thread(m6);
        Thread thread2 = new Thread(m6);
        thread1.start();
        thread2.start();

    }
}

注意:

tryLock() 尝试获取锁,获取到返回true,获取不到返回false (立刻返回)

boolean tryLock(long time,TimeUnit unit)在指定的时间后获取不到锁 去做其他事情(指定时间内)

获取到锁,才能解锁,加几次锁,就解几次锁

默认非公平锁,传入true,是公平锁

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class M6 implements Runnable{
    private int num = 100;
    //默认非公平锁,传入true,是公平锁
    private Lock lock = new ReentrantLock(true);
    @Override
    public void run() {
        while (num>0){
            //tryLock()尝试获取锁,获取到返回true,获取不到返回false
            if(lock.tryLock()){
                try {
                    sale();
                    Thread.sleep(1);
                }catch (Exception e){
                    System.out.println("发生异常");
                }finally {
                    //获取到锁,才能解锁,加几次锁,就解几次锁
                    lock.unlock();
                }
            }else {
                System.out.println("没有获取到锁");
            }
        }
    }
    public void sale(){
        if (num > 0) {
            System.out.println(Thread.currentThread().getName() + "卖出了第" + num + "号票");
            num--;
        }
    }
}

过程:
1、创建一个Lock类型的对象
2、使用Lock对象的lock方法加锁
3、使用Lock对象的unLock方法解锁
使用Lock对象作为锁对象时,需要将其放入try…catch…finally代码块中进行加锁和解锁

ReadWriteLock

ReadWriteLock读写锁 读锁写锁
排他锁|互斥锁:当前线程获取锁对象后,其他线程不能在获取该锁对象

​ 只有当前线程释放锁其他线程才能拿到锁 (写锁 ReentrantLock synchronized lock)

​ 共享锁:多个线程可以同时享有这把锁不会造成线程的等待 (读锁)

总结:
当一个线程获取到写锁, 其他线程既不能获取到写锁也不能获取到读锁

当一个线程获取到读锁,其他线程可以获取到读锁 获取不到写锁

只有同一个锁对象的写锁和读锁是互斥的并且写锁之间也是互斥的

不同锁对象之间没有影响

读读不互斥

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class M7 extends Thread{
    private static ReadWriteLock lock = new ReentrantReadWriteLock();
    //获取锁
    private static Lock readLock = lock.readLock();

    @Override
    public void run() {
        try{
            readLock.lock();
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName());
        }catch (Exception e){

        }finally {
            readLock.unlock();
        }
    }
}

读写互斥

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;

//写锁
public class M8 extends Thread{
    private Lock writelock;

    public M8(ReadWriteLock readWriteLock) {
        this.writelock = readWriteLock.writeLock();
    }
    @Override
    public void run() {
        try{
            writelock.lock();
            Thread.sleep(5000);
            System.out.println(Thread.currentThread().getName());
        }catch (Exception e){

        }finally {
            writelock.unlock();
        }
    }
}
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;

public class M9 extends Thread{
    //读锁
    private Lock readLock;
    public M9(ReadWriteLock readWriteLock) {
        this.readLock = readWriteLock.readLock();
    }
    @Override
    public void run() {
        try{
            readLock.lock();
            Thread.sleep(5000);
            System.out.println(Thread.currentThread().getName());
        }catch (Exception e){

        }finally {
            readLock.unlock();
        }
    }
}
public class MyThread {
    //main方法也是一个线程  主线程
    public static void main(String[] args) throws InterruptedException {
        ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        M8 m8 = new M8(readWriteLock);
        M9 m9 = new M9(readWriteLock);
        m8.start();
        m9.start();
    }
}

写写互斥

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class M7 extends Thread{
    private static ReadWriteLock lock = new ReentrantReadWriteLock();
    //获取锁
    private static Lock writeLock = lock.writeLock();

    @Override
    public void run() {
        try{
            writeLock.lock();
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName());
        }catch (Exception e){

        }finally {
            writeLock.unlock();
        }
    }
}
ReentrantReadWriteLock的相关特性:
1、公平性

​ 默认非公平锁 读线程之间没有锁操作 所以读操作没公平性和非公平性

​ 写操作时 由于写操作可能立即获取到锁【写锁】 导致其他线程获取不到读锁或者写锁
​ 此时涉及到公平性和非公平性

2、重入性

​ 读锁或者写锁按照锁的请求顺序可以再次获取读锁或者写锁

​ 只有写线程释放了写锁,读线程才能获取重入锁

​ 【同一个线程】写线程获取写锁后,可以在次获取读锁和写锁,但是读线程获取读锁后不能获取写锁

读锁可以嵌套读锁和写锁,一旦嵌套了写锁,对应的写锁代码进不来

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class M10 extends Thread{
    private static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    Lock writeLock = readWriteLock.writeLock();
    Lock readLock = readWriteLock.readLock();

    @Override
    public void run() {
        try{
            readLock.lock();
            test();
        }catch (Exception e){

        }finally {
            readLock.unlock();
        }
    }

    public void test(){
        try {
            writeLock.lock();
            System.out.println("我进来了");
        }catch (Exception e){

        }finally {
            writeLock.unlock();
        }
    }
}
3、锁升级

​ 读锁是不能直接升级为写锁 想要获取一个写锁 先释放读锁 才能获取写锁

4、锁降级

​ 写线程获取写锁后也可以直接获取读锁,然后释放写锁,这样便从写锁到了读锁

​ 从而实现锁降级,此时线程持有的锁是读锁

5、锁中断

读锁和写锁都支持获取锁期间被巾断 都支持tryLock5、

6、获取条件变量

写锁提供了条件发量[Condition对象]的支持 但是读锁不支持获取条件变量

7、读写锁的数量

读锁和写锁的数量最大的值只能是65535的数量

线程的生命周期

1、新建状态 new Thread这个过程新建一个线程

2、就绪状态(线程是可以运行的) 创建完线程对象后,调用start方法,处于就绪状态

3、运行状态(线程正在运行) 处于就绪状态的线程获得CPU后,开始运行,此时处于运行状态

在运行状态过程中,如果时间片到了,当前线程没有再次抢到CPU执行权,回到就绪状态

调用yield方法也能让线程回到就绪状态

4、阻塞状态 调用相关的方法,让线程暂时停止运行,并且释放CPU资源,不在竞争CPU

​ sleep方法会释放cpu资源,不会释放锁

​ wait方法会释放cpu资源,会释放锁

​ 当阻塞完毕后,从阻塞状态回到了就绪状态,得到CPU后继续运行

5、消亡: 线程中的run执行完毕或者调用stop方法(),处于死亡状态
在这里插入图片描述

守护线程与非守护线程

守护线程:主线程执行的时候 对应的守护线程也会执行当主线程执行完毕后,无论守护线程的业务是否执行完毕,都会停止运行

非守护线程:主线程执行的时候对应的非守护线程也会执行当主线程执行完毕后 非守护线程继续执行 直至run方法执行完毕才会停止 setDaemon(true)当前线程是守护线程

public class MyThread {
    //main方法也是一个线程  主线程
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(){
            @Override
            public void run() {
                for(int i=0;i<=100;i++){
                    try{
                        Thread.sleep(1000);
                    }catch (Exception e){

                    }finally {
                        System.out.println("线程一=======》"+i);
                    }
                }
            }
        };
        thread.setDaemon(true);
        thread.start();
        for (int i = 0; i <=100;i++) {
            try{
                Thread.sleep(100);
            }catch (Exception e){

            }finally {
                System.out.println("主线程=====》"+i);
            }
        }

    }
}

un方法执行完毕才会停止 setDaemon(true)当前线程是守护线程

public class MyThread {
    //main方法也是一个线程  主线程
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(){
            @Override
            public void run() {
                for(int i=0;i<=100;i++){
                    try{
                        Thread.sleep(1000);
                    }catch (Exception e){

                    }finally {
                        System.out.println("线程一=======》"+i);
                    }
                }
            }
        };
        thread.setDaemon(true);
        thread.start();
        for (int i = 0; i <=100;i++) {
            try{
                Thread.sleep(100);
            }catch (Exception e){

            }finally {
                System.out.println("主线程=====》"+i);
            }
        }

    }
}
  • 12
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值