多线程之基础篇(一)

一、Thread类

1、线程的创建 

大家都熟知创建单个线程的三种方式,通过继承Thread类创建线程并重写该类的run()方法;通过实现Runnable接口创建线程一样要重写run()方法;以上的两个run()方法都是线程的执行体;第三,使用Callable和Future来创建线程Callable接口提供了一个call()方法来作为线程的执行体,call()方法比run()方法功能要更加强大,call()方法可以有返回值,call()方法可以声明抛出异常(前两种如果要抛异常只能通过try,catch来实现);下面详细介绍一下Callable和Future

我们知道创建线程最终要由操作系统支持,java中只有调掉Thread对象,最后执行一个叫private native void start0();的本地方法,而该方法的最终实现在C++和C层面,要阅读JVM源码才能更深一步,暂时咱们知道最后是由操作系统给我们另起了一个线程取执行 “执行体”里面的业务。我们看Thread类的构造方法里面没有直接传Callable接口的构造方法,那为啥我们还能运行下面这段代码呢?

    /**
     * Callable需要配合FutureTask使用
     * FutureTask的get()可以返回任务的执行结果,方便在2个线程之间,把一个线程的结果传给另一个线程
     *
     * @throws InterruptedException
     * @throws ExecutionException
     */
    private static void testFutureAndCallable() throws InterruptedException, ExecutionException {
        FutureTask future = new FutureTask<>(() -> {
            log.debug("running.....");
            Thread.sleep(1000);
            return 100;
        });

        Thread t3 = new Thread(future, "t3");
        t3.start();

        // 当主线程运行到get方法时,主线程会阻塞住,一直等待 t1线程 结果返回
        log.debug("main接收future的返回值:{}",future.get());
        log.debug("main线程继续运行....");
    }

我们可以看到,直接把future对象传入给了Thread构造方法,其实我们可以从FureTask出发, FutureTask实现了RunnableFuture接口,而RunnableFuture又同时实现了Runnable和Future接口,所以在直接构造Thread时可以直接传入构造参数执行。

public class FutureTask implements RunnableFuture {}

public interface RunnableFuture extends Runnable, Future {}

尽管FureTask+Callable有返回值,可以抛异常,可以查看线程执行任务的情况,但是有时候为获取它的返回值主线程而陷入阻塞等待,另一个isDone()方法轮询获取值容易耗CPU资源.........

针对以上2个痛点JDK做了扩展CompletableFuture类同时实现了Future和CompletionStage接口,在CompletionStage上做了极大的扩展

Runnable——没有输入参数,也没有返回值

Function——功能型函数接口,有一个参数,有一个返回参数

Consumer——消费型函数接口,有一个输入参数,没有返回参数

—Consumer延申BiConsumer,有两个输入参数,没有返回参数

Supplier——供给型函数接口,没有输入参数,有返回值

数式接口名称 方法名称 参数 返回值
Runnable run 无参数 无返回值
Function apply 1个参数 有返回值
Consumer accept 1个参数 无返回值
Supplier get 无参数 有返回值
BiConsumer accept 2个参数

无返回值

2、interrupt()、interrupted()、isinterrupted()  

public void interrupt()

实例方法

,Just to set the interrupt flag

实例方法interrupt()仅仅是设置线程的中断状态为true,发起一个协商而不会立刻停止线程。

如果线程处于被阻塞状态(例如处于sleep,wait,join等状态),在别的线程中调用当前线程对象的interrupt方法,那么线程将立即退出被阻塞状态,并抛出一个InterruptException异常。

public static boolean interrupted()

静态方法,Thread.interrupted();

判断线程是否被中断并清除当前中断状态。

这个方法做了两件事

1.返回到当前线程的中断状态,测试当前线程是否已被中断。

2.将当前线程的中断状态清零并重新设为false,清除线程的中断状态

public boolean isInterrupted()

实例方法

判断当前线程是否被中断(通过检查中断标志位)

二、Java的锁  

乐观锁:

        1、版本号version

        2、CAS(compareAndSwap)算法,java原子类(Atomic打头的类)中的递增操作就是通过CAS自旋实现的。——Unsafe类

使用场景:适合读操作多的场景,不加锁的特点能够使其读取操作的性能大幅提升。乐观锁则直接去操作同步资源,是一种无锁算法

悲观锁: 同一时间点有且仅有一个线程占有锁

        1、synchronized

        2、ReentrantLock

使用场景:适合写操作多的场景,先加锁可以保证写操作时数据正确,现实的锁定之后在操作同步资源 

1、synchronized

 为什么任何一个对象都可以成为一把锁?

因为Java中所有的对象都默认继承了超类Object,而Object在JVM源码(C++)层面关联了一个对象ObjectMonitor,所以每个对象“天生”都带着一个对象监视器,也就是每一个被锁住的对象都会和Monitor关联起来。

 ObjectMonitor中有几个关键属性

_owner 指向持有ObjectMonitor对象的线程
_WaitSet 存放于wait状态的线程队列
_EntryList 存放处于等待锁block状态的线程队列
_recursious 锁的重入次数
_count 用来记录该线程获取锁的次数

多线程之基础篇(一)_第1张图片

管程:Monitors,也叫监视器

是一种程序结构,结构内的多个子程序(对象或模块)形成的多个工作线程互斥访问共享资源。这些共享资源一般是硬件设备或一群变量。对共享变量能够进行的所有操作集中在一个模块中。(把信号量及其操作原语“封装”在一个对象内部)管程实现了在一个时间点,最多只有一个线程在执行管程的某个子程序。管程提供了一种机制,管程可以看作一个软件模块,它是将共享的变量和对于这些共享变量的操作封装起来,形成一个具有一定接口的功能模块,进程可以调用管程来实现进程级别的并发控制。

注:synchronized和 static synchronized前者是对象锁,后者是类锁,属于不同的锁,a线程加对象锁,b线程加类锁,加锁不同,a、b线程不会产生竟态条件。

 2、自旋锁

没有成员变量的类一般都是线程安全的

cynchronized

 多线程之基础篇(一)_第2张图片

 多线程之基础篇(一)_第3张图片

多线程之基础篇(一)_第4张图片

并发压测工具 

多线程之基础篇(一)_第5张图片

 
  

 装饰器模式:Collections以synchronized打头的的实现集合的那些方法就是采用了装饰器模式。

1、AQS

AQS全称是AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架

特点:

1、用state属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个状态,控制如何获取锁和释放锁

—getState-获取state状态

—setState-设置state状态

—compareAndSetState-cas机制设置state状态[compare式保证state的原子性]

2、提供了基于FIFO的等待队列,类似于Monitor的EntryList

3、条件变量来实现等待、唤醒机制,支持多个条件变量,类似于Montor的WaitSet

你可能感兴趣的:(java,开发语言)