多线程入门

线程与进程区别

每个正在系统上运行的程序都是一个进程。每个进程包含一到多个线程。线程是一组指令的集合,或者是程序的特殊段,它可以在程序里独立执行。也可以把它理解为代码运行的上下文。所以线程基本上是轻量级的进程,它负责在单个程序里执行多任务。通常由操作系统负责多个线程的调度和执行。
使用线程可以把占据时间长的程序中的任务放到后台去处理,程序的运行速度可能加快,在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下可以释放一些珍贵的资源如内存占用等等。
如果有大量的线程,会影响性能,因为操作系统需要在它们之间切换,更多的线程需要更多的内存空间,线程的中止需要考虑其对程序运行的影响。通常块模型数据是在多个线程间共享的,需要防止线程死锁情况的发生。

总结:进程是所有线程的集合,每一个线程是进程中的一条执行路径。

多线程的作用

可以提高程序执行效率

多线程应用场景

迅雷多线程下载、分批发送短信等

多线程创建方式

1.继承Thread类,重写run方法

  1. class CreateThread extends Thread {
  2. // run方法中编写 多线程需要执行的代码
  3. publicvoid run() {
  4. for (inti = 0; i< 10; i++) {
  5. System.out.println("i:" + i);
  6. }
  7. }
  8. }
  9. public class ThreadDemo {
  10. public static void main(String[] args) {
  11. System.out.println("-----多线程创建开始-----");
  12. // 1.创建一个线程
  13. CreateThread createThread = new CreateThread();
  14. // 2.开始执行线程 注意 开启线程不是调用run方法,而是start方法
  15. System.out.println("-----多线程创建启动-----");
  16. createThread.start();
  17. System.out.println("-----多线程创建结束-----");
  18. }
  19. }

运行结果:

image.png

调用start方法后,代码并没有从上往下执行,而是有一条新的执行分之


2.实现Runnable接口,重写run方法

  1. class CreateRunnable implements Runnable {
  2. @Override
  3. publicvoid run() {
  4. for (inti = 0; i< 10; i++) {
  5. System.out.println("i:" + i);
  6. }
  7. }
  8. }
  9. /**
  10. *
  11. * @classDesc: 功能描述:(实现Runnable接口,重写run方法)
  12. * @author: 余胜军
  13. * @version: v1.0
  14. * @copyright:上海每特教育科技有限公司
  15. */
  16. publicclass ThreadDemo2 {
  17. public static void main(String[] args) {
  18. System.out.println("-----多线程创建开始-----");
  19. // 1.创建一个线程
  20. CreateRunnable createThread = new CreateRunnable();
  21. // 2.开始执行线程 注意 开启线程不是调用run方法,而是start方法
  22. System.out.println("-----多线程创建启动-----");
  23. Thread thread = new Thread(createThread);
  24. thread.start();
  25. System.out.println("-----多线程创建结束-----");
  26. }
  27. }


3.使用匿名内部类方式

  1. System.out.println("-----多线程创建开始-----");
  2. Thread thread = new Thread(new Runnable() {
  3. public void run() {
  4. for (int i = 0; i< 10; i++) {
  5. System.out.println("i:" + i);
  6. }
  7. }
  8. });
  9. thread.start();
  10. System.out.println("-----多线程创建结束-----");

问题:
1.使用哪种方式创建线程更好?
答:使用实现实现Runnable接口好,原因实现了接口还可以继续继承,继承了类不能再继承。

2.启动线程是使用调用start方法还是run方法?
答:开启线程不是调用run方法,而是start方法,调用run方法只是普通的实例方法调用

获取线程对象以及名称

**常用线程api
start() 启动线程
currentThread() 获取当前线程对象
getID() 获取当前线程ID Thread-编号 该编号从0开始
getName() 获取当前线程名称
sleep(long mill) 休眠线程
Stop() 停止线程,
常用线程构造函数
Thread() 分配一个新的 Thread 对象
Thread(String name) 分配一个新的 Thread对象,具有指定的 name正如其名。
Thread(Runable r) 分配一个新的 Thread对象
Thread(Runable r, String name) 分配一个新的 Thread对象

守护线程


Java中有两种线程,一种是用户线程,另一种是守护线程。
用户线程是指用户自定义创建的线程,主线程停止,用户线程不会停止
守护线程当进程不存在或主线程停止,守护线程也会被停止。

使用setDaemon(true)方法设置为守护线程

  1. **
  2. * 什么是守护线程? 守护线程 进程线程(主线程挂了) 守护线程也会被自动销毁.
  3. *
  4. public class DaemonThread {
  5. public static void main(String[] args) {
  6. Thread thread = new Thread(new Runnable() {
  7. @Override
  8. public void run() {
  9. while (true) {
  10. try {
  11. Thread.sleep(100);
  12. } catch (Exception e) {
  13. // TODO: handle exception
  14. }
  15. System.out.println("我是子线程...");
  16. }
  17. }
  18. });
  19. thread.setDaemon(true);
  20. thread.start();
  21. for (int i = 0; i < 10; i++) {
  22. try {
  23. Thread.sleep(100);
  24. } catch (Exception e) {
  25. }
  26. System.out.println("我是主线程");
  27. }
  28. System.out.println("主线程执行完毕!");
  29. }
  30. }

