多线程相关

 进程(Process)

定义:进程是操作系统进行资源分配和调度的基本单位,它拥有独立的内存空间和系统资源(如CPU时间、打开的文件句柄等)。每个进程都代表一个正在执行的程序实例。
资源:每个进程都有自己的地址空间,包括代码、数据、堆栈和堆等,这意味着进程之间的数据是隔离的。
上下文切换:进程之间的上下文切换成本较高,因为涉及到整个地址空间的切换。
生命周期:进程有独立的生命周期,一个进程可以创建其他进程(子进程),并且进程的结束不会直接影响其他进程。  

 线程(Thread)

 定义:线程是进程内的执行单元,是CPU调度的基本单位。多个线程可以共享同一个进程的资源,如内存空间、文件句柄等。
资源共享:同一进程内的线程可以直接访问该进程的全局变量和资源,线程间通信相对简单、高效(通常通过共享内存)。
轻量级:相比于进程,线程的创建和销毁开销较小,上下文切换也更快,因为它们共享进程的地址空间。
并发与并行:线程使得程序能够在单个进程中实现并发执行,而在多核处理器系统上,线程可以真正并行执行,充分利用硬件资源。
独立执行流:每个线程都有自己的程序计数器、栈空间和局部变量,可以独立执行不同的任务,但它们共享进程的其他资源。

 简而言之,进程资源分配的最小单位,而线程CPU调度的最小单位。一个进程可以包含一个或多个线程,这些线程共享进程的地址空间,从而实现更高效的通信和资源共享,同时也带来了同步和互斥的问题。在现代软件开发中,多线程编程是提高程序响应性和性能的常见手段。

单线程和多线程(Multi-threading)

单线程和多线程是计算机程序执行时的两种不同模型,主要区别在于如何管理和调度执行任务。以下是详细的解释:

单线程:

定义:在一个单线程程序中,只有一个执行线程来处理所有的任务。这意味着程序从上到下按顺序执行代码,一次只能做一件事情。
优点:简单、易于理解和调试,因为代码执行顺序清晰,不会出现线程间的竞态条件或死锁问题。
缺点:如果某个操作耗时较长(如网络I/O或磁盘I/O),整个程序会阻塞,直到该操作完成,导致用户体验下降,比如用户界面可能会暂时无响应。

应用场景:单线程适合简单的任务,或者对实时性和并发性要求不高的场景。

多线程:

定义:在多线程程序中,存在多个并发执行的线程,每个线程负责不同的任务。这样可以同时处理多个操作,提高程序的并行性。
优点:能够充分利用多核处理器的计算能力,提高程序的响应速度和整体性能。同时处理I/O密集型和CPU密集型任务时,可以避免程序阻塞,改善用户体验。
缺点:复杂性增加,需要考虑线程同步和通信问题,如死锁、数据竞争、资源分配等。此外,线程切换和管理也会带来一定的开销。

应用场景:多线程则适用于需要高并发、响应快速或需要充分服务器端软利用硬件资源的复杂应用,如件、图形用户界面、游戏引擎等。

