【随心记】多线程笔录

@[TOC] 【随心记】多线程笔录

1.如何使一个线程停止

可以使用“异常法”,需要停止一个线程时可在当前线程中调用isinterrupted() 获取线程是否被打上中断的标识来校验,如果返回true则表示被打上中断标识,此时抛出异常中断线程运行。在线程外通过调用 interupt() 给线程打上中断表示。

2.记录一下java中的多线程知识

java中如何创建线程;java中Runnable和Callable的区别;一个类是否可以同时继承Thread和实现Runnable接口吗?;
java中创建多线程的四中方法?

  • 继承Thread创建线程类
    • 定义Thread类的的子类,并重写此子类的run()方法,run()方法代码块的内容就是此线程的执行体。
    • 创建Thread子类的实例,就完成的线程对象的创建
    • 调用线程实例的start()方法启动线程

public class ThreadDemo extends Thread {
    @Override
    public void run() {
        System.out.println(1);
    }
}
  • 实现Runnable接口创建线程类
    • 定义 Runnable 接口的实现类,并重写该接口的 run()方法,该 run() 方法的方法体同样是该线程的线程执行体。
    • 创建 Runnable 实现类的实例,并依此实例作为 Thread 的 target 来创建 Thread 对象,该 Thread 对象才是真正的线程对象。
    • 调用线程对象的 start()方法来启动该线程。

public class RunnableDemo implements Runnable {
    @Override
    public void run() {
        System.out.println("1");
    }

    public static void main(String[] args) {
        RunnableDemo l = new RunnableDemo();
        new Thread(l).start();
    }
}

  • 通过CallAble和Future创建线程
    • 定义CallAble接口实例,并重写call()方法,此call()方法将作为线程的执行主体并有返回值。
    • 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call()方法的返回值。
    • 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线 程。
    • 调用 FutureTask 对象的 get()方法来获得子线程执行结束后的返回值。
public class CallableDemo implements Callable {
    @Override
    public Object call() throws Exception {
        int i=1;
        return i+2;
    }

