• Java多线程笔记


    这篇笔记是总结我看的《Java核心技术》并发一章。

    要说线程,首先要说进程,操作系统运行程序的基本单位是进程,如今的操作系统早已是多任务操作系统,在同一时刻运行多个任务。操作系统将CPU的时间片分配每一个进程,给人并行处理的感觉。线程在低层次上扩展了进程,一个进程通常包含多个线程。区别在于进程拥有自己的一整套变量,线程则共享数据。线程之间的通信比进程更有效,容易。创建,销毁一个线程比进程开销小。

    创建线程的方式

    继承自Thread类

    class MyThread extends Thread {

        @Override
        public void run() {
            super.run();
            //your code
        }
    }

    实现Runnable接口

    Runnable myRunnable = () -> {
    //your code };
    new Thread(myRunnable).start();

    中断线程

    当线程的run方法执行完方法体最后一条语句后,或出现了在方法中没有捕获的异常时,线程将终止。

    没有可以强制线程终止的方法,interrupt()方法可以用来请求终止线程。调用interrupt(),线程的中断状态被置位,中断状态是一个boolean标志。通过调用isInterrupted()方法来判断当前线程是否被中断。

    当在一个被阻塞的线程(调用sleep()或者wait()方法)上调用interrupt()时,阻塞调用会被InterruptedException异常中断

    如果在中断状态被置位时调用sleep(),线程不会休眠,会清除这一状态,然后抛出InterruptedException异常。

    interrupted()和isInterrupted()方法的区别

    共同点:两个方法都用来检测当前线程是否被中断不同点:interrupted()是Thread的静态方法,在检测的同时会请求中断状态。isInterrupted()是实例方法,在检测时不会清除中断状态。

    线程状态

    线程可以有以下6种状态:

    new,用new操作符创建一个新线程时,该线程还没有开始运行,该线程是new状态。

    runnable,调用start()方法,线程处于runnable状态。处于runnable状态的线程可能正在运行,也可能没有在运行,取决于操作系统给线程提供运行的时间。

    blocked,当线程试图获取一个内部的对象锁,而该锁被其他线程持有,则该线程进入阻塞状态。当其他所有线程释放该锁,并且线程调度器允许本线程持有它的时候,该线程变为非阻塞状态。

    waiting,当线程等待另一个线程通知调度器一个条件时,自己进入等待状态。

    timed waiting,给等待添加一个超时参数,等待状态保持到超时期满或接收到适当通知。

    terminated,线程被终止,不再运行。

    线程因为如下两个原因被终止:

    run方法执行完自然退出

    因为一个未捕获的异常终止了run方法

    线程属性

    线程优先级

    Java语言中,每个线程都有一个优先级。默认情况下,继承自父类的优先级。

    调用setPriority()进行设置优先级,从MAX_PRIORITY(10)到MIN_PRIORITY(1),NORM_PRIROTY为5

    每当线程调度器有机会选择新线程时,首先选择具有较高优先级的线程。

    线程优先级高度依赖于系统,Java线程的优先级映射到宿主机平台的优先级上。Windows有7个优先级,Linux中线程具有相同的优先级。

    如果有几个高优先级的线程没有进入非活动状态,低优先级的线程可能永远也不能执行。

    守护线程

    调用setDaemon(true)将线程转为守护线程

    唯一用途是为其他线程提供服务

    当只剩下守护线程时,虚拟机就退出

    未捕获异常处理器

    run()方法抛出的unchecked异常被传递到未捕获异常处理器中,这个处理是实现了Thread.UncaughtExceptionHandler接口的类

    调用setUncaughtExceptionHandler()为线程安装一个处理器,也可以调用setDefaultUncaughtExceptionHandler()为所有线程安装处理器

    同步

    多线程访问同一个资源会出现race condition(条件竞争),为防止出现并发访问的问题,代码块需要加锁

    ReentrantLock
    保护代码块的基本结构如下,保证了任何时刻只有一个线程执行代码块。只有一个线程可以获得锁,其他线程会被阻塞。
    myLock.lock();

    try {
        ...
    } finally {
        myLock.unlock();
    }

    ReentrantLock是可重入锁,线程可以重复获得已持有的锁。ReentrantLock有一个hold count(持有计数)来跟踪lock()的嵌套调用。
    线程每次调用lock()是都需要调用unlock()来释放锁

    条件对象

    线程进入并行代码块,却发现在某一条件满足下才能执行。这时需要条件对象,管理那些已经获得锁但不能做有用工作的线程。
    一个锁对象可以有一个或多个相关的条件对象,通过以下方式来创建。
    Condition condition = myLock.newCondition();
    当线程发现条件不满足时,当前线程进入阻塞状态,并放弃锁,进入该条件对象的等待集,等待其他线程激活。
    condition.await();
    condition.sigal();
    condition.sigalAll();
    sigalAll()会激活因为这一条件而等待的所有线程,一旦成为可用的,将从await()返回,从被阻塞的地方继续执行。
    如果没有其他线程来激活等待的线程,将导致死锁(deadlock)。

    synchronized

    从Java 1.0开始,每个Java对象都有一个内部锁,如果一个方法用synchronized声明,将获取对象的内部锁来保护该方法。
    内部对象锁只有一个条件对象,wait()将线程添加到等待集中,notifyAll(),notify()解除线程的阻塞状态

    wait() notifyAll() notify()是Object类的final方法,所以Condition对应的方法名改为await() signalAll() signal()
    synchronized的另一个用法是同步代码块,线程进入是需要获取obj的内部锁
    synchronized(obj) {
        ...
    }

    volatile关键字
    如果一个字段被声明为volatile,那么编译器和虚拟机就知道该字段可能被另一个线程并发更新
    volatile实现了可见性和

    final
    final修改的字段也可以被多线程安全的访问。其他线程只会在构造函数完成构造后才会看到变量的值。

    原子性
    java.util.concurrent.atomic包中很多类使用了高效的机器级指令来保证操作的原子性,不会有并发问题。
    复杂的更新可以使用compareAndSet()方法,

    乐观更新需要多次重试,性能会大幅下降
    LongAdder
    包括多个变量,可以有多个线程来更新加数,总和为当前值。
    longAdder.add(x);
    longAdder.sum();

    LongAccumulator
    LongAccumulator将这种思想扩展到其他操作上,在定义LongAccumulator时定义操作和零元素,get()获取当前值
    LongAccumulator longAccumulator = new LongAccumulator(Math::max, 0);
    longAccumulator.get();

  • 相关阅读:
    modCount到底是干什么的呢
    Java 8 中的 Streams API 详解
    2017年3月16工作日志【mysql更改字段参数、java8 map()调用方法示例】
    jquery选择器之获取父级元素、同级元素、子元素
    Java8必知必会
    javascript es6 Promise 异步同步的写法(史上最简单的教程了)
    sublime插件开发教程4
    sublime插件开发教程3
    sublime插件开发教程2
    sublime插件开发教程1
  • 原文地址:https://www.cnblogs.com/minguo/p/13125718.html
Copyright © 2020-2023  润新知