java多线程

[TOC]

问题:

  1. 并发编程不可不知道的基础概念

  2. 天使就是多线程的java程序

  3. 线程的一生

  4. 线程间的共享

  5. 线程间的协作

  6. 线程隔离的threadLocal

  7. java里的显示锁

多线程

并发编程不可不知道的基础概念
  1. 线程:cpu的核心数和线程数的关系

    • 核心数和线程数: 是1:1的关系,如果cpu是4核的话,就可以同时4个线程工作

      如果是inter的超线程技术:一个核对应了2个逻辑cpu,所以有了4核8线程

    • cpu时间片轮转机制 RR调度

      Round-Robin,轮询调度,通信中信道调度的一种策略,该调度策略使用户轮流使用共享资源,不会考虑瞬时信道条件。

  1. 进程和线程

    • 进程和线程:

      进程:操作系统在运行的时候,资源分配的最小单位;分配磁盘io,cpu等;

      ​ 进程和cpu无关

      线程:线程必须依赖一个进程,启动一个进程,至少有一个线程。

      linux和windows的进程数量有所不同,但是可以进行认为的修改。

  2. 并发和并行

    并行:同时进行,四个轮子的车。

    并发:关键字:单位时间内;在单位时间内运行的线程数量。

    java是不怎么关注虚拟机的内核,java无法调度一个线程在指定cpu核上运行。

    并发会来带一下问题:

    1.线程安全问题

    2.死锁问题

    3.线程数量,会消耗内存,有单独的栈空间,每个栈空间需要1m的空间的大小

    一个线程,在运行的时候,会消耗,运行的数据也要保持起来,另一个线程,也要去读,存好的数据,去取,取的时候也需要时间

    通过上下文切换来进行切换线程的使用。一般消耗20000个cpu时间周期,时间周期:把于1+1的时间看作成一个时间周期。

    finalize() 关于对象,释放
    finalize线程来进行,但是这个方法不一定执行

    finalize线程是个守护线程,守护线程跟主线程是同生共死的,当主线程销毁了,finalize线程也进行了销毁,所以finalize()方法就不会被调用了

    问题:gc线程是什么时候启动的?

    答:gc线程,要有资源回收的时候才启动

    答:gc是个守护线程,和主线程同生共死的

  3. Thread

    • 开启一个线程

      总共3种启动方式
      分别是:thread
      然后两种任务的形式:runnable,callable (callable这种方式不经常使用,在android asyncTask中使用了这个种方式,然后进行的)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
          >Runnable runnable = () -> {
      System.out.println("我是runnable的方式启动的");
      };
      Thread thread = new Thread() {
      @Override
      public void run() {
      super.run();
      }
      };
      >static Callable<String> callable = new Callable<String>() {
      @Override
      public String call() throws Exception {
      System.out.println("我是callable的");
      Thread.sleep(2000);
      return "aaaa";
      }
      >};
    • 停止一个线程

      1. 抛弃的方法

        Suspend 会拿着锁去挂起,容易造成死锁

        stop, 会直接停止线程,会有些文件句柄等一些都没有进行释放

      2. jdk 1.5后建议使用

        Interrupted -> 是一个静态方法;判断线程是否被中断,这个会把状态true改为false

        isInterrupted -> thread的成员方法;是否中断了, 而这个中断状态是true,不会改成false

        静态方法 方法那个在使用过程中,通过这个状态来进行判断处理

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        //1.静态方法
        //这个情况下,就可能有不一样的效果了
        while (Thread.interrupted()) {
        //执行的内容
        }

        //2.成员方法,
        while (Thread.interrupted()) {
        //执行的内容
        }
        // runnable,或者callable的情况下获取当前线程进行 interrupt
        while (Thread.currentThread().isInterrupted()) {
        //执行的内容
        }
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        //如果是在注释的这个打印,最后结果会是true,
        while (!Thread.currentThread().isInterrupted()) {
        System.out.println("TestRunnable enter isInterrupted: " + Thread.currentThread().isInterrupted());
        }

        //这样打印,最后一行输出会是false
        public class TestRunnable implements Runnable {
        @Override
        public void run() {
        // while (!Thread.currentThread().isInterrupted()) {
        // System.out.println("TestRunnable enter isInterrupted: " + Thread.currentThread().isInterrupted());
        // }
        while (!Thread.interrupted()) {
        System.out.println("TestRunnable enter isInterrupted: " + Thread.currentThread().isInterrupted());
        }
        System.out.println("TestRunnable end isInterrupted: " + Thread.currentThread().isInterrupted());
        }

        public static void main(String[] args) throws InterruptedException {
        Runnable runnable = new TestRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
        }
        }

        所以,Thread.interrupted() 是会把状态重新修改一次

        资料说明:

        interrupted()是静态方法:内部实现是调用的当前线程的isInterrupted(),并且会重置当前线程的中断状态

        isInterrupted()是实例方法,是调用该方法的对象所表示的那个线程的isInterrupted(),不会重置当前线程的中断状态

        其实看源码就知道了:

        Tests if some Thread has been interrupted. The interrupted state

        is reset or not based on the value of ClearInterrupted that is

        passed
        这句话的翻译
        测试某个线程是否被中断。中断状态是重置还是不重置取决于clearinterrupt的值通过。

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
           >public static boolean interrupted() {
        return currentThread().isInterrupted(true);
        >}
        >public boolean isInterrupted() {
        return isInterrupted(false);
        >}
        >/**
        * Tests if some Thread has been interrupted. The interrupted state
        * is reset or not based on the value of ClearInterrupted that is
        * passed.
        */
        >private native boolean isInterrupted(boolean ClearInterrupted);
  4. 线程的共享和协作

    线程之间的共享:数据是共享的;线程共享进程的所拥有的全部资源。

    线程基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器,栈),但是它可与同属一个进程的其他线程共享进程的所拥有的全部资源

    有共享就会出现,线程安全问题,从而出现了锁的这个概念

    • synchronized 内置锁 : synchronized锁的都是对象

      对象锁,在静态方法上面的锁,是class对象锁
      在普通方法上面的锁,是this 这个对象的锁

    • Lock 显示锁

    • 可重入锁解释

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      /**
      * 什么叫可重入锁:
      * 自己把自己锁上了
      * synchronized 支持,多次拿自己的,jdk设计的时候,考虑了这一点,所以也是可重入锁
      * 递归调用的时候,防止自己把自己锁死
      */
      private int count;
      public synchronized void incr() {
      count ++;
      incr();
      }
    • 生产者与消费者标准范式

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      //消费者
      sync(对象) {
      while(条件不满足) {
      对象.wait()
      }
      执行业务代码
      }

      //生产者
      sync(对象) {
      执行业务代码
      修改条件
      对象.notify/notifyAll
      }
    • 非公平锁和公平锁