多线程运行状态

image.png

线程从创建、运行到结束总是处于下面五个状态之一:新建状态、就绪状态、运行状态、阻塞状态及死亡状态


1.新建状态:


当用new操作符创建一个线程时, 例如new Thread(r),线程还没有开始运行,此时线程处在新建状态。 当一个线程处于新生状态时,程序还没有开始运行线程中的代码

2.就绪状态:


一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。当线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态。
处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。因为在单CPU的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个线程处于就绪状态。对多个处于就绪状态的线程是由Java运行时系统的线程调度程序(thread scheduler)来调度的。

3.运行状态:


当线程获得CPU时间后,它才进入运行状态,真正开始执行run()方法.

4.阻塞状态:

线程运行过程中,可能由于各种原因进入阻塞状态:
1>线程通过调用sleep方法进入睡眠状态;
2>线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者;
3>线程试图得到一个锁,而该锁正被其他线程持有;
4>线程在等待某个触发条件;

5.死亡状态:

有两个原因会导致线程死亡:
1) run方法正常退出而自然死亡,
2) 一个未捕获的异常终止了run方法而使线程猝死。 为了确定线程在当前是否存活着(就是要么是可运行的,要么是被阻塞了),需要使用isAlive方法。如果是可运行或被阻塞,这个方法返回true; 如果线程仍旧是new状态且不是可运行的, 或者线程死亡了,则返回false.

join()方法作用


join作用是让其他线程变为等待, t1.join();// 让其他线程变为等待,直到当前t1线程执行完毕,才释放。

thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。

需求 : 创建一个线程,子线程执行完毕后,主线程才能执行

  1. class JoinThread implements Runnable {
  2. public void run() {
  3. for (int i = 0; i < 100; i++) {
  4. System.out.println(Thread.currentThread().getName() + "---i:" + i);
  5. }
  6. }
  7. }
  8. /**
  9. * @classDesc: 功能描述:(Join方法)
  10. */
  11. public class JoinThreadDemo {
  12. public static void main(String[] args) {
  13. JoinThread joinThread = new JoinThread();
  14. Thread t1 = new Thread(joinThread);
  15. Thread t2 = new Thread(joinThread);
  16. t1.start();
  17. t2.start();
  18. try {
  19. //其他线程变为等待状态,等t1线程执行完成之后才能执行join方法。
  20. t1.join();
  21. } catch (Exception e) {
  22. }
  23. for (int i = 0; i < 100; i++) {
  24. System.out.println("main ---i:" + i);
  25. }
  26. }
  27. }

优先级
**现代操作系统基本采用时分的形式调度运行的线程,线程分配得到的时间片的多少决定了线程使用处理器资源的多少,也对应了线程优先级这个概念。在JAVA线程中,通过一个int priority来控制优先级,范围为1-10,其中10最高,默认值为5。下面是源码(基于1.8)中关于priority的一些量和方法。

  1. class PrioritytThread implements Runnable {
  2. public void run() {
  3. for (int i = 0; i < 100; i++) {
  4. System.out.println(Thread.currentThread().toString() + "---i:" + i);
  5. }
  6. }
  7. }
  8. /**
  9. * @classDesc: 功能描述:(Join方法)
  10. */
  11. public class ThreadDemo4 {
  12. public static void main(String[] args) {
  13. PrioritytThread prioritytThread = new PrioritytThread();
  14. Thread t1 = new Thread(prioritytThread);
  15. Thread t2 = new Thread(prioritytThread);
  16. t1.start();
  17. // 注意设置了优先级, 不代表每次都一定会被执行。 只是CPU调度会有限分配
  18. t1.setPriority(10);
  19. t2.start();
  20. }
  21. }

Yield方法

Thread.yield()方法的作用:暂停当前正在执行的线程,并执行其他线程。(可能没有效果)
yield()让当前正在运行的线程回到可运行状态,以允许具有相同优先级的其他线程获得运行的机会。因此,使用yield()的目的是让具有相同优先级的线程之间能够适当的轮换执行。但是,实际中无法保证yield()达到让步的目的,因为,让步的线程可能被线程调度程序再次选中。
结论:大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。

问题:
1.现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行?

代码:

  1. public class JoinThreadDemo02 {
  2. public static void main(String[] args) {
  3. Thread t1 = new Thread(new Runnable() {
  4. public void run() {
  5. for (int i = 0; i < 20; i++) {
  6. System.out.println("t1,i:" + i);
  7. }
  8. }
  9. });
  10. Thread t2 = new Thread(new Runnable() {
  11. public void run() {
  12. try {
  13. t1.join();
  14. } catch (Exception e) {
  15. // TODO: handle exception
  16. }
  17. for (int i = 0; i < 20; i++) {
  18. System.out.println("t2,i:" + i);
  19. }
  20. }
  21. });
  22. Thread t3 = new Thread(new Runnable() {
  23. public void run() {
  24. try {
  25. t2.join();
  26. } catch (Exception e) {
  27. // TODO: handle exception
  28. }
  29. for (int i = 0; i < 20; i++) {
  30. System.out.println("t3,i:" + i);
  31. }
  32. }
  33. });
  34. t1.start();
  35. t2.start();
  36. t3.start();
  37. }
  38. }

