清华扫地僧整理的全网最全多线程详解,看完怀疑自己的认知

gdb调试总结笔记 文章目录一、介绍二、简单操作三、用GBD 调试多进程程序四、用gdb调试多线程程序五、用gdb工具分析core文件1、core文件开关2、core文件命名和保存路径3、分析Core 文件4、运行这个有bug 的程序,可以在当前目录下生成core文件六、调试一个正在运行的程序七、设置断点的方式八、断点操作九、在调试程序时,gdb常用命令总结 一、介绍 GDB是GNU开源组织发布的一个强大的UNIX下... 阅读详情

前言

现在越来越多的公司,对精通多线程的的人才越来越重视,可见多线程技术有多热门。今天,小编结合清华扫地僧级别大佬的分享,为大家带来这篇多线程的总结,希望大家能够喜欢。

清华扫地僧整理的全网最全多线程详解,看完怀疑自己的认知,

 

一、线程概述

几乎所有的操作系统都支持同时运行多个任务,一个任务通常就是一个程序,每个运行中的程序就是一个进程。当一个程序运行时,内部可能包含了多个顺序执行流,每个顺序执行流就是一个线程。

二、线程与进程

进程概述:

几乎所有的操作系统都支持进程的概念,所有运行中的任务通常对应一个进程( Process)。当一个程序进入内存运行时,即变成一个进程。进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位。

进程特征:

1、独立性:进程是系统中独立存在的实体,它可以拥有自己独立的资源,每一个进程都拥有自己私有的地址空间。在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间

2、动态性:进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合。在进程中加入了时间的概念。进程具有自己的生命周期和各种不同的状态,这些概念在程序中都是不具备的

3、并发性:多个进程可以在单个处理器上并发执行,多个进程之间不会互相影响。

线程:

线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。

并发和并行:

并发:同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行

并行:同一时刻,有多条指令在多个处理器上同时执行  

多线程:

概述:

多线程就是几乎同时执行多个线程(一个处理器在某一个时间点上永远都只能是一个线程!即使这个处理器是多核的,除非有多个处理器才能实现多个线程同时运行。)。几乎同时是因为实际上多线程程序中的多个线程实际上是一个线程执行一会然后其他的线程再执行,并不是很多书籍所谓的同时执行。

多线程优点:

  1. 进程之间不能共享内存,但线程之间共享内存非常容易。
  2. 系统创建进程时需要为该进程重新分配系统资源,但创建线程则代价小得多,因此使用多线程来实现多任务并发比多进程的效率高
  3. Java语言内置了多线程功能支持,而不是单纯地作为底层操作系统的调度方式,从而简化了Java的多线程编程

三、使用多线程

多线程的创建:

(1)、继承Thread类:

第一步:定义Thread类的之类,并重写run方法,该run方法的方法体就代表了线程需要执行的任务

第二步:创建Thread类的实例

第三步:调用线程的start()方法来启动线程

public class FirstThread extends Thread {
     
    private int i;
    public void run() {
        for(;i<100;i++) {
            System.out.println(getName()+" "+i);
        }
    }
     
    public static void main(String[] args) {
        for(int i=0;i<100;i++) {
            //调用Thread的currentThread方法获取当前线程
            System.out.println(Thread.currentThread().getName()+" "+i);
            if(i==20) {
                new FirstThread().start();
                new FirstThread().start();
            }
        }
         
    }
 
}

(2)、实现Runnable接口:

第一步:定义Runnable接口的实现类,并重写该接口的run方法,该run方法同样是线程需要执行的任务

第二步:创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象

public class SecondThread implements Runnable {
     
    private int i;
 
    @Override
    public void run() {
        for(;i<100;i++) {
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
    }
     
    public static void main(String[] args) {
        for(int i=0;i<100;i++) {
            System.out.println(Thread.currentThread().getName()+" "+i);
            if(i==20) {
                SecondThread s1=new SecondThread();
                new Thread(s1,"新线程1").start();;
                new Thread(s1,"新线程2").start();
            }
        }
    }
     
}

(3)、使用Callable和Future创建线程

细心的读者会发现,上面创建线程的两种方法。继承Thread和实现Runnable接口中的run都是没有返回值的。于是从Java5开始,Java提供了Callable接口,该接口是Runnable接口的增强版。Callable接口提供了一个call()方法可以作为线程执行体,但call()方法比run()方法功能更强大。

创建并启动有返回值的线程的步骤如下:

第一步:创建 Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,且该call()方法有返回值,再创建 Callable实现类的实例。从Java8开始,可以直接使用 Lambda表达式创建 Callable对象

第二步:使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call方法的返回值

第三步:使用FutureTask对象作为Thread对象的target创建并启动新线程

第四步:通过FutureTask的get()方法获得子线程执行结束后的返回值

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
 
public class ThirdThread {
     