疑问:threadpoolexecutor 是怎么保留线程数量的,线程池的原理
通过死循环

扩展:final类型的应该不能反射修改吧???

回答是分两种情况的。

  1. 当final修饰的成员变量在定义的时候就初始化了值,那么java反射机制就已经不能动态修改它的值了。

  2. 当final修饰的成员变量在定义的时候并没有初始化值的话,那么就还能通过java反射机制来动态修改它的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class People {
public final String name = "aa.bb";

public People() {
}

public String getName() {
return name;
}

public static void main(String[] args) {
People people = new People();
System.out.println("name 修改前:" + people.name);
reflect(people);
System.out.println("name 修改后:" + people.name);
}

private static void reflect(People people) {
Class<? extends People> aClass = people.getClass();
try {
Field name = aClass.getDeclaredField("name");
name.setAccessible(true);
name.set(people, "bb.sdfafasdf");
} catch (Exception e) {
e.printStackTrace();
}
}
}

把people改成这样就可以进行修改了

1
2
3
4
5
6
7
8
9
10
 public final String name ;

public People() {
name = "aa.bb";
}

public String getName() {
return name;
}
}

2019-04-11整理

  1. 复习了synchronized 关键字的复习
    ThreadLocal线程隔离
    synchronized同步中使用 wait来等待notify notifyAll来进行唤醒

    读写锁,来进行单一写,多读的情况下,可以提高效率

    可重入锁概念:自己可以调取自己不会因为锁来卡死自己

  2. 正式进入话题