线程安全问题

当多个线程同时共享,同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。但是做读操作是不会发生数据冲突问题。

案例:需求现在有100张火车票,有两个窗口同时抢火车票,请使用多线程模拟抢票效果。

  1. class ThreadTrain1 implements Runnable {
  2. private int count = 100;
  3. private static Object oj = new Object();
  4. @Override
  5. public void run() {
  6. while (count > 0) {
  7. try {
  8. Thread.sleep(50);
  9. } catch (Exception e) {
  10. // TODO: handle exception
  11. }
  12. sale();
  13. }
  14. }
  15. public void sale() {
  16. // 前提 多线程进行使用、多个线程只能拿到一把锁。
  17. // 保证只能让一个线程 在执行 缺点效率降低
  18. // synchronized (oj) {
  19. // if (count > 0) {
  20. System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - count + 1) + "票");
  21. count--;
  22. // }
  23. // }
  24. }
  25. }
  26. public class ThreadDemo {
  27. public static void main(String[] args) {
  28. ThreadTrain1 threadTrain1 = new ThreadTrain1();
  29. Thread t1 = new Thread(threadTrain1, "①号窗口");
  30. Thread t2 = new Thread(threadTrain1, "②号窗口");
  31. t1.start();
  32. t2.start();
  33. }
  34. }

运行结果:

image.png

一号窗口和二号窗口同时出售火车第一张和第七张,部分火车票会重复出售。
结论发现,多个线程共享同一个全局成员变量时,做写的操作可能会发生数据冲突问题。

线程安全解决办法:

1.同步代码块


什么是同步代码块?
答:就是将可能会发生线程安全问题的代码,给包括起来。
synchronized(同一个数据){
可能会发生线程冲突问题
}
**
好处:解决了多线程的安全问题
弊端:多个线程需要判断锁,较为消耗资源、抢锁的资源。

  1. private static Object oj = new Object();
  2. public void sale() {
  3. // 前提 多线程进行使用、多个线程只能拿到一把锁。
  4. // 保证只能让一个线程 在执行 缺点效率降低
  5. synchronized (oj) {
  6. if (count > 0) {
  7. System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - count + 1) + "票");
  8. count--;
  9. }
  10. }
  11. }

2.同步函数


什么是同步函数?
答:在方法上修饰synchronized 称为同步函数

代码样例:

  1. public synchronized void sale() {
  2. if (trainCount > 0) {
  3. try {
  4. Thread.sleep(40);
  5. } catch (Exception e) {
  6. }
  7. System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "张票.");
  8. trainCount--;
  9. }
  10. }

同步函数用的是什么锁?

答:同步函数使用this锁。
证明方式: 一个线程使用同步代码块(this明锁),另一个线程使用同步函数。如果两个线程抢票不能实现同步,那么会出现数据错误。

  1. package com.itmayiedu;
  2. class ThreadTrain2 implements Runnable {
  3. private int count = 100;
  4. public boolean flag = true;
  5. private static Object oj = new Object();
  6. @Override
  7. public void run() {
  8. if (flag) {
  9. while (count > 0) {
  10. synchronized (this) {
  11. if (count > 0) {
  12. try {
  13. Thread.sleep(50);
  14. } catch (Exception e) {
  15. // TODO: handle exception
  16. }
  17. System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - count + 1) + "票");
  18. count--;
  19. }
  20. }
  21. }
  22. } else {
  23. while (count > 0) {
  24. sale();
  25. }
  26. }
  27. }
  28. public synchronized void sale() {
  29. // 前提 多线程进行使用、多个线程只能拿到一把锁。
  30. // 保证只能让一个线程 在执行 缺点效率降低
  31. // synchronized (oj) {
  32. if (count > 0) {
  33. try {
  34. Thread.sleep(50);
  35. } catch (Exception e) {
  36. // TODO: handle exception
  37. }
  38. System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - count + 1) + "票");
  39. count--;
  40. }
  41. // }
  42. }
  43. }
  44. public class ThreadDemo2 {
  45. public static void main(String[] args) throws InterruptedException {
  46. ThreadTrain2 threadTrain1 = new ThreadTrain2();
  47. Thread t1 = new Thread(threadTrain1, "①号窗口");
  48. Thread t2 = new Thread(threadTrain1, "②号窗口");
  49. t1.start();
  50. Thread.sleep(40);
  51. threadTrain1.flag = false;
  52. t2.start();
  53. }
  54. }

静态同步函数


答:什么是静态同步函数?

方法上加上static关键字,使用synchronized 关键字修饰 或者使用类.class文件。
静态的同步函数使用的锁是 该函数所属字节码文件对象
可以用 getClass方法获取,也可以用当前 类名.class 表示。
代码样例:

  1. synchronized (ThreadTrain.class) {
  2. System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "张票.");
  3. trainCount--;
  4. try {
  5. Thread.sleep(100);
  6. } catch (Exception e) {
  7. }
  8. }

总结:
synchronized 修饰方法使用锁是当前this锁。
synchronized 修饰静态方法使用锁是当前类的字节码文件

多线程死锁


同步中嵌套同步,导致锁无法释放

