2020-多线程笔记总结

JAVA 线程实现/建立方式

继承 Thread 类

Thread 类本质上是实现了 Runnable 接口的一个实例,表明一个线程的实例。 启动线程的惟一方法就是经过 Thread 类的 start()实例方法。 start()方法是一个 native 方法,它将启动一个新线程,并执行 run()方法。数据库

public class MyThread extends Thread {
    public void run() {
    System.out.println("MyThread.run()");
    }
}
MyThread myThread1 = new MyThread();
myThread1.start();
实现 Runnable 接口

若是本身的类已经 extends 另外一个类,就没法直接 extends Thread,此时,能够实现一个Runnable 接口。编程

public class MyThread extends OtherClass implements Runnable {
    public void run() {
    System.out.println("MyThread.run()");
    }
}
实现callbale

有返回值的任务必须实现 Callable 接口,相似的,无返回值的任务必须 Runnable 接口。执行Callable 任务后,能够获取一个 Future 的对象,在该对象上调用 get 就能够获取到 Callable 任务返回的 Object 了。多线程

public class callableTest {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService executor = Executors.newFixedThreadPool(10);
        //建立一个Callable,3秒后返回String类型
        Callable myCallable = new Callable() {
            @Override
            public String call() throws Exception {
//                Thread.sleep(3000);
                System.out.println("calld方法执行了");
                return "call方法返回值";
            }
        };
        List<Future> list = new ArrayList<Future>();
        for (int i = 0; i < 10; i++) {
            Future future = executor.submit(myCallable);
            list.add(future);
        }

线程的状态

image

线程生命周期

当线程被建立并启动之后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。
在线程的生命周期中,它要通过新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5 种状态。尤为是当线程启动之后,它不可能一直"霸占"着 CPU 独自运行,因此 CPU 须要在多条线程之间切换,因而线程状态也会屡次在运行、阻塞之间切换。jvm

新建状态(NEW)

当程序使用 new 关键字建立了一个线程以后,该线程就处于新建状态,此时仅由 JVM 为其分配内存,并初始化其成员变量的值。ide

就绪状态(RUNNABLE)

当线程对象调用了 start()方法以后,该线程处于就绪状态。 Java 虚拟机会为其建立方法调用栈和程序计数器,等待调度运行。工具

运行状态(RUNNING)

若是处于就绪状态的线程得到了 CPU,开始执行 run()方法的线程执行体,则该线程处于运行状态。this

阻塞状态(BLOCKED)

阻塞状态是指线程由于某种缘由放弃了 cpu 使用权,也即让出了 cpu timeslice,暂时中止运行。spa

直到线程进入可运行(runnable)状态,才有机会再次得到 cpu timeslice 转到运行(running)状态。阻塞的状况分三种:线程

等待阻塞(o.wait->等待对列) :

运行(running)的线程执行 o.wait()方法, JVM 会把该线程放入等待队列(waitting queue)中。code

同步阻塞(lock->锁池)

运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池(lock pool)中。

其余阻塞(sleep/join)

运行(running)的线程执行 Thread.sleep(long ms)或 t.join()方法,或者发出了 I/O 请求时,JVM 会把该线程置为阻塞状态。当 sleep()状态超时、 join()等待线程终止或者超时、或者 I/O处理完毕时,线程从新转入可运行(runnable)状态。

线程死亡(DEAD)

线程会如下面三种方式结束,结束后就是死亡状态。

1.正常结束
run()或 call()方法执行完成,线程正常结束。

2.异常结束
线程抛出一个未捕获的 Exception 或 Error。

3.调用 stop
直接调用该线程的 stop()方法来结束该线程—该方法一般容易致使死锁,不推荐使用。

锁的升级

若是有一个对象在被多个线程同时竞争,那么判断对象是否有锁,若是有锁,那么会先支持偏向锁,就是说当前已经得到锁的线程会优先拿到锁(markword区记录了偏向线程id)。那么拿不到锁的线程,就会升级锁,变成CAS synchronized乐观锁,会进行一段时间的循环自旋不断尝试获取锁,当自旋到必定次数后,会再次升级成synchronized重量级锁。

synchronized

锁方法会锁住this,锁静态方法会锁住class对象.锁代码块能够指定任意对象做为锁.

同步代码块可能会涉及到一个重入过程,synchronized不会说由于重入去不断重复获取锁释放锁的过程,而是用mointer每次重入去作一个计数器加一操做,在释放锁的过程当中也会逐步将计算器清零。而后让其余线程从block阻塞状态变成runnable状态去竞争这个锁。

synchronized和reentranLock的区别

synchronized不用手动编程,他是一个jvm关键字,我也不用关心他锁释放的一个过程,直接用就好了,而reentrantlock他是一个类,须要手动lock,配合try catch finally中去作一个锁释放操做

线程池

线程和数据库链接这些资源都是很是宝贵的资源。那么每次须要的时候建立,不须要的时候销毁,是很是浪费资源的。Java 里面线程池的顶级接口是 Executor,可是严格意义上讲 Executor 并非一个线程池,而只是一个执行线程的工具。真正的线程池接口是 ExecutorService。

一个线程池创建后,若是没有预加载任务,他一开始的核心线程数为0,当一个新任务被提交时,会创建一个核心线程去执行任务,若是一直来任务,而先前创建的核心线程都在忙,那么就会一直创建核心线程直到到达最大核心线程数。

但核心线程数最大,并且都在执行任务时,后来的任务会被放到blockingqueue(阻塞队列里),若是阻塞队列也满了,就会去创建新线程,此时的线程叫非核心线程,当整个线程池的线程数达到最大,他也有一个max access时,会触发拒绝策略。

拒绝策略

AbortPolicy 停止策略

会直接抛出异常来执行停止任务执行拒绝

DiscardPolicy 抛弃策略

他会丢弃不执行多余的任务来执行拒绝

DIscardOldestPolicy

会丢弃最先你未执行的任务

callrunpolicy

公平锁和非公平锁的区别

在多线程环境下

  1. 公平锁通常指代 先到达临界区的线程必定比后到临界区的线程 优先拿到锁
  2. 非公平锁则指代 先到达临界区的线程也不必定比后到临界区的线程 优先拿到锁

image

相关文章
相关标签/搜索