线程和进程的概念:

一个进程可以看成是一个应用程序;

线程可以看成进程里更细的划分,一个进程可以有多个线程,至少有一个主线程。

(有多个 线路 可以执行,一个程序里面写了多个执行路径)

单核CPU还不能说同时执行,一个CPU的核只能干一件事情,有多个事情的话,就看他的线程是怎么进行调度的了。如果是多核CPU,就可以同时干多个事情了。

  • 线程的调度方法:

    • 分时调度:

      每个线程分到相应的时间片,执行的时间到了时间片规定的时间,就按顺序切换另外一个线 程了。

    • 抢占式调度:

      线程有优先级的概念,优先级高的,抢到时间片的概率就越大。

      Java 里使用的就是这种调度方式。

每个线程是一个独立的单位(有独立的栈空间和程序计数器,这是线程私有的数据空间),

多个线程之间可以共享一些数据(方法区 和 堆空间,这是线程共享的数据空间)

区分几个易混淆的概念:

  • 同步、异步

    同步:也就是我们通常说的 线程安全 的情况;排队执行,效率低的,但是安全性好。

    异步:也就是我们通常说的 线程不安全 的情况,一起执行,效率是提高了,但安全性降低了

  • 并发、并行

    并发:一般都是并发量是多少多少,指的是在2个或多个事件,在 一段时间内 同时发生。

    并行:指的是 2个或多个事件,在 同一时刻 发生。(在一个具体的时间点上)

Java 中创建线程的3种方式

  • 实现 Runnable 接口 ,是 new Thread 里传入的参数。

  • 实现 Callable 接口,得到一个 FurtureTask<> 对象, 是 new Thread 里传入的参数

    可以调用 get()方***阻塞当前线程的执行,直到得到 返回值的结果为止。

  • 继承 Thread 类(该类实现了 Runnable 接口)

Java 种线程的生命周期(线程的状态)

  • NEW 新建

    创建线程,但还没有启动(start)

  • RUNNABLE 可运行

    该线程正在 JVM 中 运行着。

    (具体的在 操作系统 层面有没有运行(可能在等待资源),还要看底层操作系统的调度策略)

  • BLOCKED 阻塞

    请求获取 monitor lock 时,其他线程正在占用着这把锁

    该状态是被动进来的

  • WAITING 无限时等待

    等待获取 monitor lock ;需要被其他线程显示的唤醒

    该状态是 主动 进来的

    进来该状态的途径:

    Object.wait( ) , 没有 Timeout 参数 : 挂起一个线程

    Thread.join( ) , 没有 Timeout 参数

    LockSupport.park( )

  • TIMED_WAITING 限时等待

    在一定时间后,会被系统自动唤醒

    进来的途径:(都需要传入一个 Timeout 时间参数 )

    Thread.sleep( ) : 睡眠一个线程

    Object.wait( ) : 挂起一个线程

    Thread.join( )

    LockSupport.parkNanos( )

    LockSupport.parkUntil( )

  • TERMINATED 死亡

    任务结束了,自己了解。

    或 产生异常而死亡。