代码:

  1. package com.itmayiedu;
  2. class ThreadTrain6 implements Runnable {
  3. // 这是货票总票数,多个线程会同时共享资源
  4. private int trainCount = 100;
  5. public boolean flag = true;
  6. private Object mutex = new Object();
  7. @Override
  8. public void run() {
  9. if (flag) {
  10. while (true) {
  11. synchronized (mutex) {
  12. // 锁(同步代码块)在什么时候释放? 代码执行完, 自动释放锁.
  13. // 如果flag为true 先拿到 obj锁,在拿到this 锁、 才能执行。
  14. // 如果flag为false先拿到this,在拿到obj锁,才能执行。
  15. // 死锁解决办法:不要在同步中嵌套同步。
  16. sale();
  17. }
  18. }
  19. } else {
  20. while (true) {
  21. sale();
  22. }
  23. }
  24. }
  25. /**
  26. *
  27. * @methodDesc: 功能描述:(出售火车票)
  28. * @author: 余胜军
  29. * @param:
  30. * @createTime:2017年8月9日 下午9:49:11
  31. * @returnType: void
  32. * @copyright:上海每特教育科技有限公司
  33. */
  34. public synchronized void sale() {
  35. synchronized (mutex) {
  36. if (trainCount > 0) {
  37. try {
  38. Thread.sleep(40);
  39. } catch (Exception e) {
  40. }
  41. System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "张票.");
  42. trainCount--;
  43. }
  44. }
  45. }
  46. }
  47. public class DeadlockThread {
  48. public static void main(String[] args) throws InterruptedException {
  49. ThreadTrain6 threadTrain = new ThreadTrain6(); // 定义 一个实例
  50. Thread thread1 = new Thread(threadTrain, "一号窗口");
  51. Thread thread2 = new Thread(threadTrain, "二号窗口");
  52. thread1.start();
  53. Thread.sleep(40);
  54. threadTrain.flag = false;
  55. thread2.start();
  56. }
  57. }

多线程三大特性

原子性、可见性、有序性
**


原子性

即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
一个很经典的例子就是银行账户转账问题:
比如从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。这2个操作必须要具备原子性才能保证不出现一些意外的问题。
我们操作数据也是如此,比如i = i+1;其中就包括,读取i的值,计算i,写入i。这行代码在Java中是不具备原子性的,则多线程运行肯定会出问题,所以也需要我们使用同步和lock这些东西来确保这个特性了。
原子性其实就是保证数据一致、线程安全一部分,

可见性

当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
若两个线程在不同的cpu,那么线程1改变了i的值还没刷新到主存,线程2又使用了i,那么这个i值肯定还是之前的,线程1对变量的修改线程没看到这就是可见性问题。

有序性

程序执行的顺序按照代码的先后顺序执行。
一般来说处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。如下:
int a = 10; //语句1
int r = 2; //语句2
a = a + 3; //语句3
r = a*a; //语句4
则因为重排序,他还可能执行顺序为 2-1-3-4,1-3-2-4
但绝不可能 2-1-4-3,因为这打破了依赖关系。
显然重排序对单线程运行是不会有任何问题,而多线程就不一定了,所以我们在多线程编程时就得考虑这个问题了。

Java内存模型