    public static void main(String[] args) {
        //ThirdThread rt=new ThirdThread();
        FutureTask<Integer> task=new FutureTask<Integer>((Callable<Integer>)()->{
            int i=0;
            for(;i<100;i++) {
                System.out.println(Thread.currentThread().getName()+"的循环变量i"+i);
            }
            return i;
        }) ;
         
        for(int i=0;i<100;i++) {
            System.out.println(Thread.currentThread().getName()+"的循环变量i为"+i);
            if(i==20) {
                new Thread(task,"有返回值的线程").start();;
            }
        }
        try {
            System.out.println("子线程的返回值"+task.get());
        }catch(Exception e) {
            e.printStackTrace();
        }
    }
 
}

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

采用Runnable、Callable接口的方式创建多线程的优缺点:

优点:

1、线程类只是实现了 Runnable接口或 Callable接口,还可以继承其他类

2、在这种方式下,多个线程可以共享同一个 target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。

缺点:

编程稍稍复杂,如果需要访问当前线程,则必须使用Thread.currentThread()方法。

采用继承 Thread类的方式创建多线程的优缺点:

优点:

编写简单,如果需要访问当前线程,则无须使用 Thread.current Thread()方法,直接使用this即可获得当前线程

缺点:

因为线程已经继承了Thread类,所以不能再继承其他类

线程的生命周期:

新建和就绪状态:

当程序使用new关键字创建一个线程后,该线程就处于新建状态。

当线程对象调用了start()方法后,该线程就处于就绪状态。

运行和阻塞状态:

如果处于就绪状态的线程获取了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态。

当线程调用sleep(),调用一个阻塞式IO方法,线程会被阻塞

死亡状态:

1、run()或者call()方法执行完成,线程正常结束

2、线程抛出一个未捕获的Exception或Error

3、直接调用该线程的stop方法来结束该线程——该方法容易导致死锁,不推荐使用

清华扫地僧整理的全网最全多线程详解,看完怀疑自己的认知,

 

线程状态转化图

四、控制线程

join线程

Thread提供了让一个线程等待另一个线程完成的方法——join方法。当在某个程序执行流中调用其直到被 join方法加入的join线程执行完为止

public class JoinThread extends Thread {
     
    //提供一个有参数的构造器,用于设置该线程的名字
    public JoinThread(String name) {
        super(name);
    }
     
    //重写run方法,定义线程体
    public void run() {
        for(int i=0;i<10;i++) {
            System.out.println(getName()+" "+i);
        }
    }
     
    public static void main(String[] args) throws InterruptedException {
        //启动子线程
        new JoinThread("新线程").start();
        for(int i=0;i<10;i++) {
            if(i==5) {
                JoinThread jt=new JoinThread("被join的线程");
                jt.start();
                //main线程调用了jt线程的join方法,main线程
                //必须等jt执行结束才会向下执行
                jt.join();
            }
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
    }
     
     
 
}

运行结果

main 0
main 1
main 2
main 3
main 4
新线程 0
新线程 1
新线程 2
新线程 3
被join的线程 0
新线程 4
被join的线程 1
新线程 5
被join的线程 2
新线程 6
被join的线程 3
新线程 7
被join的线程 4
新线程 8
被join的线程 5
新线程 9
被join的线程 6
被join的线程 7
被join的线程 8
被join的线程 9
main 5
main 6
main 7
main 8
main 9

后台线程:

有一种线程,它是在后台运行的,它的任务是为其他的线程提供服务,这种线程被称为“后台线程( Daemon Thread)”,又称为“守护线程”或“精灵线程”。JVM的垃圾回收线程就是典型的后台线程。

后台线程有个特征:如果所有的前台线程都死亡,后台线程会自动死亡。

调用 Thread对象的 setDaemon(true)方法可将指定线程设置成后台线程。下面程序将执行线程设置成后台线程,可以看到当所有的前台线程死亡时,后台线程随之死亡。当整个虚拟机中只剩下后台线程时,程序就没有继续运行的必要了,所以虚拟机也就退出了。

 public class DaemonThread extends Thread {
 
    //定义后台线程的线程体与普通线程没有什么区别
    public void run() {
        for(int i=0;i<1000;i++) {
            System.out.println(getName()+" "+i);
        }
    }
     