1. Condition lock的这个锁中有个 newCondition的这个方法,返回一个condition对象

Condition 里面有几个方法
主要有: await() 相当于wait()

​ signal()/signalAll() 相当于notify、notifyAll,但是不同的是,当signal()之后,只会唤醒对应的Condition对象,所以当一对一唤醒的时候,比notify要好用,notify可能会唤醒不该唤醒的线程。

// todo 明天补上代码

2.线程池
  • 为什么要使用线程池

    1. 线程池可以复用线程,降低反复创建线程的消耗
    2. 提高响应速度 (来回创建肯定消耗时间和资源)
    3. 提高线程的可管理性
  • runtime 可以获取到 jdk运行时的一些信息

    1
    Runtime.getRuntime().availableProcessors();  //获取 cpu 逻辑核心数
  • 在看线程池的源码过程中,犯了一个错误,看到 runnable.run()误认为是普通方法,并没有开启线程,实际上是在封装好的 Thread内里面的run方法调用的,所以通过普通方法调用,这种方式是合理的

    还是没找到循环在哪里,还没搞懂,有问题????todo

    线程池 worker 线程 中 run 方法 runWorker(this) 里面进行了调取 getTask()
    线程池中通过线程自己里面在 getTask的时候 workQueue.take(); 进行堵塞,然后让线程存活了起来

  • 线程池的使用

    1. BlockingQueue对应的几个常用的方法

      add() 增加 会抛异常
      remove() 删除 会抛异常

      offer() 插入成功返回true 失败返回 false

      poll() 移除队列的元素

      put() 数据满,插入堵塞
      tack() 为空时,取出堵塞

    2. 线程池的一些参数含义

      1
      2
      3
      4
      5
      6
      7
      8
      public ThreadPoolExecutor(int corePoolSize, //线程池的核心线程数
      int maximumPoolSize, //线程池最大线程数 10 就有5个非核心线程数
      long keepAliveTime, //线程池当达到最大线程数的时候,非核心线程数的线程存活的时间
      TimeUnit unit, //时间单位
      BlockingQueue<Runnable> workQueue, //排队的任务队列
      ThreadFactory threadFactory, //线程的命名??
      RejectedExecutionHandler handler //队列+最大线程数已经极限了,然后做出的处理
      )
    3. jdk的4种拒绝策略(TODO)

    4. 启动线程池的两种方式

      execute()

      submit() —AbstractExecutorService实现的

    5. 线程池核心数量的确定

>
1
2
3
4
5
6
>*  任务的特性相关:
>* cpu密集型: 当前计算任务需要cpu 不超过机器上的同时运行的线程个数 cpu逻辑核心数
>* IO密集型: 读写操作时,网络的时候 一般来讲配置多点线程数 2*cpu逻辑核心数
>* 混合型 --> 拆分 为 cpu密集型和io密集型
>* io 和 cpu消耗的差不多的时候 拆分
>* io 10s cpu 10ms 相当于 cpu密集型 就不需要拆分了
  1. 所有的ui控件都是线程不安全的

  2. AsyncTask的原理 //todo

  3. Volatile 可变的 //todo
    都会到主内存去取,但是写回到主内存的时候并不安全
    适用于:当一个写,多个读的时候,非常适用

  4. 悲观锁 synchronized和lock都是悲观锁
    乐观锁 cas写回操作,知道相等为止
    典型的悲观锁
    AtomicBoolean , AtomicInteger

    1
    2
    3
    4
    5
    6
    7
    8
    9
    在unsafe.class里面有
    public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
    var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
    }
//这些细节决定了这些

1
StickyRecyclerHeadersAdapter
//将一个item回到顶部
1
((LinearLayoutManager) mRecyclerView.getLayoutManager()).scrollToPositionWithOffset(posi, 0);