共享内存模型指的就是Java内存模型(简称JMM),JMM决定一个线程对共享变量的写入时,能对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(**main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本**。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。

image.png

从上图来看,线程A与线程B之间如要通信的话,必须要经历下面2个步骤:
1. 首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去。
2. 然后,线程B到主内存中去读取线程A之前已更新过的共享变量。
下面通过示意图来说明这两个步骤:
image.png

如上图所示,本地内存A和B有主内存中共享变量x的副本。假设初始时,这三个内存中的x值都为0。线程A在执行时,把更新后的x值(假设值为1)临时存放在自己的本地内存A中。当线程A和线程B需要通信时,线程A首先会把自己本地内存中修改后的x值刷新到主内存中,此时主内存中的x值变为了1。随后,线程B到主内存中去读取线程A更新后的x值,此时线程B的本地内存的x值也变为了1。
从整体来看,这两个步骤实质上是线程A在向线程B发送消息,而且这个通信过程必须要经过主内存。JMM通过控制主内存与每个线程的本地内存之间的交互,来为java程序员提供内存可见性保证。
总结:什么是Java内存模型:java内存模型简称jmm,定义了一个线程对另一个线程可见。共享变量存放在主内存中,每个线程都有自己的本地内存,当多个线程同时访问一个数据的时候,可能本地内存没有及时刷新到主内存,所以就会发生线程安全问题。
**


Volatile

Volatile 关键字的作用是变量在多个线程之间可见。

  1. class ThreadVolatileDemo extends Thread {
  2. public boolean flag = true;
  3. @Override
  4. public void run() {
  5. System.out.println("开始执行子线程....");
  6. while (flag) {
  7. }
  8. System.out.println("线程停止");
  9. }
  10. public void setRuning(boolean flag) {
  11. this.flag = flag;
  12. }
  13. }
  14. public class ThreadVolatile {
  15. public static void main(String[] args) throws InterruptedException {
  16. ThreadVolatileDemo threadVolatileDemo = new ThreadVolatileDemo();
  17. threadVolatileDemo.start();
  18. Thread.sleep(3000);
  19. threadVolatileDemo.setRuning(false);
  20. System.out.println("flag 已经设置成false");
  21. Thread.sleep(1000);
  22. System.out.println(threadVolatileDemo.flag);
  23. }
  24. }

运行结果:
image.png

已经将结果设置为fasle为什么?还一直在运行呢。
原因:线程之间是不可见的,读取的是副本,没有及时读取到主内存结果。
解决办法使用Volatile关键字将解决线程之间可见性, 强制线程每次读取该值的时候都去“主内存”中取值

Volatile非原子性

  1. public class VolatileNoAtomic extends Thread {
  2. private static volatile int count;
  3. // private static AtomicInteger count = new AtomicInteger(0);
  4. private static void addCount() {
  5. for (int i = 0; i < 1000; i++) {
  6. count++;
  7. // count.incrementAndGet();
  8. }
  9. System.out.println(count);
  10. }
  11. public void run() {
  12. addCount();
  13. }
  14. public static void main(String[] args) {
  15. VolatileNoAtomic[] arr = new VolatileNoAtomic[100];
  16. for (int i = 0; i < 10; i++) {
  17. arr[i] = new VolatileNoAtomic();
  18. }
  19. for (int i = 0; i < 10; i++) {
  20. arr[i].start();
  21. }
  22. }
  23. }

运行结果:
image.png
结果发现 数据不同步,因为Volatile不用具备原子性。

使用AtomicInteger原子类

AtomicInteger是一个提供原子操作的Integer类,通过线程安全的方式操作加减。

  1. public class VolatileNoAtomic extends Thread {
  2. static int count = 0;
  3. private static AtomicInteger atomicInteger = new AtomicInteger(0);
  4. @Override
  5. public void run() {
  6. for (int i = 0; i < 1000; i++) {
  7. //等同于i++
  8. atomicInteger.incrementAndGet();
  9. }
  10. System.out.println(count);
  11. }
  12. public static void main(String[] args) {
  13. // 初始化10个线程
  14. VolatileNoAtomic[] volatileNoAtomic = new VolatileNoAtomic[10];
  15. for (int i = 0; i < 10; i++) {
  16. // 创建
  17. volatileNoAtomic[i] = new VolatileNoAtomic();
  18. }
  19. for (int i = 0; i < volatileNoAtomic.length; i++) {
  20. volatileNoAtomic[i].start();
  21. }
  22. }
  23. }


volatile与synchronized区别


仅靠volatile不能保证线程的安全性。(原子性)
①volatile轻量级,只能修饰变量。synchronized重量级,还可修饰方法
②volatile只能保证数据的可见性,不能用来同步,因为多个线程并发访问volatile修饰的变量不会阻塞。
synchronized不仅保证可见性,而且还保证原子性,因为,只有获得了锁的线程才能进入临界区,从而保证临界区中的所有语句都全部执行。多个线程争抢synchronized锁对象时,会出现阻塞。
线程安全性
线程安全性包括两个方面,①可见性。②原子性。
从上面自增的例子中可以看出:仅仅使用volatile并不能保证线程安全性。而synchronized则可实现线程的安全性。

ThreadLocal

ThreadLocal提高一个线程的局部变量,访问某个线程拥有自己局部变量。
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
ThreadLocal的接口方法
ThreadLocal类接口很简单,只有4个方法,我们先来了解一下:

  • void set(Object value)设置当前线程的线程局部变量的值。
  • public Object get()该方法返回当前线程所对应的线程局部变量。
  • public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
  • protected Object initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。

案例:创建三个线程,每个线程生成自己独立序列号。
代码:

  1. class Res {
  2. // 生成序列号共享变量
  3. public static Integer count = 0;
  4. public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
  5. protected Integer initialValue() {
  6. return 0;
  7. };
  8. };
  9. public Integer getNum() {
  10. int count = threadLocal.get() + 1;
  11. threadLocal.set(count);
  12. return count;
  13. }
  14. }
  15. public class ThreadLocaDemo2 extends Thread {
  16. private Res res;
  17. public ThreadLocaDemo2(Res res) {
  18. this.res = res;
  19. }
  20. @Override
  21. public void run() {
  22. for (int i = 0; i < 3; i++) {
  23. System.out.println(Thread.currentThread().getName() + "---" + "i---" + i + "--num:" + res.getNum());
  24. }
  25. }
  26. public static void main(String[] args) {
  27. Res res = new Res();
  28. ThreadLocaDemo2 threadLocaDemo1 = new ThreadLocaDemo2(res);
  29. ThreadLocaDemo2 threadLocaDemo2 = new ThreadLocaDemo2(res);
  30. ThreadLocaDemo2 threadLocaDemo3 = new ThreadLocaDemo2(res);
  31. threadLocaDemo1.start();
  32. threadLocaDemo2.start();
  33. threadLocaDemo3.start();
  34. }
  35. }

ThreadLoca实现原理:
ThreadLoca通过map集合
Map.put(“当前线程”,值);


问题:设计4个线程,其中两个线程每次对j增加1,另外两个线程对j每次减少1。写出程序。

多线程之间通讯

需求:第一个线程写入(input)用户,另一个线程取读取(out)用户.实现读一个,写一个操作。

image.png

代码实现基本实现:

共享资源源实体类:

  1. class Res {
  2. public String userSex;
  3. public String userName;
  4. }