    public static void main(String[] args) {
        DaemonThread t=new DaemonThread();
        //将此线程设置为后台线程
        t.setDaemon(true);
        t.start();
        for(int i=0;i<10;i++) {
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
        //程序到此执行结束,前台线程(main)结束,后台线程也随之结束
    }
     
}

运行结果:

main 0
Thread-0 0
main 1
Thread-0 1
Thread-0 2
main 2
Thread-0 3
Thread-0 4
Thread-0 5
main 3
main 4
Thread-0 6
main 5
Thread-0 7
Thread-0 8
main 6
main 7
main 8
Thread-0 9
main 9
Thread-0 10
Thread-0 11
Thread-0 12
Thread-0 13
Thread-0 14
Thread-0 15
Thread-0 16
Thread-0 17
Thread-0 18
Thread-0 19
Thread-0 20
Thread-0 21

线程睡眠:

如果需要让当前正在执行的线程暂停一段时间,并进入阻塞状态,则可以通过调用 Thread类的静态 sleep方法来实现。 sleep方法有两种重载形式

static void sleep(long millis):让当前正在执行的线程暂停millis毫秒,并进入阻塞状态

static void sleep(long millis,int nanos):让当前正在执行的线程暂停millis毫秒加上nanos毫微秒,并进入阻塞状态,通常我们不会精确到毫微秒,所以该方法不常用

import java.util.Date;
 
public class SleepTest {
     
    public static void main(String[] args) throws InterruptedException {
        for(int i=0;i<10;i++) {
            System.out.println("当前时间"+new Date());
            Thread.sleep(1000);
        }
    }
 
}

改变线程优先级:

每个线程执行时都有一定的优先级,优先级高的线程获得较多的执行机会,优先级低的线程则获得较少的执行机会。

每个线程默认的优先级都与创建它的父线程的优先级相同,在默认情况下,main线程具有普通优先级,由main线程创建的子线程也具有普通优先级。

Thread类提供了 setPriority(int newPriority)、 getPriority()方法来设置和返回指定线程的优先级,其中 setPriority()方法的参数可以是一个整数,范围是1-10之间,也可以使用 Thread类的如下三个静态常量

MAX_PRIORITY:其值是10

MIN_PRIORITY:其值时1

NORM_PRIPRITY:其值是5

public class PriorityTest extends Thread {
     
    //定义一个构造器,用于创建线程时传入线程的名称
    public PriorityTest(String name) {
        super(name);
    }
     
    public void run() {
        for(int i=0;i<50;i++) {
            System.out.println(getName()+",其优先级是:"+getPriority()+"循环变量的值:"+i);
        }
    }
     
    public static void main(String[] args) {
        //改变主线程的优先级
        Thread.currentThread().setPriority(6);
        for(int i=0;i<30;i++) {
            if(i==10) {
                PriorityTest low=new PriorityTest("低级");
                low.start();
                System.out.println("创建之初的优先级:"+low.getPriority());
                //设置该线程为最低优先级
                low.setPriority(Thread.MIN_PRIORITY);
            }
            if(i==20) {
                PriorityTest high=new PriorityTest("高级");
                high.start();
                System.out.println("创建之初的优先级"+high.getPriority());
                high.setPriority(Thread.MAX_PRIORITY);
            }
        }
    }
     
     
 
}

线程同步

线程安全问题:

现有如下代码:

public class Account {
     
    private String accountNo;
    private double balance;
     
    public Account() {}
 
    public Account(String accountNo, double balance) {
        super();
        this.accountNo = accountNo;
        this.balance = balance;
    }
 
    public String getAccountNo() {
        return accountNo;
    }
 
    public void setAccountNo(String accountNo) {
        this.accountNo = accountNo;
    }
 
    public double getBalance() {
        return balance;
    }
 
    public void setBalance(double balance) {
        this.balance = balance;
    }
     
    public int hashCode() {
        return accountNo.hashCode();
    }
     
    public boolean equals(Object obj) {
        if(this==obj) {
            return true;
        }
        if(obj!=null&&obj.getClass()==Account.class) {
            Account target=(Account)obj;
            return target.getAccountNo().equals(accountNo);
        }
        return false;
    }
     
}
import com.alibaba.util.Account;
 
public class DrawThread extends Thread{
     
    //模拟用户账户
    private Account account;
     
    //当前取钱线程所希望的钱数
    private double drawAmount;
     
    public DrawThread(String name,Account account,double drawAmount) {
        super(name);
        this.account=account;
        this.drawAmount=drawAmount;
    }
 
    //多个线程修改同一个共享数据,可能发生线程安全问题
    @Override
    public void run() {
            if(account.getBalance()>drawAmount) {
                System.out.println(getName()+"取钱成功"+" "+drawAmount);
                try {
                    Thread.sleep(1);
                }catch(Exception e) {
                    e.printStackTrace();
                }
                account.setBalance(account.getBalance()-drawAmount);
                System.out.println("\t余额为"+" "+account.getBalance());
            }else {
                System.out.println("余额不足,取钱失败");
            }
 
    }
     
}
 import com.alibaba.util.Account;
 
public class DrawTest {
     