    public static void main(String[] args) {
        CallableDemo callableDemo = new CallableDemo();
        FutureTask futureTask = new FutureTask(callableDemo);
        Object l = null;
        try {
            l = futureTask.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        Thread 	ls = new Thread(futureTask);
        Thread 	lt = new Thread(futureTask);
        ls.start();
        lt.start();
        System.out.println(l);
    }
}
  • 通过线程池创建线程
    通过线程池不用new也可创建线程对象且线程可复用。利用 ThreadPoolExecutor 创建线程池。
    *
public class ExecutorServiceDemo {
    /**
     * @param threadPoolName  线程池名称
     * @param corePoolSize    核心线程数
     * @param maximumPoolSize 最大线程数
     * @param queueSize       队列容量
     * @return
     */
    public static ExecutorService getExecutorService(String threadPoolName, int corePoolSize, int maximumPoolSize, int queueSize) {
        ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat(threadPoolName + "-%d").build();
        ExecutorService pool = new ThreadPoolExecutor(corePoolSize, maximumPoolSize,
                0L, TimeUnit.MILLISECONDS,new LinkedBlockingDeque<Runnable>(queueSize)
                , namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
        return pool;
    }

    public static void main(String[] args) {
        CallableDemo callableDemo = new CallableDemo();

        ExecutorService executorService = getExecutorService("demo",10,20,10);
        FutureTask futureTask = (FutureTask) executorService.submit(callableDemo);
        try {
            System.out.println(futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
  • java中Callable于Runnable的不同

    • Callable拥有Call()方法,而Runnable的是run()方法
    • Callable的Call()方法有返回值,而Runnable的run()方法无返回值
    • Callable的Call()方法可抛出异常,而Runnable的run()方法无法抛出异常
  • 一个类中是否可同时继承Thread类和实现Runnable接口?
    答案是可以的。因为T类从 Thread 类中继承了 run()方法,这个 run()方法可以被当作对 Runnable 接口的实现。

多线程同步

在多线程的环境中,经常会遇到数据的共享问题,即当多个线程需要访问同 一资源时,他们需要以某种顺序来确保该资源在某一时刻只能被一个线程使用, 否则,程序的运行结果将会是不可预料的,在这种情况下,就必须对数据进行 同步。——>锁对象!什么是锁对象! 一个对象只有一个钥匙,多线程对一个资源使用时同时只有一个线程能使用此资源,其他线程只能等锁释放后才能使用。怎么创建锁对象? 1.类名.this 2.对象.getClass();有同步方法代码时,内置锁才会起作用(Synchronize);

  • 在 Java 中,提供了四种方式来实现同步互斥访问: synchronized 和 Lock 和 wait()/notify()/notifyAll()方法和 CAS。
  • Synchronize的用法
    • synchronized 同步代码块:
      synchronized(object)
      {
      }

      • 表示线程在执行的时候会将 object 对象上锁。(注意这个对象可以是任意 类的对象,也可以使用 this 关键字或者是 class 对象)。
        可能一个方法中只有几行代码会涉及到线程同步问题,所以 synchronized 块 比 synchronized 方法更加细粒度地控制了多个线程的访问, 只有 synchronized 块中的内容不能同时被多个线程所访问,方法中的其他语句仍然 可以同时被多个线程所访问(包括 synchronized 块之前的和之后的)。
    • 修饰非静态的方法

      • 当 synchronized 关键字修饰一个方法的时候,该方法叫做同步方法。
        Java 中的每个对象都有一个锁(lock),或者叫做监视器(monitor), 当一个线程访问某个对象的 synchronized 方法时,将该对象上锁,其他任何 线程都无法再去访问该对象的 synchronized 方法了(这里是指所有的同步方 法,而不仅仅是同一个方法),直到之前的那个线程执行方法完毕后(或者是 抛出了异常),才将该对象的锁释放掉,其他线程才有可能再去访问该对象的 synchronized 方法。
        注意这时候是给对象上锁,如果是不同的对象,则各个对象之间没有限制 关系。
        注意,如果一个对象有多个 synchronized 方法,某一时刻某个线程已经进入 到了某个 synchronized 方法,那么在该方法没有执行完毕前,其他线程是无法访 问该对象的任何 synchronized 方法的。
  • 修饰静态方法
    • 当synchronized关键字修饰的方法同时也被static关键字修饰时,上面说过修饰非静态方法是lock对象,给对象上锁,而静态变量不属于对象,是属于类的。所以无论一个类创建多少个对象线程的执行还是有顺序的,只有一个线程执行完成或者抛出异常其他线程才能访问此方法。
  • 结论
    • syschronized 方法(同步方法)是一种粗粒度的并发控制方案某一时刻只能有一个线程执行此方法。
    • synchronized代码块(同步代码快)是一种细粒度并发控制方案,synchronized代码块以外的代码是可以同时被多个线程访问到的。
  • lock的用法
    • lock使用时必须配合try catch finally使用。需要在finally代码块中释放线程避免出现死锁现象。如下:
Lock lock = ...;
lock.lock();
try{
//处理任务
}
catch(Exception ex){
}
finally{
lock.unlock();
//释放锁
}
  • 相较于synchronized 讲lock更个性化,synchronized的取锁,释放锁是自动完成的而lock则需要手动获取锁释放锁。同样synchronized不支持线程中断,例如一个线程正在使用资源其他线程则需要等待,lock可以中断线程正在等待(可中断锁)。lock.trylock可以知道是否是否获取到锁如果获取到则返回true反之返回false。
  • lock是一个接口,synchronized是一个java关键字
  • lock的线程可以多条线程同时读到一个资源但是不能修改所以lock也叫”读写锁”。
  • 保证锁的公平性,lock可以做到公平锁。Lock lock=new ReentrantLock(true) 缺省值为false;时这个锁的代码就是公平锁。例如 A B C 三个线程,在三个线程竞争同一个资源的时候会按照等待时间的长短来分配锁(等待时间最长的优先获取锁),如果设置为false则是随机分配。synchronized无法做到。
  • 在相同线程竞争一个资源不激烈下lock与synchronized的区别相差无几,当存在大量线程竞争激烈时使用lock比synchronized好的多。所以要在合适的业务场景下选择锁的使用。
  • volatile 和 synchronized的区别
    • volatile作用于变量上属于变量修饰符,而synchronized属于方法或者代码块修饰符。
    • volatile可以禁止指令重排(禁止编译器优化),synchronized所修饰的方法或者代码块会经过编译器优化。
    • volatile仅能实现变量的修改可见性,并不能保证原子性,synchronized可 以 保 证 变 量 的 修 改 可 见 性 和 原 子 性,线程取到数据,存入寄存器,UAL计算,写入寄存器,指向变量会在释放锁之前完成保证了数据的可见性。
    • volatile不会对变量进行加锁因此不会造成线程阻塞,synchronized会对方法、代码块加锁造成线程阻塞。
  • 什么情况下vlatile可以替换synchronized
    • 在仅需要共享变量的可见性的时候使用vlatile,在需要一致性,原子性,可见性的时候用synchronized。
  • wait,notifyAll,notify的用法
    • 首先,wait、notify 方法是针对对象的,调用任意对象的 wait()方法都将导致线程阻塞, 阻塞的同时也将释放该对象的锁,相应地,调用任意对象的 notify()方法则将随机解除该对 象阻塞的线程,但它需要重新获取改对象的锁,直到获取成功才能往下执行;其次,wait、 notify 方法必须在 synchronized 块或方法中被调用,并且要保证同步块或方法的锁对象与调 用 wait、notify 方法的对象是同一个,如此一来在调用 wait 之前当前线程就已经成功获取 某对象的锁,执行 wait 阻塞后当前线程就将之前获取的对象锁释放。
    • 为什么 wait(),notify(),notifyAll()等方法都定义在 Object 类中?
      • 因为这三个方法都需要定义在同步代码块或同步方法中,这些方法的调用是依赖锁对 象的,而同步代码块或同步方法中的锁对象可以是任意对象,那么能被任意对象调用的方 法一定定义在 Object 类中。
    • notify()和 notifyAll()有什么区别?
      • notifyAll 使所有原来在该对象上等待被 notify 的线程统统退出 wait 的状态,变成等待该对 象上的锁,一旦该对象被解锁,他们就会去竞争。 notify 他只是选择一个 wait 状态线程进行通知,并使它获得该对象上的锁,但不惊动其他 同样在等待被该对象 notify 的线程们,当第一个线程运行完毕以后释放对象上的锁,此时 如果该对象没有再次使用 notify 语句,即便该对象已经空闲,其他 wait 状态等待的线程由 于没有得到该对象的通知,继续处在 wait 状态,直到这个对象发出一个 notify 或 notifyAll, 它们等待的是被 notify 或 notifyAll,而不是锁。
    • CAS
      • CAS是一个非阻塞锁(自旋)。乐观锁。多个线程使用同一资源,它们会认为没有人在竞争此资源,当如尝试获取资源时如果获取失败了便会自旋重试直到。不像synchronized和lock一样造成线程阻塞。
    • 锁的层次之分
      • 锁分为程序层面的,比如java代码层通过synchronized同步锁可重入锁,公平锁,读写锁。还分为数据库层面的悲观锁,乐观锁,表锁,行锁,页锁。
  • 实现线程通信的方法
    • 1.Object类的wait/notiFy/noTiFyAll方法
    • 2.Volatile关键字
    • 3.用 Condition 接口
      • Condition 是被绑定到 Lock 上的,要创建一个 Lock 的 Condition 对 象必须用 newCondition()方法。在一个 Lock 对象里面可以创建多个 Condition 对象,线程可以注册在指定的 Condition 对象中,从而可以有 选择性地进行线程通知,在线程调度上更加灵活。
      • 在 Condition 中,用 await()替换 wait(),用 signal()替换 notify(), 用 signalAll()替换 notifyAll(),传统线程的通信方式, Condition 都可以实现。 调用 Condition 对象中的方法时,需要被包含在 lock()和 unlock()之间。
    • 4.管道实现线程间的通信
      • 实现方式:一个线程发送数据到输出管道流,另一个线程从输入管道流中 读取数据。
        基本流程:
        1)创建管道输出流 PipedOutputStream pos 和管道输入流 PipedInputStream pis。
        2)将 pos 和 pis 匹配,pos.connect(pis)。
        3)将 pos 赋给信息输入信息的线程,pis 赋给获取信息的线程,就可以实 现线程间的通讯了。
        缺点:
        1)管道流只能在两个线程之间传递数据。
        线程 consumer1 和 consumer2 同时从 pis 中 read 数据,当线程 producer 往管道流中写入一段数据(1,2,3,4,5,6)后,每一个时刻只有一个 线程能获取到数据,并不是两个线程都能获取到 producer 发送来的数据,因 此一个管道流只能用于两个线程间的通讯。
        2)管道流只能实现单向发送,如果要两个线程之间互通讯,则需要两个管道流。
        线程 producer 通过管道流向线程 consumer 发送数据,如果线程 consumer 想给线程 producer 发送数据,则需要新建另一个管道流 pos1 和 pis1,将 pos1 赋给 consumer1,将 pis1 赋给 producer1。
  • 如何做到线程安全
    • 定义) 当多个线程处理同一处代码时做到了同单线程一样的结果,且结果同预期的一样的情况下就做到了线程安全。
    • Synchronized,Lock,原子类(如 atomicinteger 等),同步容器、并 发容器、 阻塞队列 、 同步辅助类(比 如 CountDownLatch, Semaphore, CyclicBarrier)都可做到。
  • 多线程的优点和缺点?
    • 充分发挥CPU的能力。 调度CPU性能。
    • 程序响应更快
    • 缺点
      • 上下文切换的开销
      • 当 CPU 从执行一个线程切换到执行另外一个线程的时候,它需要先存储当 前线程的本地的数据,程序指针等,然后载入另一个线程的本地数据,程序指 针等,最后才开始执行。这种切换称为“上下文切换”。CPU 会在一个上下文 中执行一个线程,然后切换到另外一个上下文中执行另外一个线程。上下文切 换并不廉价。如果没有必要,应该减少上下文切换的发生。
      • 增加资源消耗线程在运行的时候需要从计算机里面得到一些资源。 除了 CPU,线程还需 要一些内存来维持它本地的堆栈。它也需要占用操作系统中一些资源来管理线 程。
      • 编程更复杂
      • 在多线程访问共享数据的时候,要考虑线程安全问题。
  • 遵循的多线程最佳实践
    • 给线程起个有意义的名字。
    • 避免锁定和缩小同步的范围 。 相对于同步方法我更喜欢同步块,它给我拥有对锁的绝对控制权。
    • 多用同步辅助类,少用 wait 和 notify 。 首先,CountDownLatch, Semaphore, CyclicBarrier 这些同步辅助 类简化了编码操作,而用 wait 和 notify 很难实现对复杂控制流的控制。其次, 这些类是由最好的企业编写和维护在后续的 JDK 中它们还会不断优化和完善, 使用这些更高等级的同步工具你的程序可以不费吹灰之力获得优化。
    • 多用并发容器,少用同步容器。 如果下一次你需要用到 map,你应该首先想到用 ConcurrentHashMap。
  • 多线程的性能一定就优于单线程吗?
    • 对于单核 CPU,如果是 CPU 密集型任务,如解压文件,多线程的性能反 而不如单线程性能,因为解压文件需要一直占用 CPU 资源,如果采用多线程, 线程切换导致的开销反而会让性能下降。如果是交互类型的任务,肯定是需要 使用多线程的。
    • 对于多核 CPU,对于解压文件来说,多线程肯定优于单线程,因为多个线 程能够更加充分利用每个核的资源。
    • 总结:所以不是什么场景都适合多线程的。要搭配服务器硬件和业务场景。
  • 多线程中锁的种类。
    • 可重入锁
      • ReentrantLock 和 synchronized 都是可重入锁。
        • 如果当前线程已经获得了某个监视器对象所持有的锁,那么该线程在该方法 中调用另外一个同步方法也同样持有该锁。
