Java多线程进阶简单梳理

Java多线程进阶简单梳理

1、什么是进程?什么是线程?

进程就是一个应用程序;线程就是进程中的执行单元。一个进程可以启动多个线程。

2、运行Java应用程序时

启动JVM就是一个进程。JVM就会启动main(主线程),同时调用垃圾回收线程负责守护,回收垃圾。

所以Java应用程序里至少有两个线程并发,主线程和垃圾回收线程。

3、进程和线程有什么关系?

进程可以看作大公司,而线程可以看作公司里的员工。

进程与进程之间,内存独立不共享。

线程与线程之间,堆内存和方法区内存共享。栈内存独立。一个线程一个栈,栈内存与栈内存独立。

Java中的多线程机制,目的就是为了提高程序的处理效率。

线程之间宏观上同时进行,微观上依次进行。

4、run( )和start( )的区别

  • run( )

    public class TestThread01 {
        /**
         * 主线程线程体
         * @param args
         */
        public static void main(String[] args) {
            //创建线程对象
            Thread_01 t1 = new Thread_01();
            //调用run()方法只是单纯的调用Thread_01类里的run方法,并没有开辟新的栈内存
            t1.run();
            //run()方法里的代码执行完才会接着执行主线程剩余的代码
            //主线程打印
            for (int i = 0; i < 1000; i++) {
                System.out.println("main->"+i);
            }
        }
    }
    
    class Thread_01 extends Thread{
        /**
         * 分支线程线程体
         */
        @Override
        public void run() {
            for (int i = 0; i < 1000; i++) {
                System.out.println("Thread_01->"+i);
            }
        }
    }
    

    如果调用分支线程对象的run( )方法,就只是单纯地调用了Thread_01这个类里的run( )方法并没有开辟分支线程的栈空间,Java回严格按照代码行的顺序执行,所以它会执行完run( )方法,再执行主线程里接下来的方法。

    JVM内存图:

Java多线程进阶简单梳理_第1张图片

当执行到t1.run()的时候主线程里会开辟run方法栈帧执行run方法,等run方法执行完毕后才接着执行主线程里剩余的代码。

总结:所以执行run方法就是单纯的执行线程类里的run方法,并且先让run方法执行完了之后再执行主线程里的接下来的代码。在内存里也并没有开启新的分支线程栈

  • start( )

    public class TestThread01 {
        /**
         * 主线程线程体
         * @param args
         */
        public static void main(String[] args) {
            //创建线程对象
            Thread_01 t1 = new Thread_01();
            //调用start()方法就会开启分支线程栈同时立马执行分支线程中的run方法
            t1.start();
            //主线程打印
            for (int i = 0; i < 1000; i++) {
                System.out.println("main->"+i);
            }
        }
    }
    
    class Thread_01 extends Thread{
        /**
         * 分支线程线程体
         */
        @Override
        public void run() {
            for (int i = 0; i < 1000; i++) {
                System.out.println("Thread_01->"+i);
            }
        }
    }
    

    当创建线程对象后调用start()方法的时候就会开辟新的分支线程栈同时才会执行分支线程中的run方法。所以start方法就仅仅相当于开启线程的作用。开启成功后这行代码就结束了,主线程就会接着执行剩下的代码,这个时候分支线程和主线程是并发的。

    JVM内存图:

    Java多线程进阶简单梳理_第2张图片

    当t1执行start( )方法的时候,就会立马开辟一个分支线程t1的栈,然后分支线程立马执行run方法,同时start( )方法结束,执行接下的方法,所以此时主线程和分支线程是并发的。

    总结:所以执行start方法才是开辟了分支线程的栈,然后立马执行run方法。这才是并发。此时,主线程的main方法和分支线程的run方法是平级的。

5、使用继承Thread还是实现Runnable?

推荐使用实现Runnable的方法创建线程,因为Java支持单继承实现多接口,如果采用继承Thread的方法的话,那么他就不能再继承另外的方法了,实现Runnable接口则既可以继承另外的类,还可以实现另外的接口。

