【JAVA并发编程-黑马】第一章

2023-11-02


一、创建线程的几种方式


二、查看进程的方法


三、线程运行原理–栈桢Debug


栈与栈帧

Java Virtual Machine Stacks (Java 虚拟机栈)

我们都知道 JVM 中由堆、栈、方法区所组成,其中栈内存是给谁用的呢?其实就是线程,每个线程启动后,虚拟机就会为其分配一块栈内存。

  • 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
  • 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
  • 栈桢以线程为单位,相互之间是独立的
public class Test {
    public static void main(String[] args) {
        method1(10);
    }

    public static void method1(int x){
        int y = x + 1;
        Object o = method2();
        System.out.println(o);

    }

    public static Object method2(){
        Object o = new Object();
        return o;
    }
}

下图,走到断点时,产生了一个main栈桢,栈桢里面有一个局部变量:
在这里插入图片描述
方法走到下图标记的位置的时候,有两个栈桢,method1栈桢是新加入的,也有局部变量:
在这里插入图片描述
走到下图标记的位置时,添加了method2栈桢,有三个栈桢:
在这里插入图片描述
走到下图标记的时候,只有两个栈在桢了,因为走完method2,method2栈桢释放了,同时,也说明一个问题,栈是后进先出的:
在这里插入图片描述
debug到这里已经将问题说明白了,就不再继续了.


四、线程运行原理图解


4.1 类加载


加载字节码文件,将字节码文件加载到方法区的内存中,这里为了好理解,就没有写二进制的代码了,写的是java代码.
在这里插入图片描述


4.2 启动main线程


类加载完成后,JVM会启动main线程,并且分配一块栈内存给它。接下来这个线程就交给了任务调度器去调度执行,如果抢到CPU了,main方法是方法的执行入口,会给main方法分配一个栈桢内存.
在这里插入图片描述
栈内存中有局部变量表、返回地址、锁记录、操作数栈。main栈桢的局部变量表是args,返回地址是程序的退出地址。
程序计数器:记录下一次该执行什么命令,例如,记录了下一个执行的方法method1(10)

继续执行:
在这里插入图片描述
在这里插入图片描述
现在methd2方法被执行完了,需要释放掉内存:
在这里插入图片描述
然后method1执行结束释放内存,main执行完成,释放内存。


五、线程上下文切换(Thread Context Switch)


因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程的代码

  • 线程的 cpu 时间片用完
  • 垃圾回收
  • 有更高优先级的线程需要运行
  • 线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法
    当 Context Switch 发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念就是程序计数器(Program Counter Register),它的作用是记住下一条 jvm 指令的执行地址,是线程私有的
  • 状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等
  • Context Switch 频繁发生会影响性能

六、常用方法


6.1 run和start


  • 直接调用 run 是在主线程中执行了 run,没有启动新的线程
  • 使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码

6.2 sleep和yield


sleep

  1. 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
  2. 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
  3. 睡眠结束后的线程未必会立刻得到执行
  4. 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性

yield

  1. 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程
  2. 具体的实现依赖于操作系统的任务调度器

对比
1.就绪状态,还是有机会被任务调度器调用的,但是阻塞状态,任务调度器是不会分配时间片给这种状态的线程的
2.sleep是有具体的等待时间可设置的,而yield几乎是没有等待时间。

sleep打断

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                System.out.println("enter sleep...");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    System.out.println("wake up...");
                    e.printStackTrace();
                }
            }
        };
        t1.start();

        Thread.sleep(1000);
        System.out.println("interrupt...");
        t1.interrupt();
    }

当然,推荐使用这样的方式进行睡眠,代码可读性更好

TimeUnit.SECONDS.sleep(2);

执行结果
在这里插入图片描述


6.3 线程优先级

  • 线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它
  • 如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用