public sychrnozied void A() {
 int i = 1;
 B();
}
public sychronized void B() {
 String l ="A";
}

可重入锁最大的作用是避免死锁。如果锁是不具有可重入性的话,那么该 线程在调用 test2 前会等待当前对象锁的释放,实际上该对象锁已被当前线程 所持有,不可能再次获得,那么线程在调用同步方法、含有锁的方法时就会产 生死锁。

  • 可中断锁
    • 在 Java 中,synchronized 不是可中断锁,而 Lock 是可中断锁。 lockInterruptibly()的用法已经体现了 Lock 的可中断性。如果某一线程 A 正 在执行锁中的代码,另一线程 B 正在等待获取该锁,可能由于等待时间过长, 线程 B 不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线 程中断它,这种就是可中断锁。
  • 公平锁
    • 在 Java 中,synchronized 就是非公平锁,它无法保证等待的线程获取锁 的顺序。而对于 ReentrantLock 和 ReentrantReadWriteLock,它默认情况 下是非公平锁,但是可以设置为公平锁。公平锁即尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个 锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该锁, 这种就是公平锁。
  • 读写锁
    • 正因为有了读写锁,才使得多个线程之间的读操作不会发生冲突。 ReadWriteLock 就是读写锁,它是一个接口,ReentrantReadWriteLock 实 现了这个接口。可以通过 readLock()获取读锁,通过 writeLock()获取写锁。
  • 锁优化
    • 自旋锁
      1:为了等待线程不等待造成阻塞线程便会忙循环(自旋),自旋需要一个以上的处理器,虽然自旋消除了线程上下文切换的资源消耗但是如果一个线程一直占用资源时间过长还是会一直占用硬件资源。自旋锁默认是十次循环,可通过-xx:PreBlockSpin来更改
      2:自适应自旋锁:自旋的时间不再固定,而是由前一次在同一个锁上的自旋 时间及锁的拥有者的状态来决定
    • 锁清除
      指虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不 可能存在共享数据竞争的锁进行清除(逃逸分析技术:在堆上的所有数据都不会 逃逸出去被其它线程访问到,可以把它们当成栈上数据对待)。
    • 如果虚拟机探测到有一串零碎的操作都对同一个对象加锁,将会把加锁同 步的范围扩展到整个操作序列的外部。
    • 轻量级锁
      1:在代码进入同步块时,如果此同步对象没有被锁定,虚拟机首先将在当前 线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储所对象目 前的 Mark Word 的拷贝。然后虚拟机将使用 CAS 操作尝试将对象的 Mark Word 更新为执行 Lock Record 的指针。如果成功,那么这个线程就拥有了该 对象的锁。如果更新操作失败,虚拟机首先会检查对象的 Mark Word 是否指 向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,否则说 明这个对象已经被其它线程抢占。如果有两条以上的线程争用同一个锁,那轻 量级锁就不再有效,要膨胀为重量级锁。
      2:解锁过程:如果对象的 Mark Word 仍然指向着线程的锁记录,那就用 CAS 操作把对象当前的 Mark Word 和和线程中复制的 Displaced Mark Word 替 换回来,如果替换成功,整个过程就完成。如果失败,说明有其他线程尝试过 获取该锁,那就要在释放锁的同时,唤醒被挂起的线程。
      3:轻量级锁的依据:对于绝大部分的锁,在整个同步周期内都是不存在竞争 的。
      4:传统锁(重量级锁)使用操作系统互斥量来实现的。
      HotSpot 虚拟机的对象的内存布局:对象头(Object Header)分为两部分信息吗,第 一部分(Mark Word)用于存储对象自身的运行时数据,另一个部分用于存储指向方法区 对象数据类型的指针,如果是数组的话,还会由一个额外的部分用于存储数组的长度。