    public static void main(String[] args) {
        Account account=new Account("1234567",1000);
        //模拟两个线程同时操作账号
        new DrawThread("甲", account, 800).start();;
        new DrawThread("乙", account, 800).start();;
    }
 
}

现在我们来分析一下以上代码:

我们现在希望实现的操作是模拟多个用户同时从银行账户里面取钱,如果用户取钱数小于等于当前账户余额,则提示取款成功,并将余额减去取款钱数,如果余额不足,则提示余额不足,取款失败。

Account 类:银行账户类,里面有一些账户的基本信息,以及操作账户信息的方法

DrawThread类:继承了Thread,是一个多线程类,用于模拟多个用户操作同一个账户的信息

DrawTest:测试类

这时我们运行程序可能会看到如下运行结果:

甲取钱成功 800.0
乙取钱成功 800.0
    余额为 200.0
    余额为 -600.0

如何解决线程安全问题:

①、同步代码块:

为了解决线程问题,Java的多线程支持引入了同步监视器来解决这个问题,使用同步监视器的通用方法就是同步代码块。同步代码块的语法格式如下:

synchronized(obj){

  //此处的代码就是同步代码块

}

我们将上面银行中DrawThread类作如下修改:

import com.alibaba.util.Account;
 
public class DrawThread extends Thread{
     
    //模拟用户账户
    private Account account;
     
    //当前取钱线程所希望的钱数
    private double drawAmount;
     
    public DrawThread(String name,Account account,double drawAmount) {
        super(name);
        this.account=account;
        this.drawAmount=drawAmount;
    }
 
    //多个线程修改同一个共享数据,可能发生线程安全问题
    @Override
    public void run() {
        //使用account作为同步监视器,任何线程在进入下面同步代码块之前
        //必须先获得account账户的锁定,其他线程无法获得锁,也就无法修改它
        //这种做法符合:"加锁-修改-释放锁"的逻辑
        synchronized(account) {
            if(account.getBalance()>drawAmount) {
                System.out.println(getName()+"取钱成功"+" "+drawAmount);
                try {
                    Thread.sleep(1);
                }catch(Exception e) {
                    e.printStackTrace();
                }
                account.setBalance(account.getBalance()-drawAmount);
                System.out.println("\t余额为"+" "+account.getBalance());
            }else {
                System.out.println("余额不足,取钱失败");
            }
        }
         
         
    }
     
}

我们来看这次的运行结果:

甲取钱成功 800.0
    余额为 200.0
余额不足,取钱失败

我们发现结果变了,是我们希望看到的结果。因为我们在可能发生线程安全问题的地方加上了synchronized代码块

②:同步方法:

与同步代码块对应,Java的多线程安全支持还提供了同步方法,同步方法就是使用 synchronized关键字来修饰某个方法,则该方法称为同步方法。对于 synchronized修饰的实例方法(非 static方法)而言,无须显式指定同步监视器,同步方法的同步监视器是this,也就是调用该方法的对象。同步方法语法格式如下:

public synchronized void 方法名(){

  //具体代码

}

③、同步锁:

从Java5开始,Java提供了一种功能更强大的线程同步机制—一通过显式定义同步锁对象来实现同步,在这种机制下,同步锁由Lock对象充当。

Lock提供了比 synchronized方法和 synchronized代码块更广泛的锁定操作,Lock允许实现更灵活的结构,可以具有差别很大的属性,并且支持多个相关的 Condition对象。

在实现线程安全的控制中,比较常用的是 ReentrantLock(可重入锁)。使用该Lock对象可以显式加锁、释放锁,通常使用ReentrantLock的代码格式如下:

class X{
    //定义锁对象
    private final ReentrantLock lock=new ReentrantLock();
    //...
     
    //定义需要保护线程安全的方法
    public void m() {
        //加锁
        lock.lock();
        try {
            //需要保证线程安全的代码
            //...method body
        }finally {
            //释放锁
            lock.unlock();
        }
    }
     
}

死锁:

当两个线程相互等待对方释放同步监视器时就会发生死锁,Java虚拟机没有监测,也没有采取措施来处理死锁情况,所以多线程编程时应该采取措施避免死锁岀现。一旦岀现死锁,整个程序既不会发生任何异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。

死锁是很容易发生的,尤其在系统中出现多个同步监视器的情况下,如下程序将会出现死锁

class A{
    public synchronized void foo(B b) {
        System.out.println("当前线程名:"+Thread.currentThread().getName()+"进入A实例的foo方法");//①
         
        try {
            Thread.sleep(200);
        }catch(InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("当前线程名:"+Thread.currentThread().getName()+"企图调用B的方法");//③
        b.last();
    }
     
    public synchronized void last() {
        System.out.println("进入了A类的last方法");
    }
}
 
 
class B{
     
