基础概念

什么是进程和线程?

进程:进程是程序运行资源分配的最小单位

其中包括:CPU、内存空间、磁盘IO等,同一条进程中的多条线程共享该进程的全部系统资源,进程与进程之间是相互独立的。

线程:线程是CPU调度的最小单位,必须依赖于进程而存在

线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的、能独立运行的基本单位。线程基本不拥有系统资源,只拥有一点在虚拟机栈的内存。

CPU核心数和线程数关系

多核心:指单芯片多处理器,就是将多个并行的处理器集成到同一个芯片内,各个处理器执行不同的进程。

多线程:就是让同一个处理器上的多个线程同步执行并共享处理器的执行资源

核心数、线程数:目前主流的CPU都是多核的。增加核心数就是为了增加线程数,因为操作系统是通过线程来执行任务的,一般情况核心数:线程数=1:1的对应关系,但ntel引入超线程技术后,使核心数:线程数=1:2。

CPU 时间片轮转机制

比如说,我们的电脑是4核处理器,逻辑处理器(线程)是8;但是我们的操作系统并不是只允许只有8个线程,我们可能有几千个线程。对于这种情况呢,CPU就采用时间片轮转机制处理,给每个进程分配一个时间片来处理任务,时间完了后,就轮到其他进程来执行任务。

那么我们给线程分配多长的时间片合适呢?

时间片设置的太短会导致过多的进程切换,降低CPU效率;设置的过长呢,又可能引起对短的交互请求的响应变差。一般将时间片设置为100ms是一个比较合理的折中。

澄清并行和并发

这个举个例子:去食堂打饭!

A、B、C三个打饭窗口:

        1、A、B、C每个窗口都有一队人在打饭,这就是并行

        2、A窗口,有两队人在打饭,交替打饭,这就是并发

两者区别:并发,交替执行;并行,同时进行

高并发变成的意义和注意事项

首先我们说意义:

        1、当然是充分的利用CPU资源,一定的时间内区做更多的事情

        2、加快响应速度,多个人做事总比一个人做事快

        3、可使我们的代码模块化、异步话

注意事项:

        1、线程之间的安全性

        2、线程之间的死锁

Java里的线程

线程的启动和终止:

        启动:

        1、X entends Thread,然后X.start()

        2、X implements Runnable;然后交给Thread运行

那么 Thread和Runnable有什么区别呢?

Thread的对线程的抽象,Runnable只是对业务逻辑的抽象。

        中止:

        线程什么时候会终止呢?要么是run()方法执行完了,或者是抛出了一个未处理的异常导致线程提前结束。

        通过Thread的API我们可以看到一些过时的方法:suspend()、resume()、stop()。那么为什么这些方法会过时,不建议使用呢?

        举例:suspend()方法调用后,会进入休眠,但是在休眠的时候,并不会释放线程的资源(比如锁),这样的话很容易导致死锁的问题。stop()方法也是一样的不糊释放线程的资源。

        interrupt():API给我们提供了新的方法interrupt来中断线程。

        1、并不是说一个线程thread.interrupt()方法,线程立马就中断run()方法里面的业务;而是说线程只是受到了这么一个标识,可能线程完全不会理这个标识(中断请求)。

        线程通过isInterrupted()方法来判断是否被中断。如果一个线程处理阻塞状态(比如调用了sleep、wait等方法),则在线程检查中断标识时如果发现中断标识为true,则会在这些阻塞方法调用处抛出InterruptedException异常,并且在抛出异常后立即将线程的中断标识清除,重置为FALSE。

public class InterruptThread {
    private static class UserThread extends Thread{
        public UserThread(String name){
            super(name);
        }

        @Override
        public void run() {
            String threadNmae = Thread.currentThread().getName();
            System.out.println(threadNmae+" interrupt flag= " +isInterrupted());
            while (!isInterrupted()){
                try {
                    sleep(30);
                } catch (InterruptedException e) {
//                    interrupt(); 注释这行
                    System.out.println(threadNmae+ " is InterruptedException");
                    e.printStackTrace();
                }
                System.out.println(threadNmae+ " is running");
                System.out.println(threadNmae+ " inner interrupt flag = "+isInterrupted());
            }
            System.out.println(threadNmae+" interrupt flag= " +isInterrupted());

        }
    }