32 位 HotSpot 虚拟机中对象未被锁定的状态下, Mark Word 的 32 个 Bits 空间中 25 位 用于存储对象哈希码,4 位存储对象分代年龄,2 位存储锁标志位,1 位固定为 0。

HotSpot 虚拟机对象头 Mark Word

存储内容标志位状态对象哈希码、对象分代年龄01未锁定指向锁记录的指针00轻量级锁定指向重量级锁的指针10膨胀(重量级锁)空,不记录信息11GC 标记偏向线程 ID,偏向时间戳、对象分代年龄01可偏向

  • 偏向锁
    • 目的是消除在无竞争情况下的同步原语,进一步提高程序的运行性能。锁 会偏向第一个获得它的线程,如果在接下来的执行过程中,该锁没有被其它线 程获取,则持有锁的线程将永远不需要再进行同步。
    • 当锁第一次被线程获取的时候,虚拟机将会把对象头中的标志位设为 01, 同时使用 CAS 操作把获取到这个锁的线程的 ID 记录在对象的 Mark Word 之 中,如果成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时,都可 以不进行任何同步操作。
    • 当有另一个线程去尝试获取这个锁时,偏向模式就宣告结束。根据锁对象 目前是否处于被锁定的状态,撤销偏向后恢复到未锁定或轻量级锁定状态。
  • wait()和 sleep()的区别。
    • 这两个方法来自不同的类,sleep()来自 Thread 类,是静态方法;wait() 是 Object 类里面的方法,和 notify()或者 notifyAll()方法配套使用,来实现 线程间的通信。
    • 最主要是 sleep 是将当前线程挂起指定的时间,没有释放锁;而 wait 方法 释放了锁,使得其他线程可以使用同步控制块或者方法。
    • 使用范围:wait,notify 和 notifyAll 只能在同步控制方法或者同步控制块 里面使用,而 sleep 可以在任何地方使用 。