    public synchronized void bar(A a) {
        System.out.println("当前线程名:"+Thread.currentThread().getName()+"进入B实例的bar方法");//②
        try {
            Thread.sleep(200);
        }catch(InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("当前线程名:"+Thread.currentThread().getName()+"企图调用A的方法");//④
        a.last();
    }
     
    public synchronized void last() {
        System.out.println("进入了B类的last方法");
    }
     
}
 
public class DeadLock implements Runnable {
     
    A a=new A();
    B b=new B();
     
    public void init() {
        Thread.currentThread().setName("主线程");
        a.foo(b);
        System.out.println("进入了主线程之后");
    }
     
     
     
    @Override
    public void run() {
        Thread.currentThread().setName("副线程");
        b.bar(a);
        System.out.println("进入副线程之后");
    }
     
     
    public static void main(String[] args) {
        DeadLock d=new DeadLock();
        new Thread(d).start();
        d.init();
    }
     
     
}

运行结果:

清华扫地僧整理的全网最全多线程详解,看完怀疑自己的认知,

 

从图中可以看出,程序既无法向下执行,也不会抛出任何异常,就一直“僵持”着。究其原因,是因为:上面程序中A对象和B对象的方法都是同步方法,也就是A对象和B对象都是同步锁。程序中两个线程执行,副线程的线程执行体是 DeadLock类的run()方法,主线程的线程执行体是 Deadlock的main()方法(主线程调用了init()方法)。其中run()方法中让B对象调用b进入foo()方法之前,该线程对A对象加锁—当程序执行到①号代码时,主线程暂停200ms:CPU切换到执行另一个线程,让B对象执行bar()方法,所以看到副线程开始执行B实例的bar()方法,进入bar()方法之前,该线程对B对象加锁——当程序执行到②号代码时,副线程也暂停200ms:接下来主线程会先醒过来,继续向下执行,直到③号代码处希望调用B对象的last()方法——执行该方法之前必须先对B对象加锁,但此时副线程正保持着B对象的锁,所以主线程阻塞;接下来副线程应该也醒过来了,继续向下执行,直到④号代码处希望调用A对象的 last()方法——执行该方法之前必须先对A对象加锁,但此时主线程没有释放对A对象的锁——至此,就出现了主线程保持着A对象的锁,等待对B对象加锁,而副线程保持着B对象的锁,等待对A对象加锁,两个线程互相等待对方先释放,所以就出现了死锁。

六、线程池

系统启动一个新线程的成本是比较高的,因为它涉及与操作系统交互。在这种情形下,使用线程池可以很好地提高性能,尤其是当程序中需要创建大量生存期很短暂的线程时,更应该考虑使用线程池。

与数据库连接池类似的是,线程池在系统启动时即创建大量空闲的线程,程序将一个 Runnable对象或 Callable对象传给线程池,线程池就会启动一个空闲的线程来执行它们的run()或call()方法,当run()或call()方法执行结束后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个Runnable对象的run()或call()方法。

创建线程池的几个常用的方法:

1.newSingleThreadExecutor创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

2.newFixedThreadPool创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

3.newCachedThreadPool创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,

那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

4.newScheduledThreadPool创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
public class ThreadPoolTest {
     