线程里的一些机制或概念

  • Executor

    线程池,里面可以有很多个线程,可以管理多个异步任务的执行(多个任务的执行互不干扰)。

    因为频繁创建和销毁单个线程是需要耗费一定性能的,用线程池管理(线程可以反复使用)就可以解决这个问题。

    常用的是 缓存线程池。

    Executors.newCachedThreadPoll( )

    有空闲线程就直接使用,否则就创建一个线程,再装入该线程池中。

    定长线程池:

    Executors.newFixedThreadPool( )

    固定线程数量的线程池。

    有空闲线程就直接使用。

    没有空闲线程,线程池又装满了,就等待空闲线程的出现。

    没有空闲线程,线程池没有装满,就创建线程放入该线程池中。

    单线程线程池:

    Executors.newSingleThreadExecutor( )

    定长线程池 的 数量为 1 的情况

    周期性任务定长线程池:

    Executors.newScheduledThreadPool( )

    Executors.

    首先是个定长线程池,具有定长线程池的功能。

    他还可以执行周期任务,首次执行还可以指定延迟多少秒执行。

  • Daemon

    守护线程。 程序运行时,在后台提供服务的 线程,可以没有。

    所有非守护线程结束了,守护线程也就跟着结束了。

    main() 线程属于非守护线程。

  • sleep( )

    使一个线程睡眠,可能产生 中断异常。

    异常不可以 跨线程 处理,所以只能在本地进行处理。 (可以在捕获这个异常时结束这个异常)

  • yield()

    调用该方法,说明该线程已经完成了他的任务的主要部分,可以让出处理器资源了(给其他线程处理),

    只是给 线程调度器 的一个建议,具体给那个线程执行,还要看操作系统的具体策略。

  • 中断异常

    可以用 interrupt( ) 方法来做标记,提前结束线程。

    对 Executor 线程池中的中断:

    shutdown( ) 方法,等待所有线程执行完后,再关闭。

    shutdownNow( ) 方法,相当于调用每个线程的 interrupt()方法。

    中断单个线程:

    用 submit()提交一个线程,得到一个 Future<?> 对象,调用该对象的 cancel(true)方法中断线程。

互斥同步(阻塞同步)

这是一种 悲观 的同步策略。只有做正确的同步措施,才会没问题,否则就肯定会出现问题。

无论共享的数据是否出现竞争,都有进行加锁、用户态核心转换、维护锁计数器 和 检查是否需要唤醒线程。

随着硬件指令集的发展,我们可以使用基于 冲突检测乐观并发策略。(非阻塞同步)

先操作,没有其他线程竞争共享数据就好 , 有的话就采取补偿措施(不断重试,直到成功)即可。

可以加锁。分为隐式锁和显式锁。

隐士锁: synchronized 关键字
  • 可以用来 同步代码块

    synchronized( ){ };

    ( ) 里放入锁对象,这个锁对象要是所有线程都共有的,不能是线程私有的,否则起不到同步的作用。

    是一种 粒度很细的方法,可以同步具体的一行代码。

  • 可以用来 同步方法

    在方法声明的时候,加上 synchronized 关键字即可。

    如果是 非静态方法,加锁的对象是 this,也就是调用该方法的那个对象,那个调用者。

    如果是 静态方法,加锁的对象是 类名.class, 也就那整个类,那个字节码文件。

显示锁: ReentrantLock 。 是 java.util.concurrent (J.U.C) 包中的锁
  • 声明一个 ReentrantLock 对象,调用他的 lock()方法加锁,调用他的 unlock()解锁。
比较

synchronized 是 JVM 实现的,ReentrantLock 是 JDK 实现的。

JVM 对 synchronized 做了很多很多优化。

synchronized 加锁的线程 不可以中断,ReentrantLock 加锁的线程 可以中断。

synchronized 是不公平锁,ReentrantLock 也是不公平锁,但可以设置为 公平锁。

公平锁 和 不公平锁

公平锁: 多个线程等待同一把锁时,先来的先得到锁。

不公平锁: 多个线程等待同一把锁时,得到锁的结果时 不确定的。

一个 ReentrantLock 可以同时绑定 多个Condition 对象。

一般都是使用 synchronized 来加锁,是 JVM 原生支持的。

线程之间的协调

  • join()方法

    在线程中调用另一个线程的 join()方法,将当前线程挂起,直到调用该方法的线程结束。

    可以保证线程的先后执行顺序。

  • wait() 、 notify() 、 notifyAll()

    他们都是 Object 的方法。

    wait() 挂起线程时, 会释放锁。 Thread 里的 sleep() 不会释放锁。

    需要用 notify()或 norifyAll() 来唤醒。

  • await() 、 signal() 、 signalAll()

    他们都是 java.util.concurrent (J.U.C)类库里的方法,通过 Condition 类来实现线程之间的协调。

    可以用 Lock 来获取一个 Condition 对象。