一. Java 多线程:
Java 给多线程编程提供了内置的支持. 一条线程指的是进程中一个单一顺序的控制流, 一个进程中可以并发多个线程, 每条线程并行执行不同的任务.
多线程是多任务的一种特别的形式, 但多线程使用了更小的资源开销.
这里定义和线程相关的另一个术语 -- 进程: 一个进程包括由操作系统分配的内存空间, 包含一个或多个线程. 一个线程不能独立的存在, 它必须是进程的一部分. 一个进程一直运行, 直到所有的非守护线程都结束运行后才能结束.
多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的.
二. 线程的生命周期:
线程是一个动态的执行过程, 它也有从产生到死亡的过程
a) 新建状态:
使用 new 关键字和 Thread 类或其子类建立一个线程对象后, 该线程对象就处于新建状态. 它保持这个状态直到程序 start()这个线程.
b) 就绪状态:
当线程对象调用了 start()方法之后, 该线程就进入就绪状态. 就绪状态的线程处于就绪队列中, 要等待 JVM 里线程调度器的调度.
c) 运行状态:
如果就绪状态的线程获取 CPU 资源, 就可以执行 run(), 此时线程便处于运行状态. 处于运行状态的线程最为复杂, 它可以变为阻塞状态, 就绪状态和死亡状态.
d) 阻塞状态:
如果一个线程执行了 sleep(睡眠),suspend(挂起)等方法, 失去所占用资源之后, 该线程就从运行状态进入阻塞状态. 在睡眠时间已到或获得设备资源后可以重新进入就绪状态. 可以分为三种:
等待阻塞: 运行状态中的线程执行 wait() 方法, 使线程进入到等待阻塞状态.
同步阻塞: 线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用).
其他阻塞: 通过调用线程的 sleep() 或 join() 发出了 I/O 请求时, 线程就会进入到阻塞状态. 当 sleep() 状态超时, join() 等待线程终止或超时, 或者 I/O 处理完毕, 线程重新转入就绪状态.
e) 死亡状态:
一个运行状态的线程完成任务或者其他终止条件发生时, 该线程就切换到终止状态.
三. 线程的优先级
每一个 Java 线程都有一个优先级, 这样有助于操作系统确定线程的调度顺序. Java 线程的优先级是一个整数, 其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY ).
默认情况下, 每一个线程都会分配一个优先级 NORM_PRIORITY(5).
具有较高优先级的线程对程序更重要, 并且应该在低优先级的线程之前分配处理器资源. 但是, 线程优先级不能保证线程执行的顺序, 而且非常依赖于平台.
四. 创建一个线程
a) 通过继承 Thread 类
b) 通过实现 Runnable 接口
c) 通过 Callable 和 Future 创建线程
创建 Callable 接口的实现类, 并实现 call()方法, 该 call()方法将作为线程执行体, 并且有返回值.
创建 Callable 实现类的实例, 使用 FutureTask 类来包装 Callable 对象, 该 FutureTask 对象封装了该 Callable 对象的 call()方法的返回值.
使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程.
调用 FutureTask 对象的 get()方法来获得子线程执行结束后的返回值.
五. 线程的常见方法
序号 | 方法描述 |
1 | public void start()
|
2 | public void run()
|
3 | public final void setName(String name)
|
4 | public final void setPriority(int priority)
|
5 | public final void setDaemon(boolean on)
|
6 | public final void join(long millisec)
|
7 | public void interrupt()
|
8 | public final boolean isAlive()
|
序号 | 方法描述 |
1 | public static void yield()
|
2 | public static void sleep(long millisec)
|
3 | public static boolean holdsLock(Object x)
|
4 | public static Thread currentThread()
|
六. 线程的几个主要概念
a) 线程同步
b) 线程安全: 如果你的代码在多线程下执行和在单线程下执行永远都能获得一样的结果, 那么你的代码就是线程安全的.
不可变: 像 String,Integer,Long 这些, 都是 final 类型的类, 任何一个线程都改变不了它们的值, 要改变除非新创建一个, 因此这些不可变对象不需要任何同步手段就可以直接在多线程环境下使用
绝对线程安全: 不管运行时环境如何, 调用者都不需要额外的同步措施. 要做到这一点通常需要付出许多额外的代价, Java 中标注自己是线程安全的类, 实际上绝大多数都不是线程安全的, 不过绝对线程安全的类, Java 中也有, 比方说 CopyOnWriteArrayList,CopyOnWriteArraySet
相对线程安全: 相对线程安全也就是我们通常意义上所说的线程安全, 像 Vector 这种, add,remove 方法都是原子操作, 不会被打断, 但也仅限于此, 如果有个线程在遍历某个 Vector, 有个线程同时在 add 这个 Vector,99% 的情况下都会出现 ConcurrentModificationException, 也就是 fail-fast 机制.
线程非安全: 这个就没什么好说的了, ArrayList,LinkedList,HashMap 等都是线程非安全的类
a) 线程间通信
b) 线程死锁
c) 线程控制: 挂起, 停止和恢复
七. 多线程中的常见问题
a) Run()和 start()之间的区别?
只有调用了 start()方法, 才会表现出多线程的特性, 不同线程的 run()方法里面的代码交替执行. 如果只是调用 run()方法, 那么代码还是同步执行的, 必须等待一个线程的 run()方法里面的代码全部执行完毕之后, 另外一个线程才可以执行其 run()方法里面的代码.
b) Runnable 接口和 Callable 接口的区别?
Runnable 接口中的 run()方法的返回值是 void, 它做的事情只是纯粹地去执行 run()方法中的代码而已; Callable 接口中的 call()方法是有返回值的, 是一个泛型, 和 Future,FutureTask 配合可以用来获取异步执行的结果.
这其实是很有用的一个特性, 因为多线程相比单线程更难, 更复杂的一个重要原因就是因为多线程充满着未知性, 某条线程是否执行了? 某条线程执行了多久? 某条线程执行的时候我们期望的数据是否已经赋值完毕? 无法得知, 我们能做的只是等待这条多线程的任务执行完毕而已. 而 Callable+Future/FutureTask 却可以获取多线程运行的结果, 可以在等待时间太长没获取到需要的数据的情况下取消该线程的任务, 真的是非常有用.
c) Sleep()方法和 wait()方法的区别?
sleep 方法和 wait 方法都可以用来放弃 CPU 一定的时间, 不同点在于如果线程持有某个对象的监视器 (监视对象同步),sleep 方法不会放弃这个对象的监视器, wait 方法会放弃这个对象的监视器, 并且 wait() 只能在同步中使用
来源: https://www.cnblogs.com/Tom-shushu/p/9033973.html