    public static void main(String[] args){
        UserThread userThread = new UserThread("userThread");
        userThread.start();
        try {
            Thread.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        userThread.interrupt();

    }

如果在catch方法里面再次加上interrupt()方法,线程的中断标识才会为TRUE。

那这是为什么呢?

因为,如果sleep方法不重置中断标识为FALSE,那就和stop方法一样,直接终止线程,那线程的一些资源(比如锁)根本就得不到释放。所以我们可以在catch方法里面释放一些资源再次给它interrupt()。

深入理解start()和run()方法

start()方法调用了才是真正的启动了一个线程才会和操作系统中的线程挂钩。run()方法只是处理业务逻辑的地方,我们完全可以把它当做一个普通的方法,和一个类中任意的一个方法没有区别。

其他的线程相关方法

yield()方法:使当前线程让出CPU占有权,但让出的时间不可设定。也不会释放锁资源。注意:并不是每个线程都需要这个锁,而且执行yield()的线程不一定就会持有锁,我们完全可以在释放锁后在调用yield()方法。

所有执行yield()的线程可能在进入到就绪状态后别操作系统再次选中马上又被执行。

        wait()/notify()/notifyAll():后面单独讲到

join方法:举个例子:A、B两个线程,A中调用了B线程的join()方法,那么只有当B线程执行完毕,才会执行A线程。这就保证了两个线程按照顺序执行(此处为面试常考点)

线程的优先级

在Java线程中,我们可以给每个线程指定一个优先级(1~10),通过方法setPriority(int)方法,默认为5。优先级越高的线程分配的时间片的数量越多,这样我们在平常的处理过程中可以这样:

        1、针对频繁阻塞(休眠或者I/O操作)的线程,我们可以设置优先级高一点

        2、针对偏重计算(需要较多CPU时间或者偏运算)的线程,我们设置优先级低一点,确保处理器不会被独占。

守护线程

Daemon线程是一种支持型线程,因为它主要被作用程序中后台调度以及支持性工作。这意味着,当我们的用户线程结束后,守护线程也跟着一起结束。我们一般用不上,比如垃圾回收线程就是Daemon线程。

Daemon线程被用作完成支持工作,但是在JVM退出时Daemon线程中的finally块并不一定执行。在构建Daemon线程时,不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑。

public class DaemonThreadTest {

    public static class UseThread extends Thread{
        @Override
        public void run() {
            try {
                while (!isInterrupted()){
                    System.out.println("~~~~~~~~");
                }
            }finally {
                //守护线程中finally不一定起作用
                System.out.println("finally");
            }
        }
    }

    public static void main(String[] args){
        UseThread useThread = new UseThread();
        useThread.setDaemon(true);
        SleepTools.ms(5);
        useThread.start();
        useThread.interrupt();
    }
}

线程间的共享和协作

线程间的共享:

        synchronized 内置锁:

Java支持多个线程同时访问一个对象或者成员变量,关键字synchronized可以修饰:类、方法、同步块(代码块)。加上锁之后,保证在同一时刻只能有一个线程进行操作,它保证了线程对变量访问的可见性和排他性,称为内置锁机制。

对象锁和类锁:

        对象锁:就是把一个对象当做一个锁来使用

        类锁:一般用于静态方法,就是把一个class类当做一个锁来使用,其实本质也是一个对象(当我们把一个Java文件编译之后就是一个class对象)

举几个例子:

        

public class SynchronziedTest {

    public Object object = new Object();

    public SynchronziedTest(){

    }
    // 对象锁,作用在非静态方法的synchronized相当于synchronized(this,当前SynchronziedTest这个类的对象)
    public  synchronized void method(){
        System.out.println("synchronized this method");
    }
    // 类锁,作用在静态方法,把SynchronziedTest类作为锁,实则是编译后的class对象锁
    public static synchronized void method1(){
        System.out.println("synchronized static method");
    }
    // 对象锁,作用在代码块(同步块),this表示当前SynchronziedTest类的对象作为锁
    public void method3(){
        synchronized (this){
            System.out.println("synchronized code");
        }

    }
    // 对象锁,作用在代码块(同步块),把new 出来的object对象作为锁
    public void method4(){
        synchronized (object){
            System.out.println("synchronized object code");
        }

    }

    public static void main(String[] args){
        SynchronziedTest test = new SynchronziedTest();
        test.method();
        SynchronziedTest.method1();
        test.method3();
        test.method4();
    }
}