6.4 sleep方法的一个应用


        while(true) {
            try {
                //Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

上面代码在单核CPU下运行,CPU会占用到100%,如果将注释的代码放开,即加上sleep方法,CPU只有3%左右。找一台单核的linux虚拟机,使用top命令查看。

6.5 join方法

join方法:等待线程结束,谁来调用这个方法,就等待谁的线程结束。

    static int r = 0;
    public static void main(String[] args) throws InterruptedException {
        test1();
    }
    private static void test1() throws InterruptedException {
        System.out.println("开始");
        Thread t1 = new Thread(() -> {
            System.out.println("开始");
            TimeUnit.SECONDS.sleep(1);
            System.out.println("结束");
            r = 10;
        });
        t1.start();
        System.out.println("结果为:" + r);
        System.out.println("结束");
    }

执行结果:
在这里插入图片描述
如果我们希望结果是10呢?

    static int r = 0;
    public static void main(String[] args) throws InterruptedException {
        test1();
    }
    private static void test1() throws InterruptedException {
        System.out.println("开始");
        Thread t1 = new Thread(() -> {
            System.out.println("开始");
            TimeUnit.SECONDS.sleep(1);
            System.out.println("结束");
            r = 10;
        });
        t1.start();
        t1.join();
        System.out.println("结果为:" + r);
        System.out.println("结束");
    }

上面代码在start之后,添加了join方法,表示等t1线程结果返回,才能继续往下执行。体现了同步应用

6.6 join同步应用

加入两个依赖:

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.10</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>

在测试类上添加注解:

@Slf4j(topic = "c.Test")
    static int r = 0;
    static int r1 = 0;
    static int r2 = 0;
	private static void test2() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            r1 = 10;
        });
        Thread t2 = new Thread(() -> {
            try {
                sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            r2 = 20;
        });
        t1.start();
        t2.start();
        long start = System.currentTimeMillis();
        log.debug("join begin");
        t2.join();
        log.debug("t2 join end");
        t1.join();
        log.debug("t1 join end");
        long end = System.currentTimeMillis();
        log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
    }

执行结果:在这里插入图片描述
如果将上面的两个join方法调用位置,执行结果还是3ms。

6.7 join限时同步

    public static void test3() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            r1 = 10;
        });

        long start = System.currentTimeMillis();
        t1.start();

        // 线程执行结束会导致 join 结束
        log.debug("join begin");
        t1.join(1000);
        long end = System.currentTimeMillis();
        log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
    }

执行结果:
在这里插入图片描述
只在1003ms结束,所以r1的值还是0。
如果将t1.join(3000);
打印的结果为:
在这里插入图片描述
r1的值已经是10了,耗时2000ms,说明join中的参数时间,如果大于线程的执行时间,就以线程执行完毕为准,如果小于线程执行时间,就以设置的时间为准,所以是限时同步。

6.8 interrupt打断阻塞

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            log.debug("sleep...");
            try {
                Thread.sleep(5000); // wait, join
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t1");

        t1.start();
        Thread.sleep(1000);
        log.debug("interrupt");
        t1.interrupt();
        log.debug("打断标记:{}", t1.isInterrupted());
    }

执行结果:
在这里插入图片描述
如果在sleep时被打断,被标记为true,但是sleep方法会清除标记,导致标记为false

视频中说打断标记为false,但是这里的结果是true,此处存疑!

解惑
观察上面结果,打断标记的输出,在异常抛出之前就输出了

调试过程
首先,我在catch块中加入了System.out.println(Thread.currentThread().isInterrupted());发现打印的结果是false,说明打断标记确实为false,再结合上面输出结果,发现:打印语句其实在catch代码块执行之前执行了。所以,我们如果想要看到正确的结果,需要在打印语句之后休眠一段时间,完整代码如下:

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            log.debug("sleep...");
            try {
                Thread.sleep(5000); // wait, join
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println(Thread.currentThread().isInterrupted());
            }
        },"t1");

        t1.start();
        Thread.sleep(1000);
        log.debug("interrupt");
        t1.interrupt();
        Thread.sleep(1000);
        log.debug("打断标记:{}", t1.isInterrupted());
    }

执行结果如下:
在这里插入图片描述

6.9 interrupt打断正常运行的线程

        Thread t1 = new Thread(() -> {
            while(true) {
                boolean interrupted = Thread.currentThread().isInterrupted();
                if(interrupted) {
                    log.debug("被打断了, 退出循环");
                    break;
                }
            }
        }, "t1");
        t1.start();

        Thread.sleep(1000);
        log.debug("interrupt");
        t1.interrupt();

执行结果:
在这里插入图片描述
使用这种方式可以优雅的终止一个线程,并不是立刻将线程杀死,而是给了线程一个料理后事的机会。

