Java多线程那些事儿(一)

为什么要学习多线程?

Java多线程与并发是当前面试的热点,你如果想要找到工作或者高薪水,是必须要熟练掌握的。如Boss直聘上关于Java专家与初级开发工程师的招聘要求:

回忆这几年编码工作经历,我整理了关于多线程方面的知识,由浅入深帮助你了解多线程方面的知识。

什么是进程

进程是操作系统资源分配的最小单位,如下任务管理器中的都是进程,PID是进程id。通俗点就是win10、Mac、linux这些电脑操作系统都是以进程为单位进行资源分配(磁盘空间,内存空间等)。


什么是线程

操作系统调度执行的最小单位,进程得到系统分配的资源后,需要调用执行任务,每个任务都是以线程的方式去执行的。如上图中的任务管理器,PID14436,这个进程有3个线程在执行。

线程的状态
 

创建线程的方法

1.实现Runnable方法

public class MyThread implements Runnable {

    @Override

    public void run() { 
    // 在新的线程中执行的任务
    }

}

2.实现Thread类

public class MyThread extends Thread {

    @Override

    public void run() {
    // 在新的线程中执行的任务
    }

}

线程优先级

线程优先级是操作系统用来调度线程的一种机制。在操作系统中,每个线程都有一个优先级,表示该线程需要执行的顺序。当多个线程同时竞争CPU时间片时,操作系统会根据线程的优先级来决定哪个线程先执行。

线程优先级的取值范围通常为1到10,其中1表示最低优先级,10表示最高优先级。

线程控制

        Thread Joining:当一个线程执行完毕后,它会调用Thread.join()方法等待其他线程的执行。在等待期间,该线程将处于阻塞状态,直到所有其他线程都执行完成。

        Thread Sleep:Thread.sleep()方法可以让线程暂停一段时间,以便让其他线程有机会执行。通过使用Thread.sleep()方法,我们可以控制线程的执行时间和优先级。

        Thread Yield:Thread.yield()方法可以让当前线程放弃CPU控制权,以便让其他更高优先级的线程有机会执行。通过使用Thread.yield()方法,我们可以避免线程之间的竞争和死锁问题。

        Thread Interrupts:当一个线程被中断时,它会抛出InterruptedException异常。在多线程编程中,我们可以使用Thread.interrupt()方法来中断正在运行的线程,并通过catch语句来处理中断异常。

线程上下文切换

        由于电脑的资源是有限的,操作系统对CPU进行时间分片,每个线程都会得到自己的cpu执行时间,线程A比如得到10毫秒的CPU执行时间,10毫秒过完,不管线程A是否执行完成,都会轮到线程B执行。线程B的10毫秒执行完成才有可能轮到线程A继续执行。这个过程就叫做线程上下文切换。

为什么会有线程安全问题

        多线程环境下,可以同时访问某个共享资源会出现数据不一致、死锁等问题,就会出现线程安全问题。举个例子,你银行卡上有5000块,这时你爷爷与奶奶同时你转1000块红包,如果转账过程多线程执行,都是读取的5000块,然后执行+1000操作,导致最后你到账6000,而不是7000,这就是线程安全问题。

锁机制

        线程的锁机制是一种同步机制,用于确保多个线程不会同时访问共享资源,以避免出现竞态条件(上面的红包转账问题就是竞态条件)和死锁等问题。常见的锁包括互斥锁(Mutex)、读写锁(Read-Write Lock)和自旋锁(Spin Lock):

互斥锁:又叫排它锁,它允许一个线程持有锁并阻止其他线程访问共享资源,直到该线程释放锁为止。当一个线程获取了锁时,其他线程必须等待该线程释放锁后才能再次尝试获取锁。

读写锁:允许多个线程同时读取共享资源,但只允许一个线程进行写操作。这样可以提高读取操作的性能,避免写操作阻塞所有读操作。

自旋锁:通过循环来不断地尝试获取锁。当一个线程获取了锁时,如果其他线程也试图获取锁,则该线程会一直循环等待,直到获取到锁为止。

线程饥饿

线程饥饿分为以下两种情况:

        1.假设有多个线程正在等待共享资源。其中一个线程获取了资源但在完成任务后未能释放它,导致其他线程无限期地阻塞。因此,这些线程无法执行它们的任务,饿死了。

        2.当一个较高优先级的线程不断获取资源时,较低优先级的线程无法获得资源,导致较低优先级的线程饿死。 

线程饥饿可能导致吞吐量降低、性能差以及程序完全挂起等问题。因此,有效地管理资源并确保及时释放资源以避免线程饥饿是非常重要的。

死锁问题

假设有两个线程A和B,它们需要访问共享资源R。每个线程都需要持有两个锁才能访问R:   

    线程A需要先获得锁1,然后再获得锁2才能访问R。

    线程B需要先获得锁2,然后再获得锁1才能访问R。

    现在假设这两个线程同时运行,它们会按照以下顺序获取锁:

    线程A获取锁1,然后它尝试获取锁2。

    线程B获取锁2它继续尝试获取锁1。

    两个线程由于需要对方的锁,从而进入相互等待,这就是死锁。

如何检测死锁

可以使用Java自带的jconsole、jvisualvm等监控工具,或者jstack、jmap等命令行工具

如下代码,定义了两个线程A与B,两者互相等待引起死锁

    public static void main(String[] args) throws Exception {
        Object lock1=new Object();
        Object lock2=new Object();

        Thread threadA=new Thread(()->{
            synchronized (lock1){
                try {
                    System.out.println("线程A获取锁1");
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (lock2){
                    System.out.println("线程A获取锁2");
                }
            }
        },"线程A");
        Thread threadB=new Thread(()->{
            synchronized (lock2){
                try {
                    System.out.println("线程B获取锁2");
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (lock1){
                    System.out.println("线程B获取锁1");
                }
            }

        },"线程B");
        threadA.start();
        threadB.start();
    }

Java visualVM

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值