时隔这么久,回顾 JAVA 的多线程

2020-11-18  本文已影响0人  Edwinpanzzz

纯个人笔记,一个字一个字码的,如有错误,欢迎指正。

基本概念:程序、进程、线程

程序

程序(program) 是为完成特定任务、用某种语言编写的一组指令的集合。

进程

进程(process) 是程序的一次执行过程,或是正在运行的一个程序。如任务管理器中的 PotPlayer、Typora。

image

线程

线程(Thread) 是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。如火绒安全中的病毒查杀、防护中心。

image

一个进程中的多个线程共享相同的内存单元/内存地址空间。他们从同一堆中分配对象,可以访问相同的变量和对象。通俗的讲,就是一个进程中的多个线程拥有各自的虚拟机栈和程序计数器,共享方法区和堆。

image

并行与并发

并行

多个 CPU 同时执行多个任务。

并发

一个 CPU (采用时间片) 同时执行多个任务。

多线程的优点

何时需要多线程

线程的创建和使用

方式一:继承 Thread 类

1)定义子类继承 Thread 类。

2)子类中重写 Thread 类中的 run 方法。

3)创建 Thread 子类对象,即创建了线程对象。

4)调用线程对象 start 方法:启动线程,调用 run 方法。

注意:

1、如果自己手动调用 run() 方法,那么就只是普通方法,没有启动多线程模式。

2、run() 方法由 JVM 调用,什么时候调用,执行的过程控制都有操作系统的 CPU

调度决定。

3、想要启动多线程,必须调用 start 方法。

4、一个线程对象只能调用一次 start() 方法启动,如果重复调用了,则将抛出以上

的异常 llegalThreadStateException 。要想创建多个线程就需要用 new 创建多个线程对象。

方式二:实现 Runnable 接口

1) 定义子类,实现 Runnable 接口。

2)子类中重写 Runnable 接口中的 run 方法。

3)通过 Thread 类含参构造器创建线程对象。

4)将 Runnable 接口的子类对象作为实际参数传递给 Thread 类的构造器中。

5)调用 Thread 类的 start 方法:开启线程,调用 Runnable 子类接口的 run 方法。

class MThread implements Runnable{
    @override
    public void run(){
        // do something...
    }
}

// 使用
MThread mThread = new MThread();
Thread t1 = new Thread(mThread);
t1.start();

开发中,优先选择 Runnable ,优势为:

1)多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。

2)避免了单继承的局限性

方式三:实现 Callable 接口

与使用 Runnable 相,Callable 功能更强大些:

class MThread implements Callable {
    @override
    public void call(){
        int sum = 0;
        // do something...
        return sum;
    }
}

// 调用
MThread mThread = new MThread();
FutrueTask futrueTask = new FutrueTask(mThread);
new Thread(futrueTask).start();

// 获取返回值
Object sum = futrueTask.get();

方式四:线程池创建(ExecutorService 和 Executors)

class MThread implements Runnable{
    @override
    public void run(){
        // do something...
    }
}

ExecutorService service = Executors.newCachedThreadPool(10);
service.execute(new MThread());
service.shutdown();

Thread 类的有关方法

线程的调度

调度策略

1、时间片

image

2、抢占式

高优先级的线程抢占 CPU。

Java 的调度方法

线程的优先级

线程的优先等级

涉及的方法

说明:低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用。

线程的生命周期

JDK 中用 Thread.State 类定义了线程的几种状态。线程的一个完整的生命周期中通常要经历如下的五种状态:

状态

具备了运行的条件,只是没分配到 CPU 资源

程的操作和功能

止自己的执行,进入阻塞状态

image

线程的安全问题

当多条语句在操作同一个线程共享数据(如抢票时的票数)时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行,导致共享数据的错误。

同步代码块(Synchronized)

synchronized (同步监视器){
    // 需要被同步的代码(操作共享数据的代码)
}

同步监视器:俗称,锁。任意对象都可以作为同步锁。必须确保使用同一个资源的多个线程共用一把锁,这个非常重要,否则就无法保证共享资源的安全。

继承 Thread 类的线程安全问题

class MThread extends Thread{
    // 新建对象
    private static Object obj = new Object();
    // 或 Dog dog = new Dog(); 都行,只要是一个对象
    @override
    public void run(){
        synchronized (obj){
            // 需要被同步的代码(操作共享数据的代码)
        }
    }
}

使用当前对象:

class MThread extends Thread{
    @override
    public void run(){
        synchronized (MThread.class){
            // 需要被同步的代码(操作共享数据的代码)
        }
    }
}

实现 Runnable 类的线程安全问题

class MThread implements Runnable{
    Object obj = new Object();
    // 或 Dog dog = new Dog(); 都行,只要是一个对象
    @override
    public void run(){
        synchronized (obj){
            // 需要被同步的代码(操作共享数据的代码)
        }
    }
}

下面就没有共用一把锁:

class MThread implements Runnable{
    // 或 Dog dog = new Dog(); 都行,只要是一个对象
    @override
    public void run(){
        Object obj = new Object();
        synchronized (obj){
            // 需要被同步的代码(操作共享数据的代码)
        }
    }
}

使用当前对象:

class MThread extends Thread{
    @override
    public void run(){
        synchronized (this){
            // 需要被同步的代码(操作共享数据的代码)
        }
    }
}

同步方法

如果操作共享数据的代码完成地声明在一个方法中,我们不妨将此代码声明为同步的。

继承 Thread 类的线程安全问题

class MThread implements Runnable{
    @override
    public void run(){
        synchronized (obj){
            // 需要被同步的代码(操作共享数据的代码)
        }
    }
    
    // 锁为当前类 MThread.class
    private static synchronized void show(){
        // do something...
    }
}

实现 Runnable 类的线程安全问题

class MThread implements Runnable{
    @override
    public void run(){
        synchronized (obj){
            // 需要被同步的代码(操作共享数据的代码)
        }
    }
    
    // 锁为 this
    private synchronized void show(){
        // do something...
    }
}

关于同步方法的总结:

1、同步方法仍然涉及到同步监视器,只是不需要我们显示声明。

2、非静态方法,同步监视器是 this;静态的方法,同步监视器是当前类本身。

Lock 锁方式解决线程安全问题

class MThread implements Runnable{
    private ReentrantLock lock = new ReentrantLock();
    
    @override
    public void run(){
        try{
            lock.lock();
            // do something...
        }finally{
            lock.unlock();
        }
    }
}
上一篇下一篇

猜你喜欢

热点阅读