执行特点 :随机的(针对的是线程和线程之间,在线程内部,还是顺序。 

JVM:JVM是多线程的,

  run与start的区别

线程的调度 

        线程调度是指操作系统内核为了使得多个线程能够公平或高效地共享处理器时间而进行的决策过程。在多线程环境中,因为CPU只有一个物理核心(在多核系统中是多个核心),所以不可能同时执行所有线程。线程调度器负责决定哪个线程应该获得CPU的执行权,以及分配给每个线程的时间片。

分时调度(Time-Sharing Scheduling)

  • 在这种调度策略中,操作系统将CPU时间划分为多个时间片,每个线程可以连续执行一个时间片,然后切换到下一个线程。这种方式使得所有线程看起来都在同时运行,因为切换速度快于人类感知。
  • 分时调度可以进一步细分为轮转调度(Round Robin Scheduling),其中所有线程按预定顺序轮流执行

 抢占式调度(Preemptive Scheduling)

  •  抢占式调度允许高优先级的线程中断正在执行的低优先级线程,从而立即获得CPU时间。这种调度方式通常用于实时操作系统或者需要快速响应的系统中。
  • 如果线程的优先级相同,可能会采用某种策略来决定哪一个先执行,例如随机选择。

优先级没有卵用.

然而,我们在Java语言中设置的线程优先级,它仅仅只能被看做是一种"建议"(对操作系统的建议), 实际上,操作系统本身,有它自己的一套线程优先级 (静态优先级 + 动态优先级)

Java官方: 线程优先级并非完全没有用,我们Thread的优先级,它具有统计意义,总的来说,高优先级的线程 占用的cpu执行时间多一点,低优先级线程,占用cpu执行时间,短一点

线程控制API

线程休眠sleep

try {
    Thread.sleep(2000);  // 使线程休眠2秒钟
} catch (InterruptedException e) {
    e.printStackTrace();
}

多线程实现方式二:实现Runnable接口

 

public class Main {

    public static void main(String[] args) {
        // 创建 Runnable 对象
        MyRunnable myRunnable = new MyRunnable();

        // 创建 Thread 对象,传入 Runnable 对象
        Thread thread = new Thread(myRunnable);

        // 启动线程
        thread.start();
    }

    static class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("Thread is running from MyRunnable class.");
            // 在这里可以添加更多的线程逻辑
        }
    }
}

 public static void main(String[] args){

        //创建了一个匿名内部类的实例,该类实现了Runnable接口。当创建一个新的Thread对象时,它接收这个Runnable实例作为参数。这表示当线程运行时,会执行Runnable的run()方法。
        //new Thread(new Runnable() { ... }): 这里创建了一个新的线程对象,Runnable的实现定义在花括号内。
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            //@Override public void run() { ... }: 这是Runnable接口的run()方法的实现。当线程开始执行时,会调用这个方法。
            }
        });


        //是一个Lambda表达式,它代表了一个无参数并且返回值为void的匿名函数。这个函数的主要操作是打印当前线程的名字
        new Thread(()->{
           System.out.println(Thread.currentThread().getName());
        });
    }

 

线程的生命周期

NEW(新建)
线程被创建,但尚未启动。这通常是通过调用Thread类的构造函数或者实现Runnable接口的类的构造函数来创建线程对象。
RUNNABLE(可运行/运行):
线程已经启动并且正在执行,或者准备执行。在Java中,这可能意味着线程获得了CPU的时间片并正在执行其run()方法。
BLOCKED(阻塞):
线程被阻塞,无法继续执行,通常是因为它正在等待获取一个监视器锁(例如,通过synchronized关键字)。
WAITING(无时限等待):
线程在没有设定超时的情况下等待其他线程执行特定操作,比如调用了wait()方法而没有指定时间参数。
TIMED_WAITING(定时等待):
线程正在等待一段时间,或者等待其他条件满足,如调用了wait(long timeout)、sleep(long timeout)或join(long timeout)等方法。
TERMINATED(终止):
线程执行完毕或者被外部中断(interrupt()),线程生命周期结束。

 

 

 

锁的执行流程 

 

 

 

同步代码块(synchronized)

synchronized关键字用于实现线程同步,确保同一时间只有一个线程可以执行特定代码块。这主要用来解决多线程环境下对共享资源的并发访问问题,防止竞态条件和数据不一致性。

synchronized(object){
    // 对共享数据的访问操作
}

 其中,object是同步监视器,通常是需要保护的共享资源的对象引用。当一个线程进入同步代码块时,它会获取object的锁,其他试图进入的线程会等待,直到锁被释放。一旦线程完成同步代码块中的操作并退出,锁会被释放,其他线程就可以获取锁并进入代码块

synchronized有以下特点:
互斥性:同一时刻,只有一个线程能执行同步代码块。
可见性:线程在同步代码块中修改的变量对其他线程是可见的。
可重入性:一个线程已经持有锁的情况下,可以再次进入同步代码块,这称为可重入性。
阻塞和唤醒:当锁被其他线程持有时,试图进入的线程会被阻塞,直到锁被释放,然后线程会被唤醒。
同步代码块比同步方法更灵活,因为你可以指定任意对象作为锁,只同步需要保护的代码部分,而不是整个方法。这可以提高程序的并发性能,因为只有涉及共享资源的部分才会被锁定。 