6.10 线程设计模式之两段终止模式

在一个线程 T1 中如何“优雅”终止线程 T2?这里的【优雅】指的是给 T2 一个料理后事的机会。

错误思路

使用线程对象的 stop() 方法停止线程

  • stop 方法会真正杀死线程,如果这时线程锁住了共享资源,那么当它被杀死后就再也没有机会释放锁,其它线程将永远无法获取锁
  • 使用 System.exit(int) 方法停止线程目的仅是停止一个线程,但这种做法会让整个程序都停止

两阶段终止模式
在这里插入图片描述

@Slf4j(topic = "c.Test")
public class Test {
    public static void main(String[] args) throws InterruptedException {
        TwoPhaseTermination tpt = new TwoPhaseTermination();
        tpt.start();
        Thread.sleep(3500);
        tpt.stop();
    }
}

@Slf4j(topic = "c.Test")
class TwoPhaseTermination {
    // 监控线程
    private Thread monitorThread;
    // 停止标记
    private volatile boolean stop = false;
    // 判断是否执行过 start 方法
    private boolean starting = false;

    // 启动监控线程
    public void start() {
        synchronized (this) {
            if (starting) { // false
                return;
            }
            starting = true;
        }
        monitorThread = new Thread(() -> {
            while (true) {
                Thread current = Thread.currentThread();
                // 是否被打断
                if (current.isInterrupted()) {
                    log.debug("料理后事");
                    break;
                }
                try {
                    Thread.sleep(1000);
                    log.debug("执行监控记录");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    current.interrupt();//再次调用,将false变为true,执行下一次循环时,发现标记是true,执行料理后事的代码
                }
            }
        }, "monitor");
        monitorThread.start();
    }

    // 停止监控线程
    public void stop() {
        stop = true;
        monitorThread.interrupt();
        System.out.println(Thread.currentThread().isInterrupted());
    }
}

执行结果:
在这里插入图片描述
如果在sleep时被打断,被标记为true,但是sleep方法会清除标记,导致标记为false,会抛出异常,进入catch代码,执行catch代码后,标记会记为true。
如果在执行监控记录时被打断,不会抛出代码,打断标记被记为true。

6.11 静态的Thread.interrupted()

  • Thread.interrupted();也是判断线程是否被打断,但是它会清除打断标记
  • isInterrupted方法判断线程是否被打断,但是它不会清除打断标记

6.12 interrupt打断park

打断标记为true的情况下,park会失效。

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            log.debug("park...");
            LockSupport.park();
            log.debug("unpark...");
            log.debug("打断状态:{}", Thread.currentThread().isInterrupted());
        }, "t1");
        t1.start();
        sleep(1);
        t1.interrupt();

    }

执行结果:
在这里插入图片描述
上面的结果可见:输出park后,由于调用了park方法,暂停了1s,后来执行了t1.interrupt();打断标记变为true,导致了park失效,继续执行后面的代码。

当然,如果再是打断标记为false,park方法立即会生效。
例如将Thread.currentThread().isInterrupted()变为Thread.currentThread().interrupt()

6.13 过时的方法

  • stop() 停止线程运行
  • suspend() 挂起(暂停)线程运行
  • resume() 恢复线程运行

6.14 守护线程

只要有一个线程运行,整个JAVA进程都不会结束

有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    break;
                }
            }
            log.debug("结束");
        }, "t1");
        t1.setDaemon(true);
        t1.start();

        Thread.sleep(1000);
        log.debug("结束");
    }

结果:
在这里插入图片描述
主线程结束了,即使t1线程没有执行完,也会被结束。

  • 垃圾回收器线程就是一种守护线程,如果程序停止了,垃圾回收线程也会被强制停止
  • Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等待它们处理完当前请求

6.15 线程的五种状态