输入线程资源:

  1. class IntThrad extends Thread {
  2. private Res res;
  3. public IntThrad(Res res) {
  4. this.res = res;
  5. }
  6. @Override
  7. public void run() {
  8. int count = 0;
  9. while (true) {
  10. if (count == 0) {
  11. res.userName = "余胜军";
  12. res.userSex = "男";
  13. } else {
  14. res.userName = "小紅";
  15. res.userSex = "女";
  16. }
  17. count = (count + 1) % 2;
  18. }
  19. }
  20. }

输出线程:

  1. class OutThread extends Thread {
  2. private Res res;
  3. public OutThread(Res res) {
  4. this.res = res;
  5. }
  6. @Override
  7. public void run() {
  8. while (true) {
  9. System.out.println(res.userName + "--" + res.userSex);
  10. }
  11. }
  12. }

运行代码

  1. Res res = new Res();
  2. IntThrad intThrad = new IntThrad(res);
  3. OutThread outThread = new OutThread(res);
  4. intThrad.start();
  5. outThread.start();

运行代码

image.png


注意:数据发生错乱,造成线程安全问题

解决线程安全问题

IntThrad 加上synchronized

  1. class IntThrad extends Thread {
  2. private Res res;
  3. public IntThrad(Res res) {
  4. this.res = res;
  5. }
  6. @Override
  7. public void run() {
  8. int count = 0;
  9. while (true) {
  10. synchronized (res) {
  11. if (count == 0) {
  12. res.userName = "余胜军";
  13. res.userSex = "男";
  14. } else {
  15. res.userName = "小紅";
  16. res.userSex = "女";
  17. }
  18. count = (count + 1) % 2;
  19. }
  20. }
  21. }
  22. }

输出线程加上synchronized

  1. class Res {
  2. public String userName;
  3. public String sex;
  4. }
  5. class InputThread extends Thread {
  6. private Res res;
  7. public InputThread(Res res) {
  8. this.res = res;
  9. }
  10. @Override
  11. public void run() {
  12. int count = 0;
  13. while (true) {
  14. synchronized (res) {
  15. if (count == 0) {
  16. res.userName = "余胜军";
  17. res.sex = "男";
  18. } else {
  19. res.userName = "小红";
  20. res.sex = "女";
  21. }
  22. count = (count + 1) % 2;
  23. }
  24. }
  25. }
  26. }
  27. class OutThrad extends Thread {
  28. private Res res;
  29. public OutThrad(Res res) {
  30. this.res = res;
  31. }
  32. @Override
  33. public void run() {
  34. while (true) {
  35. synchronized (res) {
  36. System.out.println(res.userName + "," + res.sex);
  37. }
  38. }
  39. }
  40. }
  41. public class ThreadDemo01 {
  42. public static void main(String[] args) {
  43. Res res = new Res();
  44. InputThread inputThread = new InputThread(res);
  45. OutThrad outThrad = new OutThrad(res);
  46. inputThread.start();
  47. outThrad.start();
  48. }
  49. }

wait()、notify、notifyAll()方法

wait()、notify()、notifyAll()是三个定义在Object类里的方法,可以用来控制线程的状态。
这三个方法最终调用的都是jvm级的native方法。随着jvm运行平台的不同可能有些许差异。
如果对象调用了wait方法就会使持有该对象的线程把该对象的控制权交出去,然后处于等待状态。
如果对象调用了notify方法就会通知某个正在等待这个对象的控制权的线程可以继续运行。
如果对象调用了notifyAll方法就会通知所有等待这个对象控制权的线程继续运行。
注意:一定要在线程同步中使用,并且是同一个锁的资源

  1. class Res {
  2. public String userSex;
  3. public String userName;
  4. //线程通讯标识
  5. public boolean flag = false;
  6. }
  1. class IntThrad extends Thread {
  2. private Res res;
  3. public IntThrad(Res res) {
  4. this.res = res;
  5. }
  6. @Override
  7. public void run() {
  8. int count = 0;
  9. while (true) {
  10. synchronized (res) {
  11. if (res.flag) {
  12. try {
  13. // 当前线程变为等待,但是可以释放锁
  14. res.wait();
  15. } catch (Exception e) {
  16. }
  17. }
  18. if (count == 0) {
  19. res.userName = "余胜军";
  20. res.userSex = "男";
  21. } else {
  22. res.userName = "小紅";
  23. res.userSex = "女";
  24. }
  25. count = (count + 1) % 2;
  26. res.flag = true;
  27. // 唤醒当前线程
  28. res.notify();
  29. }
  30. }
  31. }
  32. }
  1. class OutThread extends Thread {
  2. private Res res;
  3. public OutThread(Res res) {
  4. this.res = res;
  5. }
  6. @Override
  7. public void run() {
  8. while (true) {
  9. synchronized (res) {
  10. if (!res.flag) {
  11. try {
  12. res.wait();
  13. } catch (Exception e) {
  14. // TODO: handle exception
  15. }
  16. }
  17. System.out.println(res.userName + "--" + res.userSex);
  18. res.flag = false;
  19. res.notify();
  20. }
  21. }
  22. }
  23. }
  1. public class ThreaCommun {
  2. public static void main(String[] args) {
  3. Res res = new Res();
  4. IntThrad intThrad = new IntThrad(res);
  5. OutThread outThread = new OutThread(res);
  6. intThrad.start();
  7. outThread.start();
  8. }
  9. }