    public static void main(String[] args) {
        ExecutorService pool=Executors.newFixedThreadPool(6);
        Runnable target=()->{
            for(int i=0;i<10;i++) {
                System.out.println(Thread.currentThread().getName()+"的i的值"+i);
            }
        };
        pool.submit(target);
        pool.submit(target);
        pool.submit(target);
        //关闭线程池
        pool.shutdown();
         
    }
 
}

运行结果:

pool-1-thread-1的i的值0
pool-1-thread-2的i的值0
pool-1-thread-3的i的值0
pool-1-thread-2的i的值1
pool-1-thread-1的i的值1
pool-1-thread-2的i的值2
pool-1-thread-3的i的值1
pool-1-thread-2的i的值3
pool-1-thread-1的i的值2
pool-1-thread-2的i的值4
pool-1-thread-3的i的值2
pool-1-thread-2的i的值5
pool-1-thread-1的i的值3
pool-1-thread-2的i的值6
pool-1-thread-3的i的值3
pool-1-thread-2的i的值7
pool-1-thread-1的i的值4
pool-1-thread-2的i的值8
pool-1-thread-3的i的值4
pool-1-thread-2的i的值9
pool-1-thread-1的i的值5
pool-1-thread-3的i的值5
pool-1-thread-1的i的值6
pool-1-thread-1的i的值7
pool-1-thread-1的i的值8
pool-1-thread-1的i的值9
pool-1-thread-3的i的值6
pool-1-thread-3的i的值7
pool-1-thread-3的i的值8
pool-1-thread-3的i的值9

后记

怎么样?来自清华扫地僧的功力如此深厚,想了解是哪位吗?那就请多多留言评论,或者关注小编,私信回复【学习】,即可免费获赠清华大佬海量学习资料。

心动就赶紧行动起来吧~~~

IC芯片验证 - 手把手教你搭建UVM验证环境 这是一个UVM的demo项目 阅读详情

相关推荐

解决com.kingbase8.util.KSQLException: This _connection has been closed.

一个消息管理系统,系统采用kingbase8数据库,数据库采用单体模式,后台应用也采用springboot单体模式。系统正式上线后,出现几个JDBC响应的异常信息: com.kingbase8.util.KSQLException: An I/O error occurred while sending to the backend. java.net.SocketTimeoutException: Read timed out.

qq_38127559的博客 2759

Java 面试题 synchronized 和Lock区别??

synchronized 和Lock区别? 来源及用法: lock是一个接口,是java写的控制锁的代码,而synchronized是java的一个内置关键字,synchronized是托管给JVM执行的; synchronized:在需要同步的对象中加入此控制,synchronized可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象。 lock:一般使用ReentrantLock类做为锁。在加锁和解锁处需要通过lock()和unlock()显示指出。所以一般会在finally块中写un

芒果冰沙的博客 625

rv1126 读取sensor输入,输出到屏幕显示【RK_MPI API接口】

rv1126 libdrm VO

学习记录 1万+

多线程认知

界面假死或者任务量大时  内存错误,注意内存的释放 还是引用一下别人的心得  进程与线程 进程和线程是两个相对的概念,通常来说,一个进程可以定义程序的一个实例(Instance)。在Win32中,进程并不执行什么,它只是占据应用程序所使用的地址空间。为了让进程完成一定的工作,进程必须至少占有一个线程,正是这个线程负责包含进程地址空间中的代码。实际上,一个

nailwl 541

ARM架构下Horizon Client安装全攻略:从依赖检查到环境变量配置

本文详细介绍了在ARM架构设备上安装VMware Horizon Client的全过程,包括依赖检查、软件包获取、系统部署和环境变量配置。特别针对华为鲲鹏等ARM平台,提供了从基础准备到性能优化的完整指南,帮助用户高效完成虚拟桌面客户端的部署与调优。

github5actions的博客 556

RK3588工业开发板实战:如何用IgH EtherCAT主站精准控制伺服电机(附完整代码)

本文详细介绍了如何利用RK3588工业开发板和IgH EtherCAT主站实现伺服电机的精准控制。从硬件选型、实时内核配置到EtherCAT主站安装和核心代码实现,提供了一套完整的工业自动化解决方案,特别适合需要高精度运动控制的场景。

pca5navigator的博客 972

Linux 如何查看一个进程的堆栈

有两种方法:第一种:$pstack   进程ID 或者  $gstack   进程ID第二种:先执行程序,然后 $gdb attach进程ID,再执行$thread    apply all bt 第三种:strace -f -p pid  该方法和pstack类似第四中:gcore pid ,输出core文件,gdb cmd corefile两种方法都可以列出进程所有的线程的当前的调用栈。不过,...

风清扬 2272

5分钟搞定!用L型匹配电路优化窄带天线性能(附Smith圆图实操)

本文针对窄带天线阻抗匹配难题,提供了一套5分钟快速解决方案。通过详解L型匹配电路的核心原理,并结合Smith圆图这一射频工程师的“瑞士军刀”,手把手指导如何选择拓扑、计算元件值并进行实际调试,旨在帮助硬件工程师高效优化天线性能,实现最大功率传输。

yyy34的博客 447

厚积薄发打卡Day25 :狂神说Java之多线程详解<全网最全(代码+笔记)>

厚积薄发打卡Day25 :狂神说Java之多线程详解(全网最全_代码+笔记) 概述 课程大纲: 线程简介 线程实现(重点) 线程状态 线程同步(重点) 线程通信问题 高级主题 线程、进程、多线程 线程简介: 多任务: 现实中太多这样同时做多件事情的例子了,看起来是多个任务都在做,其实本质上我们的大脑在同一时间依旧只做了一件事情。 多线程: 原来是一条路,慢慢因为车太多了,道路堵塞,效率极低。为了提高使用的效率,能够充分利用道路,于是加了多个车道。从此,妈妈再也不用担心道路堵塞了。 程序 -&

COOLGWAYNE_TECH_BLOG 1028

提交 ieee 手稿出现:Do not include the Reviewer PDF with my submission 怎么办

IEEE投稿Reviewer PDF选项说明 投稿时出现的"Do not include the Reviewer PDF with my submission"选项控制是否附带评审专用PDF文件。Reviewer PDF是系统自动生成包含所有投稿材料的完整PDF,用于同行评审。建议保持默认不勾选状态,除非确认编辑部不需要该文件,否则可能影响审稿流程。勾选表示不附带此PDF,不勾选则会正常生成并提交该评审文件。

ZhangJiqun&191cm&85kg 2224

C#量化交易的市场数据采集与分析:从0到1构建你的‘数据雷达’——对比5大神器,代码实战全解析!

数据是量化交易的‘氧气’,而好的数据采集与分析是你的‘呼吸机’!本文5大神器+代码实战,总有一款适合你!(本文代码均经过测试,但请根据实际需求调整!

java专栏 734

清华扫地僧整理的全网最全多线程详解,看完怀疑自己的认知(1)

互联网大厂比较喜欢的人才特点:对技术有热情,强硬的技术基础实力;主动,善于团队协作,善于总结思考。无论是哪家公司,都很重视高并发高可用技术,重视基础,所以千万别小看任何知识。面试是一个双向选择的过程,不要抱着畏惧的心态去面试,不利于自己的发挥。同时看中的应该不止薪资,还要看你是不是真的喜欢这家公司,是不是能真的得到锻炼。其实我写了这么多,只是我自己的总结,并不一定适用于所有人,相信经过一些面试,大家都会有这些感触。下面有部分截图希望能对大家有所帮助。

2401_83916241的博客 814

OpenClaw 自定义 Skill 开发完整指南(最新版)

OpenClaw 中的每个 Skill,本质是一套“能力描述 + 执行逻辑”的组合包,核心特征如下:核心载体:以文件夹为单位,文件夹内必须包含SKILL.md文件(能力描述核心);扩展文件:可选agent.py(Python 执行逻辑)、index.ts(TypeScript 执行逻辑)、(辅助文档/示例资源)等;核心优势:AI 通过阅读 SKILL.md 的自然语言描述理解技能,无需严格遵循代码接口,灵活度极高;

haolove527的专栏 2万+

openoffice转化太慢且不能多线程_清华扫地僧整理的全网最全多线程详解,看完怀疑自己的认知,...

前言现在越来越多的公司,对精通多线程的的人才越来越重视,可见多线程技术有多热门。今天,小编结合清华扫地僧级别大佬的分享,为大家带来这篇多线程的总结,希望大家能够喜欢。一、线程概述几乎所有的操作系统都支持同时运行多个任务,一个任务通常就是一个程序,每个运行中的程序就是一个进程。当一个程序运行时,内部可能包含了多个顺序执行流,每个顺序执行流就是一个线程。二、线程与进程进程概述:几乎所有的操作系统都支持...

weixin_39599654的博客 786

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

gdb命令记录

【声明】声明本文部分材料来源网络,如有疑问请联系。 1. 调试可执行程序加载的共享库的方法是 $gdb –q &lt;execute filename&gt; $r &lt;cmd line para&gt; 2.gdb 调试core dump文件 启动gdb $gdb &lt;可执行文件名的全路径&gt; 3. gcc开了-O优化选项 所有static函数、...

hengshan的专栏 1613

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

【完全多线程的输入通道】MNIST图像分类

完全多线程的输入通道应用——MNIST手写数字图像分类 文章目录完全多线程的输入通道应用——MNIST手写数字图像分类  简介  项目设计  完整代码   简介        在本节我们将所有要介绍的内容放在一个 MNIST 图像的应用示例中,从数据写入到 TensorFlow 高效的文件格式,通过数据加载和预处理,到模型的训练。我们都用队列和多线程功能来实现这一点,并且依照这样的方式引入了一些更有用的组件来读取和处理 Tens

求求你们别卷了 200

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

JAVA多线程————一篇文章让你彻底征服多线程开发(一)

线程生命周期 线程的调度与控制 synchronized 死锁 守护线程 Timerschedule线程生命周期线程是一个进程中的执行场景,一个进程可以启动多个线程 多线程不是为了提高执行速度,而是提高应用程序的使用率.线程和线程共享”堆内存和方法区内存”.栈内存是独立的,一个线程一个栈.可以给现实世界中的人类一种错觉 : 感觉多线程在同时并发执行. 很多人都对其中的一些概念不够明确,如同步、

姚佳伟 3万+

gdb thread apply all

gdb &gt; thread apply all bt   不需一个线程一个线程切换了。

kernel121的专栏 3048

多线程死锁调试小技巧

  据说再高的高手在写多线程程序的时候都难确保不会产生死锁,死锁的调试也就成为一个比较常见的问题,假设有下面这样一个问题:   一个正在生产环境下运行的进程死锁了,或者你只是在跑一个程序,并没有在调试器里面打开它,然后发现没有响应,日志输出也停止了。由于你是一个有经验的程序员,会想到“我刚刚加上了新的锁策略,不一定稳定,这可能是死锁了“。但是你不想就这么杀掉进程,因为多线程的 bug 不容易重现...

weixin_33725270的博客 446

java 多线程(完全版)

尽管线程对象的常用方法可以通过API文档来了解,但是有很多方法仅仅从API说明是无法详细了解的。我们先来说一下线程对象的几个重要的方法:     首先我们来说明start() 方法。     一个线程对象生成后,如果要产生一个执行的线程,就一定要调用它的start()方法.在介绍这个方法时不得不同时说明run方法.其实线程对 象的run方法完全是一个接口回调方法,它是你这个线程对象要...

a13969726756的专栏 141

牛皮!阿里资深架构师耗费三年终于把Netty进阶之路PDF整理完了,读完我彻底跪了

前言 Netty将Java NIO接口封装,提供了全异步编程方式,是各大Java项目的网络应用开发必备神器。 在本文中,将Netty学习者咨询的相关问题,进行了归纳和总结,以问题案例做牵引,通过对案例进行剖析,讲解问题背后的原理,并结合Netty源码分析,让读者能够真正掌握Netty,在实际工作中少犯错。 本文中的案例涵盖了Netty 的启动和停止、内存、并发多线程、性能、可靠性、安全等方面,囊括了Netty绝大多数常用的功能及容易让人犯错的地方。在案例的分析过程中,还穿插讲解了Netty的问题定位思

python6_quanzhan的博客 2682

自从外包干了四年,基本废了...

先说一下自己的情况。大专生,18年通过校招进入湖南某软件公司,干了接近4年的CRUD,今年年上旬,感觉自己不能够在这样下去了,长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的CRUD,已经让我变得不思进取,谈了2年的女朋友也因为我的心态和工资和我分手了。于是,我决定要改变现状,开发卷,那就变成卷王!刚开始准备时,自己都蒙了,虽然我有编程基础,但不知道从哪里学起啊。没办法,我找到了我在腾讯的老哥,作为他的小老表,在他了解了我的情况后,直接甩给我一个网盘,说到。...

python6_quanzhan的博客 1133

2022秋招,Java岗最全面试攻略,吃透25个技术栈Offer拿到手软

我分享的这份春招 Java 后端开发面试总结包含了 JavaOOP、Java 集合容器、Java 异常、并发编程、Java 反射、Java 序列化、JVM、Redis、Spring MVC、MyBatis、MySQL 数据库、消息中间件 MQ、Dubbo、Linux、ZooKeeper、 分布式 &数据结构与算法等 25 个专题技术点,都是小编在各个大厂总结出来的面试真题,已经有很多粉丝靠这份 PDF 拿下众多大厂的 offer,今天在这里总结分享给到大家!我这篇分享篇幅可能有点长,观看的朋友可以先了解一下

python6_quanzhan的博客 1129

10年开发大佬,用300案例,附学习路线,详解多线程编程核心技术

Java多线程无处不在,如服务器、数据库、应用。多线程可以有效提升计算和处理效率,大大提升吞吐量和可伸缩性,深得广大程序员和公司的青睐。很多人学习完JavaSE/JavaEE之后想往更深入的技术进行探索,比如对大数据、分布式,高井发类的专题进行攻克时,立即遇到针对java lang包中线程类的学习,但线程类的学习并不像JDBC一样简单,学习曲线陡峭,多弯路与“坑”。要学习这些热点技术, Java多线程技术避无可避。 多线程与高并发学习路线 有了学习路线,接下来咱们就要实战学习了。小编特意为大家.

python6_quanzhan的博客 1024

二本4年Java经验,五面字节(定薪45K)

前段时间刚面试上岸,先后面试了各大小公司,拿了一些 offer,有阿里,滴滴,快手,达达,得物等公司。面试的公司大部分都能过,所以这里给大家分享下自己的经验,也给自己做个归档,希望能给正在准备跳槽,找工作的朋友一点帮助,这里主要分享下字节面试的经历!时间不长,大概二十分钟,面试官人不错。一面结果通过。过了一个星期,由于当地有阿里的子公司,就去了子公司的会议室进行视频面试。二面看上去是个组长的人,年纪并不轻。同样的自我介绍开始,然后聊来深圳发展的意愿,个人的情况,然后问了一下做的项目,就开始问问题了。二面结果

python6_quanzhan的博客 736
上一篇: 面试官:谈关于缓存穿透+击穿+雪崩,以及热点数据失效问题的解决方案
下一篇: 全网最全!高并发分布式锁架构解密,带给你独一无二的认知体验
Java领域指导者
博客等级 码龄7年 404粉丝 265原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值