synchronized(x){
 x.notify() 
 //或者 wait()
}

特别注意: sleep 和 wait 必须捕获异常(Thread.sleep()和 Object.wait() 都会抛出 InterruptedException), notify 和 notifyAll 不需要捕获异常。

  • Java 中 interrupted() 和 isInterrupted()方法的区别?
    • 前者是静态方法,后者是非静态方法。interrupted 是作用于当前正在运 行的线程, isInterrupted 是作用于调用该方法的线程对象所对应的线程。 (线程对象对应的线程不一定是当前运行的线程。例如我们可以在 A 线程中去调用 B 线程对象的 isInterrupted 方法,此时,当前正在运行的线程就是 A 线程。)
    • 前者会将中断状态清除而后者不会。
  • Java 创建线程之后,直接调用 start()方法和 run()的区别 ?
    • start()方法来启动线程,并在新线程中运行 run()方法,真正实现了 多线程运行。这时无需等待 run 方法体代码执行完毕,可以直接继续执行下面 的代码;通过调用 Thread 类的 start()方法来启动一个线程,这时此线程是 处于就绪状态,并没有运行,然后通过此 Thread 类调用方法 run()来完成其 运行操作,这里方法 run()称为线程体,它包含了要执行的这个线程的内容, run ()方法运行结束,此线程终止。然后 CPU 再调度其它线程。
    • 直接调用 run()方法的话,会把 run()方法当作普通方法来调用,会 在当前线程中执行 run()方法,而不会启动新线程来运行 run()方法。程序还 是要顺序执行, 要等待 run 方法体执行完毕后,才可继续执行下面的代码; 程 序中只有主线程——这一个线程, 其程序执行路径还是只有一条, 这样就没有 达到多线程的目的。
  • 什么是线程的上下文切换?
    • 对于单核 CPU,CPU 在一个时刻只能运行一个线程,当在运行一个线程的 过程中转去运行另外一个线程,这个叫做线程上下文切换(对于进程也是类似)。
    • 线程上下文切换过程中会记录程序计数器、CPU 寄存器的状态等数据。
    • 虽然多线程可以使得任务执行的效率得到提升,但是由于在线程切换时同 样会带来一定的开销代价,并且多个线程会导致系统资源占用的增加,所以在 进行多线程编程时要注意这些因素。
  • 怎么检测一个线程是否拥有锁?
    • 在 java.lang.Thread 中有一个方法叫 holdsLock(Object obj),它返回 true,如果当且仅当当前线程拥有某个具体对象的锁。obj等于某对象。查看此线程是否拥有此对象的锁。
  • 用户线程和守护线程有什么区别?
    • 当我们在 Java 程序中创建一个线程,它就被称为用户线程。将一个用户线 程设置为守护线程的方法就是在调用start()方法之前, 调用对象的 setDamon(true)方法。一个守护线程是在后台执行并且不会阻止 JVM 终止的 线程,守护线程的作用是为其他线程的运行提供便利服务。当没有用户线程在 运行的时候, JVM 关闭程序并且退出。一个守护线程创建的子线程依然是守护 线程。