6、线程的生命周期

线程一共有5个生命周期:

  1. 新建状态:线程对象刚刚被创建的时候(刚被new出来)
  2. 就绪状态:(线程对象执行start( )方法后线程会进入就绪状态)此时线程又叫做可运行状态,拥有能够抢夺CPU时间片(执行权)的权利,如果抢到了CPU时间片,那么他就会执行run方法,执行run方法就标志着这个线程进入了运行状态。
  3. 运行状态:抢夺到了CPU时间片的线程就会执行run方法,执行run方法就标志着这个线程进入了运行状态,当占有的时间片结束之后,就会回到就绪状态继续抢夺时间片。下一次运行的时候就会接着上次的run方法运行。
  4. 阻塞状态:当线程运行过程中遇到阻塞事件,比如用户输入或者sleep就会进入阻塞状态,释放到之前抢夺的时间片回到就绪状态重新开始抢夺。
  5. 死亡状态:线程的run方法运行结束线程就会变成死亡状态。

状态图:

Java多线程进阶简单梳理_第3张图片

7、interrupt( )打断线程sleep

public class TestThread01 {
    /**
     * 主线程线程体
     * @param args
     */
    public static void main(String[] args) {
        Thread_01 t1 = new Thread_01();
        t1.start();
        //t1线程停止睡眠
        t1.interrupt();
    }
}

class Thread_01 extends Thread{
    /**
     * 分支线程线程体
     */
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "--->begin");
        try {
            Thread.sleep(100000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "--->end");
    }
}

通过执行需要醒来的线程的interrupt方法打断线程休眠。其实质是让正在休眠(正在执行sleep方法)的线程抛出异常,从而执行catch而跳出sleep方法。

8、常见线程调度模型

  1. 抢占式调度模型:(Java采用)

    线程优先级(权重)越高,抢到CPU时间片的概率就高一点。

  2. 均分式调度模型:

    平均分配CPU时间片,每个线程占有的CPU时间片长度一样。平均分配,线程之间都是平等的。

9、什么时候需要考虑线程安全问题?

  1. 多线程并发
  2. 线程共享某一公共资源
  3. 线程同时修改某一公共资源

10、怎么解决线程安全问题

采用**“线程同步机制”**:线程排队执行,不并发,会降低效率。用效率换取安全,安全第一。

11、同步编程模型和异步编程模型

  • 同步(Synchronous)编程模型:线程A和线程B依次排队执行。
  • 异步(Asynchronous)编程模型:线程A和线程B互相执行,互不干扰,并发。

12、synchronized同步代码块

synchronized(共享的对象){
    代码块
}

在Java中,任何对象都有一把“锁”,synchronized代码块就是某个线程执行的时候,将这个对象锁占有,然后另外的线程就不能执行了。只有等这个代码块执行完毕,释放对象锁。(这里注意考虑一下栈、堆、方法区中变量)

13、死锁(deadlock)的例子

线程1:需要资源A和资源B

线程2:需要资源B和资源A

有一种情况线程1拿到了资源A,线程2拿到了资源B,此时线程1在等待资源B的释放,线程2在等待资源A的释放,可是只有等线程1用完了资源A、B后才会释放资源A,线程2用完了B、A后才会释放资源B,所以线程1就永远拿不到资源B,所以它永远释放不了资源A,就导致线程2永远拿不到资源A,也永远释放不了资源B。

14、wait( )和sleep( )方法的区别

  • wait( )

    wait是Object类的方法,它是作用于对象的,目的是让线程中的某个对象释放掉对象锁,然后进入等待状态,等待notify( )来唤醒这个对象所在的线程。

  • sleep( )

    sleep是Thread类的方法,它是作用于线程的,目的是让线程阻塞,阻塞的时候它并不会释放锁。

你可能感兴趣的:(Java,java,多线程)