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 list = new ArrayList();
        for (int i = 0; i < 10; i++) {
            Future future = executor.submit(myCallable);
            list.add(future);
        }

线程的状态

2020-多线程笔记总结_第1张图片

线程生命周期

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

新建状态(NEW)

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

就绪状态(RUNNABLE)

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

运行状态(RUNNING)

如果处于就绪状态的线程获得了 CPU,开始执行 run()方法的线程执行体,则该线程处于运行状态。

阻塞状态(BLOCKED)

阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice,暂时停止运行。

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

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

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

同步阻塞(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. 非公平锁则指代 先到达临界区的线程也不一定比后到临界区的线程 优先拿到锁

2020-多线程笔记总结_第2张图片

你可能感兴趣的