守护线程的一个典型例子就是垃圾回收器。

  • 什么是线程调度器?
    • 线程调度器是一个操作系统服务,它负责为 Runnable 状态的线程分配 CPU 时间。一旦我们创建一个线程并启动它,它的执行便依赖于线程调度器的 实现。
  • 线程的状态。
    • 版本 1.

在 Java 当中,线程通常都有五种状态,创建、就绪、运行、阻塞和死亡。

第一是创建状态。在生成线程对象,并没有调用该对象的 start 方法,这是 线程处于创建状态。
第二是就绪状态。当调用了线程对象的 start 方法之后,该线程就进入了就 绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就 绪状态。在线程运行之后,从等待或者睡眠中回来之后,也会处于就绪状态。
第三是运行状态。线程调度程序将处于就绪状态的线程设置为当前线程, 此时线程就进入了运行状态,开始运行 run 函数当中的代码。
第四是阻塞状态。线程正在运行的时候,被暂停,通常是为了等待某个事 件的发生(比如说某项资源就绪)之后再继续运行。sleep,wait 等方法都可以导 致线程阻塞。
第五是死亡状态。如果一个线程的 run 方法执行结束或者异常中断后,该 线程就会死亡。对于已经死亡的线程,无法再使用 start 方法令其进入就绪。

  • 版本 2.