这是从 操作系统 层面来描述的
在这里插入图片描述

  • 【初始状态】仅是在语言层面创建了线程对象,还未与操作系统线程关联
  • 【可运行状态】(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执行
  • 【运行状态】指获取了 CPU 时间片运行中的状态
    当 CPU 时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换
  • 【阻塞状态】如果调用了阻塞 API,如 BIO 读写文件,这时该线程实际不会用到 CPU,会导致线程上下文切换,进入【阻塞状态】等 BIO 操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】。与【可运行状态】的区别是,对【阻塞状态】的线程来说只要它们一直不唤醒,调度器就一直不会考虑
    调度它们
  • 【终止状态】表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态

6.16 六种状态

这是从 Java API 层面来描述的
根据 Thread.State 枚举,分为六种状态
在这里插入图片描述

  • NEW 线程刚被创建,但是还没有调用 start() 方法,五种状态的划分是重叠的。
  • RUNNABLE 当调用了 start() 方法之后,注意,Java API 层面的 RUNNABLE 状态涵盖了 操作系统 层面的
    【可运行状态】、【运行状态】和【阻塞状态】(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为是可运行)
  • BLOCKED , WAITING , TIMED_WAITING 都是 Java API 层面对【阻塞状态】的细分,后面会在状态转换一节详述
  • TERMINATED 当线程代码运行结束

6.17 六种状态的演示

public static void main(String[] args) throws IOException {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                log.debug("running...");
            }
        };

        Thread t2 = new Thread("t2") {
            @Override
            public void run() {
                while(true) { // runnable 既有可能分到时间片,又可能没有分到,都是runable状态

                }
            }
        };
        t2.start();

        Thread t3 = new Thread("t3") {
            @Override
            public void run() {
                log.debug("running...");
            }
        };
        t3.start();

        Thread t4 = new Thread("t4") {
            @Override
            public void run() {
                synchronized (Test.class) {
                    try {
                        Thread.sleep(1000000); // timed_waiting
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t4.start();

        Thread t5 = new Thread("t5") {
            @Override
            public void run() {
                try {
                    t2.join(); // waiting  等待t2线程执行完成
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        t5.start();

        Thread t6 = new Thread("t6") {
            @Override
            public void run() {
                synchronized (Test.class) { // blocked 由于t4线程获得了锁,没有释放,导致t6一直获取不到锁
                    try {
                        Thread.sleep(1000000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t6.start();

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("t1 state {}", t1.getState());
        log.debug("t2 state {}", t2.getState());
        log.debug("t3 state {}", t3.getState());
        log.debug("t4 state {}", t4.getState());
        log.debug("t5 state {}", t5.getState());
        log.debug("t6 state {}", t6.getState());
        System.in.read();
    }

执行结果:
在这里插入图片描述

6.18 临界区与竞态条件

一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区
例如,下面代码中的临界区

static int counter = 0;
static void increment()
// 临界区
{
	counter++;
}
static void decrement()
// 临界区
{
	counter--;
}

竞态条件 Race Condition
多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件。

七、线程安全问题分析

使用全局变量list:

public class Test {
    static final int THREAD_NUMBER = 2;
    static final int LOOP_NUMBER = 200;
    public static void main(String[] args) {
        ThreadUnsafe test = new ThreadUnsafe();
        for (int i = 0; i < THREAD_NUMBER; i++) {
            new Thread(() -> {
                test.method1(LOOP_NUMBER);
            }, "Thread" + (i+1)).start();
        }
    }
}
class ThreadUnsafe {
    ArrayList<String> list = new ArrayList<>();
    public void method1(int loopNumber) {
        for (int i = 0; i < loopNumber; i++) {
            method2();
            method3();
        }
    }

    private void method2() {
        list.add("1");
    }

    private void method3() {
        list.remove(0);
    }
}

执行结果:
在这里插入图片描述
使用局部变量list:

public class Test {
    static final int THREAD_NUMBER = 2;
    static final int LOOP_NUMBER = 200;
    public static void main(String[] args) {
        ThreadSafe test = new ThreadSafe();
        for (int i = 0; i < THREAD_NUMBER; i++) {
            new Thread(() -> {
                test.method1(LOOP_NUMBER);
            }, "Thread" + (i+1)).start();
        }
    }
}

class ThreadSafe {
    public final void method1(int loopNumber) {
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i < loopNumber; i++) {
            method2(list);
            method3(list);
        }
    }

    public void method2(ArrayList<String> list) {
        list.add("1");
    }

    private void method3(ArrayList<String> list) {
        System.out.println(1);
        list.remove(0);
    }
}

这个是线程安全的,没有报错。

下面这个例子同样是使用局部变量,但是method方法是public的,被继承重写了:

public class Test {
    static final int THREAD_NUMBER = 2;
    static final int LOOP_NUMBER = 200;
    public static void main(String[] args) {
        ThreadSafeSubClass test = new ThreadSafeSubClass();
        for (int i = 0; i < THREAD_NUMBER; i++) {
            new Thread(() -> {
                test.method1(LOOP_NUMBER);
            }, "Thread" + (i+1)).start();
        }
    }
}

class ThreadSafe {
    public final void method1(int loopNumber) {
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i < loopNumber; i++) {
            method2(list);
            method3(list);
        }
    }

    public void method2(ArrayList<String> list) {
        list.add("1");
    }

    public void method3(ArrayList<String> list) {
        System.out.println(1);
        list.remove(0);
    }
}

class ThreadSafeSubClass extends ThreadSafe{
    @Override
    public void method3(ArrayList<String> list) {
        System.out.println(2);
        new Thread(() -> {
            list.remove(0);
        }).start();
    }
}

执行结果:
在这里插入图片描述
因为子类重新开启了一个线程,和之前的线程共享list,导致了线程安全问题,所以最好就是将method3方法变成私有的,不让子类重写。

常见的线程安全类

String
Integer
StringBuffer
Random
Vector
Hashtable
java.util.concurrent 包下的类

案例分析:

public class MyServlet extends HttpServlet {
	// 是否安全?
	Map<String,Object> map = new HashMap<>();  //no
	// 是否安全?
	String S1 = "..."; //yes
	// 是否安全?
	final String S2 = "..."; //yes
	// 是否安全?
	Date D1 = new Date(); //no
	// 是否安全?
	final Date D2 = new Date(); //no
	public void doGet(HttpServletRequest request, HttpServletResponse response) {
	// 使用上述变量
	}
}

servlet是运行在tomcat上的一个实例,是单实例的,被tomcat多个线程共享使用。

public class MyServlet extends HttpServlet {
	// 是否安全?
	private UserService userService = new UserServiceImpl(); //no
	public void doGet(HttpServletRequest request, HttpServletResponse response) {
		userService.update(...);
	}
}
	public class UserServiceImpl implements UserService {
	// 记录调用次数
	private int count = 0;  //no
	public void update() {
	// ...
	count++;
	}
}
@Aspect
@Component
public class MyAspect {
	// 是否安全?
	private long start = 0L; //no,MyAspect单例,多个线程可能共享这个变量
	@Before("execution(* *(..))")
	public void before() {
		start = System.nanoTime();
	}
	@After("execution(* *(..))")
	public void after() {
		long end = System.nanoTime();
		System.out.println("cost time:" + (end-start));
	}
}

上例最好用环绕通知,做成局部变量。

public class MyServlet extends HttpServlet {
    // 是否安全
    private UserService userService = new UserServiceImpl(); //yes,不可变,没有提供修改
    public void doGet(HttpServletRequest request, HttpServletResponse response) {
        userService.update(...);
    }
}
public class UserServiceImpl implements UserService {
    // 是否安全
    private UserDao userDao = new UserDaoImpl();//yes,虽然是成员变量,但是没提供修改
    public void update() {
        userDao.update();
    }
}
public class UserDaoImpl implements UserDao {
    public void update() {
        String sql = "update user set password = ? where username = ?";
        // 是否安全
        try (Connection conn = DriverManager.getConnection("","","")){ //yes
        // ...
        } catch (Exception e) {
        // ...
        }
    }
}
public abstract class Test {
	public void bar() {
	// 是否安全
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		foo(sdf);	
	}
	public abstract foo(SimpleDateFormat sdf);
	public static void main(String[] args) {
		new Test().bar();
	}
}

其中 foo 的行为是不确定的,可能导致不安全的发生,被称之为外星方法

public void foo(SimpleDateFormat sdf) {
	String dateStr = "1999-10-11 00:00:00";
	for (int i = 0; i < 20; i++) {
		new Thread(() -> {
		try {
			sdf.parse(dateStr);
		} catch (ParseException e) {
			e.printStackTrace();
		}
		}).start();
	}
}

上例泄露引用。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【JAVA并发编程-黑马】第一章 的相关文章

  • 并发编程系列之Fork/Join

    前言 上节我们讲了阻塞队列 Java中的并发容器就算有了个基本的认识 今天我们来介绍一种线程工作模式 叫Fork Join 他是JDK7之后提供的一个并行执行框架 主要的思想我觉得是分而治之 将一个大的任务分成多个小的任务并行执行 然后等所
  • java并发总结

    一 并发基础 1 进程与线程 进程 程序由指令和数据组成 但这些指令要运行 数据要读写 就必须将指令加载至 CPU 数据加载至内存 在指令运行过程中还需要用到磁盘 网络等设备 进程就是用来加载指令 管理内存 管理 IO 的 当一个程序被运行
  • 【多线程】三种实现方案

    目录 1 多线程中的并发和并行概念 2 多线程中的进程和线程概念 3 多线程的实现方案 3 1 方式1 继承Thread类的方式进行实现 3 2 方式2 实现Runnable接口 3 3 方式3 Callble和Future 可以获取返回结
  • 源码分析【ReentrantLock】原理

    ReentrackLock底层原理 ReentrackLock介绍 非公平锁VS公平 非公平锁 公平锁 可打断VS不可打断 不可打断 默认 可打断模式 锁超时 条件变量 如何在synchronized和ReentrantLock之间进行选择
  • Java并发编程:Copy-On-Write机制详解

    前言 在多线程并发访问共享数据时 可能会出现并发问题导致程序崩溃 数据异常等情况 为了避免这些问题 Java中提供了多种并发控制方法 其中Copy On Write COW 机制就是一种常用的技术 本文将详细介绍COW机制的概念 如何保证线
  • 理解什么是 JMM

    理解什么是 JMM 本文已收录至 GitHub https github com yifanzheng java notes Java 虚拟机是一个完整的计算机的一个模型 因此这个模型自然也包含一个内存模型 Java 内存模型 也就是说 J
  • 场景题之最快返回结果

    场景题之最快返回结果 问题描述 输入中文 最快从百度翻译 谷歌翻译 有道翻译获取结果返回 代码实现 思路 采用CompletableFuture实现 多个CompletableFuture可以串行执行 也可以并行执行 其中anyOf 方法只
  • 面试官:说说CountDownLatch,CyclicBarrier,Semaphore的原理?

    CountDownLatch CountDownLatch适用于在多线程的场景需要等待所有子线程全部执行完毕之后再做操作的场景 举个例子 早上部门开会 有人在上厕所 这时候需要等待所有人从厕所回来之后才能开始会议 public class
  • Callable和Future原理解析

    首先进行分析前 我们需要了解到的概念 Callable是一个接口 是用于创建线程执行里面有一个call方法 用于线程执行的内容 由业务自己定义 Future也是一个接口 可以异步的通过get方法获取到call返回的内容 比较常见的使用场景
  • 生产者与消费者问题?

    生产者消费者模式是并发 多线程编程中经典的设计模式 简单来看 就是一个类负责生产 一个类负责消费 举例来说 一个变量 生产者不断增加这个变量 消费者不断减少这个变量 在互联网应用中 抢票机制就是应用了该模式 比如大麦网演唱会门票抢票 123
  • MPI与main()程序中的其他函数执行次数

    我原先以为只有在MPI代码区域 即MPI Init argc argv 到MPI Finalize 中的代码才会涉及到进程通信的问题 但实际上在MPI区域外的代码依然受到影响 执行的次数与开启的进程数有关 为此可以使用MPI 秩 rank
  • java中的异步处理和Feature接口(一)

    文章目录 背景介绍 Feature接口 Feature接口和Tread的区别 Feature接口示例 Feature接口的局限性 背景介绍 想象这样一个场景 你可能希望为你的法国客户提供指定主题的热点报道 为实现这一功能 你需要向 谷歌或者
  • 如何设计高性能的分布式锁

    什么是分布式锁 在 JVM 中 在多线程并发的情况下 我们可以使用同步锁或 Lock 锁 保证在同一时间内 只能有一个线程修改共享变量或执行代码块 但现在我们的服务都是基于分布式集群来实现部署的 对于一些共享资源 在分布式环境下使用 Jav
  • Sentinel客户端调用并发控制

    前言 当链路中某个应用出现不稳定 导致整个链路调用变慢 如果不加控制可能导致雪崩 这种情况如何处理呢 一 慢调用现象分析 在分布式链路中调用中 调用关系如下 methodA1与methodA2在同一个应用中 链路标号 调用链 链路1 met
  • Java并发编程实战——并发容器之ConcurrentHashMap(JDK 1.8版本)

    文章目录 ConcurrentHashmap简介 从关键属性及类上来看ConcurrentHashMap的结构 put 方法管中窥豹 CAS关键操作 ConcurrentHashmap简介 在使用HashMap时在多线程情况下扩容会出现CP
  • synchronized的作用和用法

    郁闷 参考 synchronized的作用和用法 Java中Synchronized的使用 文章目录 简单介绍 用法 实战实例 修饰代码块 修饰普通方法 修饰静态方法 简单介绍 synchronized关键字是用来控制线程同步的 就是在多线
  • ThreadPoolExecutor源码解析

    ThreadPoolExecutor源码解析 一 新建线程池的是构造方法 public ThreadPoolExecutor int corePoolSize int maximumPoolSize long keepAliveTime T
  • 并发编程系列之CountDownLatch对战Cyclicbarrier

    前言 前面我们介绍了并发容器和队列 今天我们来介绍几个非常有用的并发工具类 今天主要讲CountDownLatch和Cyclicbarrier这两个工具类 通过讲解并对比两个类的区别 OK 让我们开始今天的并发之旅吧 什么是CountDow
  • 进程、线程、管程、纤程、协程概念以及区别

    进程 进程是指在操作系统中能独立运行并作为资源分配的基本单位 由一组机器指令 数据和堆栈等组成的能独立运行的活动实体 进程在运行是需要一定的资源 如CPU 存储空间和I O设备等 进程是资源分配的基本单位 进程的调度涉及到的内容比较多 存储
  • 并发编程系列之自定义线程池

    前言 前面我们在讲并发工具类的时候 多次提到线程池 今天我们就来走进线程池的旅地 首先我们先不讲线程池框架Executors 我们今天先来介绍如何自己定义一个线程池 是不是已经迫不及待了 那么就让我们开启今天的旅途吧 什么是线程池 线程池可

随机推荐

  • 【开源】团队版 ChatGPT Web 应用,多用户,免登录

    Muchat 团队版 ChatGPT Web 应用 多用户 免登录 适合公司 组织或小团体内部使用 有人乍一看目录觉得只是发布了个 exe 但实际代码在各个子仓库中 本仓库只是一个入口 特性 无需登录 输入卡密即可使用 支持匿名试用 可设置
  • Unity中烘焙光照,同mesh同uv的模型烘焙光照时共用一张光照贴图

    我们再开发中 经常会遇到需要烘焙场景光照的情况 但是尴尬的是 一个场景中你好几个物体用的都是一个贴图 但是不同mesh 或者是相同mesh 你都会如下图 多出一张光照贴图 正常unity开发的解决办法是合并mesh 来降低光照贴图数量 但是
  • vnc 连接不上,出现Warning: zhouziqi:1 is taken because of /tmp/.X11-unix/X1

    楼主不知道怎么回事 突然就边不上VNC了 我就打算重新启动服务 发现服务启不来 我用 journalctl xe命令出现下面的东西 Apr 24 21 30 24 zhouziqi runuser 23390 pam unix runuse
  • ScriptManager的用法

    脚本管理控件 ScriptManger 是ASP NET AJAX中非常重要的控件 通过使用ScriptManger能够进行整个页面的局部更新的管理 ScriptManger用来处理页面上局部更新 同时生成相关的代理脚本以便能够通过Java
  • 怎么画因果图?因果图绘图步骤详解

    因果图设计法 因果图法是一种利用图解法分析输人的各种组合情况的测试方法 它考虑了输入条件的各种组合及输入条件之间的相互制约关系 并考虑输出情况 例如 某一软件要求输人地址 具体到市区 如 北京 昌平区 天津 南开区 其中第2个输人受到第1个
  • C++tuple快速而随意的数据结构

    初始化 lt 学号 姓名 年龄 gt tuple
  • 转:通过注册表查看操作系统默认编码的方法

    在 转 使用DOS命令chcp查看windows操作系统的默认编码以及编码和语言的对应关系 介绍了一种通过chcp查看windows操作系统默认编码的方法 同时还有一种通过注册表查看默认编码的方法 虽然是以没有dos命令简单 但也可以使用
  • mbedtls交换服务器证书,mbedtls

    mbedtls系列文章 Demo工程源码 https github com Mculover666 mbedtls study demo 本工程基于STM32L41RCT6开发板 包含了本系列文章中所编写的所有Demo 持续更新 文章目录
  • xgboost 调参经验

    本文介绍三部分内容 xgboost 基本方法和默认参数 实战经验中调参方法 基于实例具体分析 1 xgboost 基本方法和默认参数 在训练过程中主要用到两个方法 xgboost train 和xgboost cv xgboost trai
  • LambdaQueryWrapper用法简单介绍

    1 层级关系 2 LambdaQueryWrapper 与QueryWrapper查询类似 不过使用的是Lambda语法 举例如下 package com mszlu blog dao pojo import lombok Data Dat
  • pywintypes.com_error: (-2147221008, ‘CoInitialize has not been called.‘, None, None)

    在使用xlwings多线程时 出现这样的报错 导致Excel表无法打开 是多线程调用的问题 解决方法 import pythoncom 在这个线程的函数开始运行之前调用这句即可 def daily report pythoncom CoIn
  • C语言:选择+编程(每日一练Day16)

    目录 选择题 题一 题二 题三 题四 题五 编程题 题一 数对 思路一 题二 截取字符串 思路一 本人实力有限可能对一些地方解释和理解的不够清晰 可以自己尝试读代码 或者评论区指出错误 望海涵 感谢大佬们的一键三连 感谢大佬们的一键三连 感
  • 解决vscode空格间距过小 tab不是四格的问题

    打开顺序 1 文件 首选项 设置 2 在框框中输入font 如下图所示 3 在 Editor Font Family的框框中 注意 是把第一个逗号前面的东西换成Consolas 如下图所示 即可
  • Java基础-作用域

    基本概念 在Java当中 主要的变量就是属性 成员变量 和局部变量 java中作用域的分类 全局变量 也就是属性 作用域为整个类体 局部变量 除了属性之外的其他变量 作用域为定义的代码块当中 目录 局部变量 举例 结果 局部变量没有默认值
  • Scala安装

    1 需要到官方下载window版本的Scala2 12x 注意去官方第找到Download下载 2 直接下载 第二种下载方式打开技能大赛课程里面的资料 3 双击安装文件进行安装 在安装的过程需要注意安装目录不要空格 中文和特殊符号 建议在D
  • Docker安装和Portainer图形界面安装

    文章目录 1 概述 2 docker简介 2 1 架构 2 2 优点 2 3 docker和虚拟机比较 比如vmware 3 docker安装 centos环境 3 1 卸载旧版本 没有可以不执行 3 2 设置仓库 3 3 正式安装 3 4
  • 数组练习题2

    1 题目 打印杨辉三角的前n行 注 杨辉三角的本质是它的两条斜边都是1 其余数则等于它肩上的两个数之和 如 1 11 121 1331 14641 思路 计算杨辉三角的时候1 先定义第一列的值为1 2 定义对角线上的值为1 3 根据某项值
  • 卸载 kubernetes

    kubeadm reset f modprobe r ipip lsmod rm rf kube rm rf etc kubernetes rm rf etc systemd system kubelet service d rm rf e
  • linux:真机安装centos linux(突发事件:解决卡在安装界面){寻找镜像--u启制作--引导u盘--解决卡在安装界面--安装配置}

    首先准备一个8 16 g的u盘 格式为 fat32 寻找镜像 寻找可以上阿里云或者各大镜像网站以及官网寻找 我是在阿里云找的 阿里巴巴开源镜像站 OPSX镜像站 阿里云开发者社区 aliyun com 寻找你要找的版本 我这里就选7 9版本
  • 【JAVA并发编程-黑马】第一章

    文章目录 一 创建线程的几种方式 二 查看进程的方法 三 线程运行原理 栈桢Debug 四 线程运行原理图解 4 1 类加载 4 2 启动main线程 五 线程上下文切换 Thread Context Switch 六 常用方法 6 1 r