public class Demo2 {
    public static void main(String[] args) {
        TicketBox ticketBox = new TicketBox(100);

        Thread thread1 = new Thread(ticketBox);
        Thread thread2 = new Thread(ticketBox);
        Thread thread3 = new Thread(ticketBox);

        thread1.setName("窗口1");
        thread2.setName("窗口2");
        thread3.setName("窗口3");

        thread1.start();
        thread2.start();
        thread3.start();

    }

    public static class TicketBox implements Runnable {

        public TicketBox(int ticket) {
            this.ticket = ticket;
        }

        int ticket;

        //创建了一个名为lock的私有(private)常量(final)对象,类型为Object。
//它的主要作用是作为锁(锁对象),在多线程编程中使用。
        private final Object lock = new Object();

        @Override
        public void run() {
            while (true) {
                synchronized ((lock)) {
                    if ((ticket > 0)) {
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                        System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票");
                        ticket--;
                    } else {
                        System.out.println("票已售完");
                        break;
                    }
                }
            }

        }
    }
}

死锁

死锁是指两个或多个并发进程各自持有对方需要的资源,导致它们都无法继续执行的状态。每个进程都在等待其他进程释放资源,从而形成了一个无法打破的循环,导致系统停止响应。死锁通常发生在多线程或分布式系统中。

死锁的四个必要条件

  1. 互斥条件:至少有一个资源必须处于互斥使用状态,即在同一时刻只能有一个进程使用该资源。
  2. 占有并等待条件:一个进程至少占有一个资源,但又申请新的资源,而该资源被其他进程占用。
  3. 不可抢占条件:已占有的资源不能被强制剥夺,必须由占有者自己释放。
  4. 循环等待条件:存在一个进程等待序列,其中每个进程都在等待序列中的下一个进程所占有的资源。

避免死锁的方法包括:

  1. 预防死锁:通过破坏上述四个条件之一来防止死锁。例如,要求所有进程预先声明需要的所有资源,或者限制资源的分配顺序。
  2. 避免死锁:通过算法确保不会出现满足死锁条件的系统状态,例如银行家算法。
  3. 检测和恢复死锁:系统定期检测是否存在死锁,一旦发现就选择一组进程进行回滚或杀死,以解除死锁状态。

在Java中,虽然没有直接的死锁检测机制,但可以通过合理的资源分配和编程实践来避免死锁。例如,避免嵌套的synchronized块,确保资源的有序获取,以及及时释放锁等。

 

wait与notify机制

 wait() notify() 是 Java 中用于线程间通信的关键方法,它们都属于 Object 类,用于在多线程环境下协调线程的执行。这两个方法必须在同步代码块(synchronized 块或方法)中使用,因为它们涉及到对象的监视器(锁)。

拥有相同锁的线程才可以实现wait/notify机制,所以后面的描述中都是假定操作同一个锁。  

  • wait()方法是Object类的方法,它的作用是使当前执行wait()方法的线程等待,在wait()所在的 代码行处暂停执行,并释放锁,直到接到通知被唤醒。在调用wait()之前,线程必须获得锁对象,即只能在同步方法或同步块中调用wait()方法。如果调用wait()时没有持有适当的锁,则抛出 IllegalMonitorStateException,它是 RuntimeException的一个子类,因此不需要try-catch语句捕捉异常。  
  • notify()方法要在同步方法或同步块中调用, 即在调用前,线程必须获得锁对象,如果调用notify() 时没有持有适当的锁,则会抛IllegalMonitorStateException。该方法用来通知那 些可能等待该锁对象的其他线程,如果有多个线程等待,则唤醒其中随机一个线程,并使该线程重新获取锁。  
  • 需要说明的是,执行notify()方法后,当 前线程不会马上释放该锁,因wait方法而阻塞的线程也 并不能马上获取该对象锁,要等到执行notify()方 法的线程将程序执行完,也就是退出synchronized 同步区域后,当前线程才会释放锁,而处于阻塞状 态的线程才可以获取该对象锁。当第一个获得了 该对象锁的wait线程运行完毕后,它会释放该对 象锁,此时如果没有再次使用notify语句,那么其 他呈阻塞状态的线程因为没有得到通知,会继续 处于阻塞状态。  

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值