wait与sleep区别?

对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。
sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。
在调用sleep()方法的过程中,线程不会释放对象锁。
而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备
获取对象锁进入运行状态。

JDK1.5-Lock

在 jdk1.5 之后,并发包中新增了 Lock 接口(以及相关实现类)用来实现锁功能,Lock 接口提供了与 synchronized 关键字类似的同步功能,但需要在使用时手动获取锁和释放锁。

相关方法:
void lock():获取锁,调用该方法当前线程将会获取锁,当锁获得后,从该方法返回。

void lockInterruptibly() throws InterruptedException:可中断地获取锁,和lock()方法不同之处在于该方法会响应中断,即在锁的获取中可以中断当前线程。

boolean tryLock():尝试非阻塞的获取锁,调用该方法后立即返回,如果能够获取则返回true,不能则返回false。

boolean tryLock(long time, TimeUnit unit) throws InterruptedException:超时的获取锁,当前线程会在下面情况下返回:当前线程在超时时间内获取了锁、当前线程在超时时间内被中断、超时时间结束返回false。

void unlock():释放锁。

Condition newCondition():获取等待通知组件,该组件和当前的锁绑定,当前线程只有获取了锁,才能调用该组件的wait()方法,调用后,当前线程将释放锁。

Lock写法

  1. Lock lock = new ReentrantLock();
  2. lock.lock();
  3. try{
  4. //可能会出现线程安全的操作
  5. }finally{
  6. //一定在finally中释放锁
  7. //也不能把获取锁在try中进行,因为有可能在获取锁的时候抛出异常
  8. lock.ublock();
  9. }

Lock 接口与 synchronized 关键字的区别

1.Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

2.Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问;
3.lock锁可以判断是否获取锁,并且可以中断锁;synchronized无法中断,要是无法获取锁会一直阻塞。

Lock 接口可以尝试非阻塞地获取锁 当前线程尝试获取锁。如果这一时刻锁没有被其他线程获取到,则成功获取并持有锁。 Lock 接口能被中断地获取锁 与 synchronized 不同,获取到锁的线程能够响应中断,当获取到的锁的线程被中断时,中断异常将会被抛出,同时锁会被释放。
Lock 接口在指定的截止时间之前获取锁,如果截止时间到了依旧无法获取锁,则返回。
通过Lock得知线程有没有成功获取到锁,synchronized 无法做到
为什么拥有synchronized jdk1.5还要出现lock锁?
synchronized 的缺陷:
1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有;
  2)线程执行发生异常,此时JVM会让线程自动释放锁。
  那么如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,试想一下,这多么影响程序执行效率。
  因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过Lock就可以办到。
**

Condition用法

Condition的功能类似于在传统的线程技术中的,Object.wait()和Object.notify()的功能。


Condition condition = lock.newCondition();
res. condition.await(); **类似**wait

res. Condition. Signal() 类似notify
**

  1. class Res {
  2. public String userName;
  3. public String sex;
  4. public boolean flag = false;
  5. Lock lock = new ReentrantLock();
  6. }
  7. class InputThread extends Thread {
  8. private Res res;
  9. Condition newCondition;
  10. public InputThread(Res res, Condition newCondition) {
  11. this.res = res;
  12. this.newCondition=newCondition;
  13. }
  14. @Override
  15. public void run() {
  16. int count = 0;
  17. while (true) {
  18. // synchronized (res) {
  19. try {
  20. res.lock.lock();
  21. if (res.flag) {
  22. try {
  23. // res.wait();
  24. newCondition.await();
  25. } catch (Exception e) {
  26. // TODO: handle exception
  27. }
  28. }
  29. if (count == 0) {
  30. res.userName = "余胜军";
  31. res.sex = "男";
  32. } else {
  33. res.userName = "小红";
  34. res.sex = "女";
  35. }
  36. count = (count + 1) % 2;
  37. res.flag = true;
  38. // res.notify();
  39. newCondition.signal();
  40. } catch (Exception e) {
  41. // TODO: handle exception
  42. }finally {
  43. res.lock.unlock();
  44. }
  45. }
  46. // }
  47. }
  48. }
  49. class OutThrad extends Thread {
  50. private Res res;
  51. private Condition newCondition;
  52. public OutThrad(Res res,Condition newCondition) {
  53. this.res = res;
  54. this.newCondition=newCondition;
  55. }
  56. @Override
  57. public void run() {
  58. while (true) {
  59. // synchronized (res) {
  60. try {
  61. res.lock.lock();
  62. if (!res.flag) {
  63. try {
  64. // res.wait();
  65. newCondition.await();
  66. } catch (Exception e) {
  67. // TODO: handle exception
  68. }
  69. }
  70. System.out.println(res.userName + "," + res.sex);
  71. res.flag = false;
  72. // res.notify();
  73. newCondition.signal();
  74. } catch (Exception e) {
  75. // TODO: handle exception
  76. }finally {
  77. res.lock.unlock();
  78. }
  79. // }
  80. }
  81. }
  82. }
  83. public class ThreadDemo01 {
  84. public static void main(String[] args) {
  85. Res res = new Res();
  86. Condition newCondition = res.lock.newCondition();
  87. InputThread inputThread = new InputThread(res,newCondition);
  88. OutThrad outThrad = new OutThrad(res,newCondition);
  89. inputThread.start();
  90. outThrad.start();
  91. }
  92. }