一般来说,线程包括以下这几个状态:创建(new)、就绪(runnable)、运 行(running)、阻塞(blocked)、timed_waiting、waiting、消亡(dead)。

  • 有三个线程 T1,T2,T3,怎么确保它们按顺序执行?
    • join()方法。
  • 在一个主线程中,要求有大量子线程执行完之后,主线程才执行完成。多种方式,考虑效率。
    • 在主函数中使用 join()方法。
t1.start();
t2.start();
t3.start();
t1.join(); //不会导致 t1 和 t2 和 t3 的顺序执行 
t2.join();
t3.join();
System.out.println("Main finished");
  • CountDownLatch,一个同步辅助类,在完成一组正在其他线程中执行 的操作之前,它允许一个或多个线程一直等待。
public class WithLatch {
 public static void main(String[] args) {
 CountDownLatch latch = new CountDownLatch(3);
 for (int i = 0; i < 3; i++) {
 new ChildThead(i, latch).start();
 }
 try {
 latch.await();
 }
 catch (InterruptedException e) {
 e.printStackTrace();
 }
 System.out.println("Main finished");
 }
 static class ChildThead extends Thread {
 private int id = -1;
 private CountDownLatch latch = null;
 public ChildThead(int id, CountDownLatch latch) {
 this.id = id;
 this.latch = latch;
 }
 public void run() {
 try {
 Thread.sleep(Math.abs(new Random().nextint(5000)));
 System.out.println(String.format("Child Thread %dfinished", id));
 }
 catch (InterruptedException e) {
 e.printStackTrace();
 }
 finally {
 latch.countDown();
 }
 }
 }
}
  • 使用线程池。
public class WithExecutor {
 public static void main(String[] args) throws InterruptedException {
 ExecutorService pool = Executors.newFixedThreadPool(3);
 List<Callable<Void>> list = new ArrayList<Callable<Void>>();
 for (int i = 0; i < 3; i++) {
 list.add(new ChildThead(i));
 }
 try {
 pool.invokeAll(list);
 }
 finally {
 pool.shutdown();
 }
 System.out.println("Main finished");
 }
 static class ChildThead implements Callable<Void> {
 private int id = -1;
 public ChildThead(int id) {
 this.id = id;
 }
 public Void call() throws Exception {
 try {
 Thread.sleep(Math.abs(new Random().nextint(5000)));
 System.out.println(String.format("Child Thread %dfinished", id));
 }
 catch (InterruptedException e) {
 e.printStackTrace();
 }
 return null;
 }
 }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值