参考:https://frxcat.fun/
Java 多线程
线程的相关概念
程序
 01
01进程
 02
02什么是线程
 03
03其他相关概念
 04
04 05
05线程的基本使用
创建线程的两种方式
- 继承Thread类,重写run方法。
- 实现Runnable接口,重写run方法。
线程应用案例 1-继承 Thread 类
 06
06| 12
 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
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 
 | package study.threaduse;
 public class Thread01 {
 public static void main(String[] args) throws InterruptedException{
 
 Cat cat = new Cat();
 cat.start();
 
 System.out.println("主线程继续执行"+Thread.currentThread().getName());
 for (int i = 0; i < 10; i++) {
 System.out.println("主线程i"+i);
 
 Thread.sleep(1000);
 }
 
 }
 
 }
 
 
 
 
 
 
 
 
 class Cat extends Thread{
 int times=0;
 public void run(){
 
 while(true){
 System.out.println("喵喵,我是小喵咪"+(++times)+" 线程名"+Thread.currentThread().getName());
 try {
 Thread.sleep(1000);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 if(times==8){
 break;
 }
 }
 }
 
 }
 
 | 
线程应用案例 2-实现 Runnable 接口
- java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时在用继承Thread类方法来创建线程显然不可能了。
- java设计者们提供了另外一个方式创建线程,就是通过实现Runnable接口来创建线程。
请编写程序,该程序可以每隔1秒。在控制台输出“hi!”,当输出10次后,自动退出。请使用实现Runnable接口的方式实现。Thread02.java,这里底层使用了设计模式[代理模式=>代码模拟实现Runnable接口开发线程的机制
| 12
 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
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 
 | package study.threaduse;
 public class Thread02 {
 public static void main(String[] args) throws InterruptedException {
 
 
 
 
 Tiger tiger = new Tiger();
 ThreadProxy threadProxy = new ThreadProxy(tiger);
 threadProxy.start();
 }
 
 }
 class Animal{}
 class Tiger extends Animal implements Runnable{
 
 @Override
 public void run() {
 System.out.println("老虎嗷嗷叫");
 }
 }
 
 class ThreadProxy implements Runnable{
 private Runnable target=null;
 
 @Override
 public void run() {
 if(target!=null){
 target.run();
 }
 }
 
 public ThreadProxy(Runnable target) {
 this.target = target;
 }
 public void start(){
 start0();
 }
 public void start0(){
 run();
 }
 }
 
 
 class Dod implements Runnable {
 
 int count = 0;
 
 @Override
 public void run() {
 while (true) {
 System.out.println("小狗汪汪叫" + (++count) + Thread.currentThread().getName());
 
 try {
 Thread.sleep(1000);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 if (count == 10) {
 break;
 }
 }
 }
 }
 
 | 
线程使用应用案例-多线程执行
请编写一个程序,创建两个线程,一个线程每隔1秒输出“hello,world”,输出10次,退出,一个线程每隔1秒输出“hi”,输出5次退出.Thread03.java
| 12
 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
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 
 | package study.threaduse;
 public class Thread03 {
 public static void main(String[] args) {
 T1 t1 = new T1();
 new Thread(t1).start();
 T2 t2 = new T2();
 new Thread(t2).start();
 new Thread(t2).start();
 
 
 }
 }
 
 class T1 implements Runnable {
 int count = 0;
 
 @Override
 public void run() {
 
 while (true) {
 System.out.println("Hello,world" + (++count));
 try {
 Thread.sleep(1000);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 if (count == 100)
 break;
 
 }
 
 }
 }
 
 class T2 implements Runnable {
 int count = 0;
 
 @Override
 public void run() {
 
 while (true) {
 System.out.println("Hi" + (++count));
 try {
 Thread.sleep(1000);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 if (count == 50)
 break;
 
 }
 
 }
 }
 
 | 
线程如何理解
 07
07 08
08继承 Thread vs 实现 Runnable 的区别
- 从java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本质上没有区别,从jdk帮助文档我们可以看到Thread类本身就实现了Runnable接口。 
- 实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制,建议使用Runnable。 
| 12
 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
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 
 | package study.threaduse.ticket.synchronized_;
 
 public class SellTicket {
 public static void main(String[] args) {
 
 
 
 
 SellTicket02 sellTicket02 = new SellTicket02();
 new Thread(sellTicket02).start();
 new Thread(sellTicket02).start();
 new Thread(sellTicket02).start();
 }
 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 class SellTicket02 implements Runnable {
 private  int tickNum = 100;
 @Override
 public void run() {
 while (true) {
 if (tickNum <= 0) {
 System.out.println("售票结束...");
 break;
 }
 
 try {
 Thread.sleep(50);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 System.out.println("窗口线程名称" + Thread.currentThread().getName() + "售出一张票" + "剩余票数为" + (--tickNum));
 
 }
 }
 
 }
 
 | 
线程终止
基本说明
- 当线程完成任务后,会自动退出。
- 还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式。
应用案列
 09
09| 12
 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
 29
 30
 31
 32
 33
 34
 35
 36
 
 | package study.threaduse.exit_;
 public class ThreadExit_ {
 public static void main(String[] args) throws InterruptedException {
 T t = new T();
 t.start();
 
 
 
 System.out.println("主线程休眠10秒");
 Thread.sleep(10*1000);
 t.setLoop(false);
 
 }
 }
 class T extends Thread{
 int count=0;
 
 private boolean loop=true;
 @Override
 public void run() {
 while (loop){
 try {
 Thread.sleep(50);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 System.out.println("T 运行中..."+count++);
 }
 }
 
 public void setLoop(boolean loop) {
 this.loop = loop;
 }
 
 }
 
 | 
线程常用方法
常用方法第一组
 10
10注意事项和细节
 11
11应用案例
 12
12| 12
 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
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 
 | package study.threaduse.method_;
 public class ThreadMethod01 {
 public static void main(String[] args) throws InterruptedException {
 
 T t = new T();
 t.setName("frx");
 t.setPriority(Thread.MIN_PRIORITY);
 t.start();
 for (int i = 0; i < 5; i++) {
 Thread.sleep(1000);
 System.out.println("hi"+i);
 }
 System.out.println(t.getName()+"程序的优先级是"+t.getPriority());
 t.interrupt();
 
 }
 }
 class T extends Thread{
 
 
 @Override
 public void run() {
 while (true) {
 for (int i = 0; i < 100; i++) {
 System.out.println(Thread.currentThread().getName() + "吃包子..");
 }
 System.out.println(Thread.currentThread().getName() + "休眠中...");
 try {
 Thread.sleep(20000);
 } catch (InterruptedException e) {
 
 
 System.out.println(Thread.currentThread().getName() + "被 interrupt了");
 }
 }
 }
 }
 
 | 
方法第二组
 13
13应用案例
 14
14| 12
 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
 29
 30
 31
 32
 
 | package study.threaduse.method_;
 public class ThreadMethod02 {
 public static void main(String[] args) throws InterruptedException {
 T2 t2 = new T2();
 t2.start();
 for (int i = 0; i <=20; i++) {
 Thread.sleep(1000);
 System.out.println("主线程(小弟)吃了"+i+"个包子");
 if(i==5){
 System.out.println("主线程让子线程 全部吃完");
 
 Thread.yield();
 
 }
 }
 
 }
 }
 class T2 extends Thread{
 @Override
 public void run() {
 for (int i = 0; i <=20; i++) {
 try {
 Thread.sleep(1000);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 System.out.println("子线程(大哥)吃了"+i+"个包子");
 }
 }
 }
 
 | 
课堂练习
 15
15| 12
 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
 29
 30
 31
 32
 33
 34
 35
 
 | package study.threaduse.method_;
 public class ThreadMethodExercise {
 public static void main(String[] args) throws InterruptedException {
 T3 t3 = new T3();
 Thread thread = new Thread(t3);
 for (int i = 0; i <=10; i++) {
 Thread.sleep(1000);
 System.out.println("hi"+i);
 if(i==5){
 thread.start();
 thread.join();
 }
 }
 System.out.println("主线程结束...");
 
 }
 }
 class T3 implements Runnable{
 @Override
 public void run() {
 for (int i = 0; i <=10; i++) {
 System.out.println("hello"+i);
 
 try {
 Thread.sleep(1000);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 
 
 }
 System.out.println("子线程结束..");
 }
 }
 
 | 
用户线程和守护线程
- 用户线程:也叫工作线程,当线程的任务执行完或通知方式结束。
- 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束。
- 常见的守护线程:垃圾回收机制。
应用案例 ThreadMethod03.java
下面我们测试如何将一个线程设置成守护线程
| 12
 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
 29
 30
 31
 
 | package study.threaduse.method_;
 public class ThreadMethod03 {
 public static void main(String[] args) throws InterruptedException {
 MyDaemonThread myDaemonThread = new MyDaemonThread();
 
 
 myDaemonThread.setDaemon(true);
 myDaemonThread.start();
 for (int i = 0; i <=10; i++) {
 System.out.println("小可爱"+i);
 Thread.sleep(1000);
 
 
 }
 
 }
 }
 class MyDaemonThread extends Thread{
 @Override
 public void run() {
 while (true){
 try {
 Thread.sleep(1000);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 System.out.println("ok");
 }
 }
 }
 
 | 
线程的生命周期
JDK 中用 Thread.State 枚举表示了线程的几种状态
 16
16线程状态转换图
 17
17写程序查看线程状态
| 12
 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
 29
 30
 31
 32
 33
 34
 35
 36
 
 | package study.threaduse.state_;
 public class ThreadState_ {
 public static void main(String[] args) throws InterruptedException {
 T t = new T();
 System.out.println(t.getName() + " 状态 " + t.getState());
 t.start();
 while (Thread.State.TERMINATED != t.getState()) {
 System.out.println(t.getName() + " 状态 " + t.getState());
 Thread.sleep(500);
 
 }
 System.out.println(t.getName() + " 状态 " + t.getState());
 }
 }
 
 class T extends Thread {
 @Override
 public void run() {
 while (true) {
 for (int i = 0; i < 10; i++) {
 System.out.println("hi" + i);
 try {
 Thread.sleep(1000);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 
 }
 break;
 
 }
 }
 
 
 }
 
 | 
线程的同步
先看一个问题
 18
18Synchronized
线程同步机制
- 在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性。也可以这里理解:
- 线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作。
同步具体方法
 19
19析同步原理
 20
20互斥锁
基本介绍
- Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。 
- 每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。 
- 关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问。 
- 同步的局限性:导致程序的执行效率要降低。 
- 同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)。 
- 同步方法(静态的)的锁为当前类本身。 
使用互斥锁来解决售票问题
| 12
 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
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 
 | package study.threaduse.ticket.synchronized_;
 public class SellTicket {
 public static void main(String[] args) {
 
 
 
 
 SellTicket02 sellTicket02 = new SellTicket02();
 new Thread(sellTicket02).start();
 new Thread(sellTicket02).start();
 new Thread(sellTicket02).start();
 }
 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 class SellTicket02 implements Runnable {
 private  int tickNum = 100;
 private boolean loop = true;
 Object object =new Object();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 public  void sell() {
 synchronized (this) {
 if (tickNum <= 0) {
 System.out.println("售票结束...");
 loop = false;
 return;
 }
 
 try {
 Thread.sleep(50);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 System.out.println("窗口线程名称" + Thread.currentThread().getName() + "售出一张票" + "剩余票数为" + (--tickNum));
 }
 }
 @Override
 public void run() {
 while (loop) {
 sell();
 }
 
 }
 }
 
 
 
 | 
注意事项和细节
- 同步方法如果没有使用static修饰:默认锁对象为this。 
- 如果方法使用static修饰,默认锁对象:当前类.class。 
- 实现的落地步骤: 
- 需要先分析上锁的代码选择同步代码块或同步方法。
- 要求多个线程的锁对象为同一个即可!
线程的死锁
基本介绍
- 多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程是一定要避免死锁的发生.
应用案列
- 妈妈:你先完成作业,才让你玩手机。 
- 小明:你先让我玩手机,我才完成作业。 
应用案例 DeadLock_.java
| 12
 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
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 
 | package study.threaduse.ticket.synchronized_;
 public class DeadLock_ {
 public static void main(String[] args) {
 
 DeadLockDemo A = new DeadLockDemo(true);
 A.setName("A线程");
 DeadLockDemo B = new DeadLockDemo(false);
 B.setName("B线程");
 A.start();
 B.start();
 
 }
 }
 class DeadLockDemo extends Thread{
 static Object o1=new Object();
 static Object o2=new Object();
 boolean flag;
 
 public DeadLockDemo(boolean flag) {
 this.flag = flag;
 }
 
 @Override
 public void run() {
 
 
 
 
 if(flag){
 synchronized (o1) {
 System.out.println(Thread.currentThread().getName() + "进入1");
 synchronized (o2) {
 System.out.println(Thread.currentThread().getName() + "进入2");
 }
 }
 }
 else {
 synchronized (o2) {
 System.out.println(Thread.currentThread().getName() + "进入3");
 synchronized (o1) {
 System.out.println(Thread.currentThread().getName() + "进入4");
 }
 }
 }
 }
 }
 
 | 
释放锁
下面操作会释放锁
- 当前线程的同步方法、同步代码块执行结束
- 当前线程在同步代码块、同步方法中遇到break.return。- 案例:没有正常的完事,经理叫他修改bug,不得已出来。
 
- 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
- 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。- 案例:没有正常完事,觉得需要酝酿下,所以出来等会再进去。
 
下面操作不会释放锁
- 线程执行同步代码块或同步方法时,程序调用Thread.sleep(),Thread.yield()方 法暂停当前线程的执行,不会释放锁。
- 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起, 该线程不会释放锁。- 提示;应尽量避免使用suspend()和resume()来控制线程,方法不再推荐使用
 
本章作业
- 在main()方法中启动两个线程。 
- 第1个线程循环随机打印100以内的整数。 
- 直到第2个线程从键盘读取了”Q“命令。 
| 12
 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
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 
 | package study.threaduse.homework;
 
 import java.util.Scanner;
 
 public class Homework01 {
 public static void main(String[] args) {
 A a = new A();
 B b = new B(a);
 a.start();
 b.start();
 }
 }
 
 
 class A extends Thread {
 private boolean loop = true;
 
 @Override
 public void run() {
 
 while (loop) {
 System.out.println((int)(Math.random() * 100 + 1));
 
 try {
 Thread.sleep(1000);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 }
 System.out.println("a线程退出...");
 
 }
 
 public void setLoop(boolean loop) {
 this.loop = loop;
 }
 }
 
 
 class B extends Thread {
 private A a;
 private Scanner scanner = new Scanner(System.in);
 
 public B(A a) {
 this.a = a;
 }
 
 @Override
 public void run() {
 while (true) {
 
 System.out.println("请输入你指令(Q)表示退出:");
 char key = scanner.next().toUpperCase().charAt(0);
 if(key == 'Q') {
 
 a.setLoop(false);
 System.out.println("b线程退出.");
 break;
 }
 }
 }
 }
 
 
 | 
- 有两个用户分别从同一个卡上取钱(总额:10000).
- 每次都取1000,当余额不足时,就不能取款了。
- 不能出现超取现象->线程同步问题。
| 12
 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
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 
 | package study.threaduse.homework;
 public class Homework02 {
 public static void main(String[] args) {
 T t = new T();
 Thread  thread= new Thread(t);
 Thread thread1 = new Thread(t);
 thread.setName("T1");
 thread1.setName("T2");
 thread.start();
 thread1.start();
 
 
 }
 }
 
 
 class T implements Runnable{
 private int money=10000;
 @Override
 public void run() {
 while (true) {
 
 
 
 
 
 synchronized (this) {
 
 if (money < 1000) {
 System.out.println("余额不足");
 break;
 }
 money -= 1000;
 System.out.println(Thread.currentThread().getName() + "取出了1000块 当前余额=" + money);
 }  try {
 Thread.sleep(1000);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 }
 }
 }
 
 |