如何停止线程?

停止线程思路
1. 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
2. 使用stop方法强行终止线程(这个方法不推荐使用,因为stop和suspend、resume一样,也可能发生不可预料的结果)。
3. 使用interrupt方法中断线程。

  1. class StopThread implements Runnable {
  2. private boolean flag = true;
  3. @Override
  4. public synchronized void run() {
  5. while (flag) {
  6. try {
  7. wait();
  8. } catch (Exception e) {
  9. //e.printStackTrace();
  10. stopThread();
  11. }
  12. System.out.println("thread run..");
  13. }
  14. }
  15. /**
  16. *
  17. * @methodDesc: 功能描述:(停止线程)
  18. * @author: 余胜军
  19. * @param:
  20. * @createTime:2017年8月20日 下午8:07:34
  21. * @returnType: void
  22. * @copyright:上海每特教育科技有限公司
  23. */
  24. public void stopThread() {
  25. flag = false;
  26. }
  27. }
  28. /**
  29. *
  30. * @classDesc: 功能描述:(停止线程)
  31. * @author: 余胜军
  32. * @createTime: 2017年8月20日 下午8:05:25
  33. * @version: v1.0
  34. * @copyright:上海每特教育科技有限公司
  35. */
  36. public class StopThreadDemo {
  37. public static void main(String[] args) {
  38. StopThread stopThread1 = new StopThread();
  39. Thread thread1 = new Thread(stopThread1);
  40. Thread thread2 = new Thread(stopThread1);
  41. thread1.start();
  42. thread2.start();
  43. int i = 0;
  44. while (true) {
  45. System.out.println("thread main..");
  46. if (i == 300) {
  47. // stopThread1.stopThread();
  48. thread1.interrupt();
  49. thread2.interrupt();
  50. break;
  51. }
  52. i++;
  53. }
  54. }
  55. }

使用interrupt停止线程

相关方法:
interrupt():其作用是中断此线程(此线程不一定是当前线程,而是指调用该方法的Thread实例所代表的线程),但实际上只是给线程设置一个中断标志,线程仍会继续运行。
image.png

interrupted():内部实现是调用的当前线程的isInterrupted(),并且会重置当前线程的中断状态(即变回false)
image.png

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

https://blog.csdn.net/zhuyong7/article/details/80852884

Thread.interrupted()如果为false,说明没有中断线程

相关代码

  1. /**
  2. * 线程的中断,使用interrupt方法,一定是自己线程内部对线程进行处理
  3. */
  4. class Interrupt{
  5. public static void main(String[] args) throws InterruptedException {
  6. Thread t= new Thread(){
  7. @Override
  8. public void run() {
  9. while (!interrupted()){
  10. System.out.println(Thread.currentThread().getName()+"状态: is run!");
  11. }
  12. System.out.println("线程中断标志为:"+interrupted());
  13. }
  14. };
  15. Thread t1=new Thread(new Runnable() {
  16. @Override
  17. public void run() {
  18. while (!Thread.currentThread().isInterrupted()){
  19. System.out.println(Thread.currentThread().getName()+"状态: is run!");
  20. }
  21. System.out.println("线程中断标志为:"+Thread.currentThread().isInterrupted());
  22. }
  23. });
  24. //使用Thread类实现
  25. // t.start();
  26. // Thread.sleep(2000);
  27. // t.interrupt();
  28. // 使用Runnable实现
  29. t1.start();
  30. Thread.sleep(2000);
  31. t1.interrupt();
  32. }
  33. }
  1. /**
  2. * 线程的中断,使用interrupt方法,抛出异常时,线程标志位会复位,变成false,无法进行中断了,在捕获异常的时候再次执行该方法
  3. */
  4. class InterruptThrow{
  5. public static void main(String[] args) throws InterruptedException {
  6. Thread t= new Thread(){
  7. @Override
  8. public void run() {
  9. while (!interrupted()){
  10. System.out.println(Thread.currentThread().getName()+"状态: is run!");
  11. }
  12. System.out.println("线程中断标志为:"+interrupted());
  13. }
  14. };
  15. Thread t1=new Thread(new Runnable() {
  16. @Override
  17. public void run() {
  18. while (!Thread.currentThread().isInterrupted()){
  19. try {
  20. Thread.sleep(200);
  21. } catch (InterruptedException e) {
  22. System.out.println("线程中断标志为:"+Thread.currentThread().isInterrupted());
  23. Thread.currentThread().interrupt();
  24. e.printStackTrace();
  25. }
  26. System.out.println(Thread.currentThread().getName()+"状态: is run!");
  27. }
  28. System.out.println("线程中断标志为:"+Thread.currentThread().isInterrupted());
  29. }
  30. });
  31. //使用Thread类实现
  32. // t.start();
  33. // Thread.sleep(2000);
  34. // t.interrupt();
  35. // 使用Runnable实现
  36. t1.start();
  37. Thread.sleep(2000);
  38. t1.interrupt();
  39. }
  40. }


**