多的是你不知道的“多线程”的事

2020-05-18  本文已影响0人  JackDaddy

我们在开发过程中肯定会遇到多线程的情景,这篇文章主要来介绍一下关于多线程的知识点。

什么是进程?

操作系统调度的最基本单位,包含有操作系统分配的基本资源。

什么是线程?

CPU调度的最基本单位,不含有任何资源。

线程 VS 进程

并行 VS 并发

我们要记住,这两种情况都是存在多个CPU的情况下

电脑中核心数的概念?

我们经常可以听到电脑几核几核,指的就是CPU中具有几个线程数可以并行执行任务,近几年,Interl又引入了一个虚拟线程技术,指的就是将1个线程数虚拟成2个线程数,以提高CPU 的使用效率

可以通过右键打开任务管理器-性能选项卡来查看两者的关系 核心数与虚拟核心数

Java中的多线程

开启一个线程的方式

相信很多人对新开一个线程肯定很熟悉,会回答以下 3

  1. 继承 Thread 类,然后调用该线程的 start 方法
class testThreadA extends Thread {
    @Override
    public void run() {
        super.run();
    }
}
----------------------------------------------------------------------------------------------
Thread threadA = new testThreadA();
threadA.start();
  1. 实现 Runnable 接口,在主线程调用 start 方法
class testThreadD implements Runnable{
    @Override
    public void run() {
       System.out.println("this is thread-D");
    }
}
----------------------------------------------------------------------------------------------------
Runnable runnable = new testThreadD(); 
Thread threadD = new Thread(runnable);
threadD.start();
  1. 实现 Callable 接口,通过 FutureTask 这个类,最后调用 start 方法。这种方式启动线程可以得到在Callable 中的返回值。
class CallThreadE implements Callable<String>{
    @Override
    public String call() throws Exception {
        //在这里返回需要的值
        return null;
    }
}
----------------------------------------------------------------------------------------------------
CallThreadE callThreadE = new CallThreadE();
FutureTask<String> futureTask = new FutureTask<>(callThreadE);
Thread threadE = new Thread(futureTask);
threadE.start();
try {
            //得到返回值
            String result = futureTask.get();
        } catch (ExecutionException e) {
            e.printStackTrace();
    }

而实际上第 3 种启动方式是属于第 2 种启动方式,原因如下:

线程的状态

所谓线程的状态指的就是线程在JVM中的状态,有以下 6 种状态:

线程的状态

在这里有几个问题需要注意一下:

关于死锁

所谓死锁,指的就是在多线程下(N≥2),竞争多个同步资源(M≥2 且 M≤N)的情况下,而发生的情况。
死锁发生的具有以下 4 个条件:
1)互斥:指的是一个线程拿到资源后,其他资源没办法再继续获得资源,要想获得这个资源只能等待,等到拿到的资源线程释放资源后等待的线程才能拿到资源。
2)资源争夺的顺序不一致:多线程对同步资源的争夺顺序不一致,因为一个线程持有了一个同步资源后不释放,继续请求另一个同步资源时,再次请求的这个同步资源此时被另一个线程所持有。
3)不释放:指的是当某一个线程持有了同步资源后,必须执行完任务后才会释放这个同步资源。
4)请求和保持:指的是线程在已经持有一个同步资源的情况下,不释放此同步资源,同时又去请求新的同步资源。
以下是死锁的一个代码示例:

public static void main(String[] args) throws InterruptedException {

        final Boo boo = new Boo();
         //定义两把不同的锁(同步资源)
        final Object obj13 = new Object();
        final Object obj14 = new Object();

        final Thread t1 = new Thread() {
            public void run() {
                //线程一:先请求 obj13这把锁,然后请求objobj14这把锁,释放顺序是反过来的,也就是要先释放obj14
               //再释放obj13
                synchronized (obj13){
                    boo.methodA();
                    synchronized (obj14){
                        boo.methodB();
                    }
                }
            }
        };

        Thread t2 = new Thread() {
            public void run() {
                //线程一:先请求 obj14这把锁,然后请求objobj13这把锁,释放顺序是反过来的,也就是要先释放obj13
               //再释放obj14
                synchronized (obj14){
                    boo.methodA();
                    synchronized (obj13){
                        boo.methodB();
                    }
                }
            }
        };

        t1.setName("thread-111");
        t2.setName("thread-222");
        t1.start();
        t2.start();
  }
------------------------------------------------------------------------------------------
class Boo {

    public void methodA() {
        try {
            Thread t = Thread.currentThread();
            System.out.println(t.getName() + "正在执行A方法");
            Thread.sleep(1000);
            System.out.println(t.getName() + "执行A方法完毕");
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }
    }

    public void methodB() {
        try {
            Thread t = Thread.currentThread();
            System.out.println(t.getName() + "正在执行B方法");
            Thread.sleep(1000);
            System.out.println(t.getName() + "执行B方法完毕");
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }
    }
}

发生死锁会带来以下 3 个危害:

避免死锁的常见算法有:资源分配法,银行家算法。
而打破死锁的只要打破上面所说的任意一个条件就可以了。

关于活锁

所谓活锁,指的就是两个线程在尝试拿锁的时候,每个线程每次拿到的都是同一把锁,而在尝试拿另一把锁的时候总是拿不到,而把自己原来已有的的锁释放的过程。
解决活锁的办法是:
每个线程休眠随机数,错开拿锁的时间。

关于ThreadLocal

当我们需要在线程中设置一个独自的变量时,就可以使用threadLocal,使用了threadLocal 的变量只受当前线程影响,不会被其他线程影响。

public static void main(String[] args) throws InterruptedException {
         //ThreadLocal 初始化
        final ThreadLocal<String> tl1 = new ThreadLocal<>();
        final ThreadLocal<Integer> tl2 = new ThreadLocal<>();

        tl1.set("这是:tl1");
        tl2.set(0);

        Thread t1 = new Thread() {
            @Override
            public void run() {
                super.run();
                tl1.set("this is great");
                String result = tl1.get();
                System.out.println(result);
            }
        };

        Thread t2 = new Thread() {
            @Override
            public void run() {
                super.run();
                tl1.set("this is fanstastic");
                String result = tl1.get();
                System.out.println(result);
            }
        };

        t1.start();
        t2.start();
    }

ThreadLocal 的实现

**如果我们不去看 ThreadLocal 的源码,自己去实现一个 ThreadLocal 来达到变量线程隔离的目标。大多人会选择使用 HashMap 来实现:

public class MyThreadLocal<T> {
    //使用一个map来存储每个线程数据
    Map<Thread, T> threadMap = new HashMap<>();

    public synchronized T get() {
        return threadMap.get(Thread.currentThread());
    }

    public synchronized void setThreadMap(T t) {
        threadMap.put(Thread.currentThread(), T);
    }
}

使用这种方式看似可以达到线程隔离变量的作用,实际上有以下几个问题:

那接下来我们来看一下JDK中是如何实现的,首先进入到 ThreadLocal 的 get方法:

public T get() {
        Thread var1 = Thread.currentThread();
        //注意这里也是跟我们刚才想的一样,用当前线程作key,从一个 ThreadLocalMap 里获取值
        ThreadLocal.ThreadLocalMap var2 = this.getMap(var1);
        if (var2 != null) {
            ThreadLocal.ThreadLocalMap.Entry var3 = var2.getEntry(this);
            if (var3 != null) {
                Object var4 = var3.value;
                return var4;
            }
        }

        return this.setInitialValue();
    }

然后我们进入到 ThreadLocalMap 中看看是什么:

ThreadLocalMap
因为可能需要隔离多个变量,因此在 ThreadLocalMap 内部使用一个数组来存储。整个 ThreadLocal 的结构: ThreadLocal 通过这种方法达到了变量线程隔离。

每天进步的你超酷的~

上一篇 下一篇

猜你喜欢

热点阅读