1. 多线程基础
1.1. 线程与进程(彩蛋链接)
- 进程:是一个正在执行中的程序
- 线程:是进程的一个独立的控制单元,程序的执行路径
- 举例:Java VM main线程
- 扩展:Java VM 的垃圾回收线程
1.2. 线程的几个状态
1.3. 对线程对象和名称的操作
- Thread.currentThread() 获取对象
- getName() 获取当前线程线程名称
- 设置线程名称 setName() 或者通过构造函数
1.4. 售票问题
情景:多个窗口同时出售火车票,总票数100张
解决办法
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| package com.leeeyou.ticket;
public class SaleTicketWithProblem { public static void main(String[] args) { Ticket t1 = new Ticket(); Ticket t2 = new Ticket(); Ticket t3 = new Ticket(); Ticket t4 = new Ticket();
t1.start(); t2.start(); t3.start(); t4.start(); } }
class Ticket extends Thread { private volatile static int ticket = 50; private boolean flag = true;
@Override public void run() { while (flag) { if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":" + ticket + " "); ticket--; } else { System.out.println(Thread.currentThread().getName() + ": no ticket"); flag = false; } } }
}
|
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| package com.leeeyou.ticket;
public class SaleTicketWithProblem2 { public static void main(String[] args) { Ticket2 t = new Ticket2();
Thread t1 = new Thread(t); Thread t2 = new Thread(t); Thread t3 = new Thread(t); Thread t4 = new Thread(t);
t1.start(); t2.start(); t3.start(); t4.start(); }
}
class Ticket2 implements Runnable { public int ticket = 50; private boolean flag = true;
@Override public void run() { while (flag) { if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "...." + ticket--); } else { flag = false; System.out.println(Thread.currentThread().getName() + ": no ticket"); } } }
}
|
结果如下所示,出现了0,-1,-2号票:
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 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
| Thread-0:50 Thread-1:49 Thread-2:49 Thread-3:47 Thread-3:46 Thread-2:46 Thread-1:44 Thread-0:44 Thread-3:42 Thread-2:41 Thread-1:40 Thread-0:39 Thread-3:38 Thread-2:37 Thread-1:36 Thread-0:36 Thread-2:34 Thread-3:33 Thread-1:32 Thread-0:32 Thread-2:30 Thread-3:29 Thread-1:28 Thread-0:27 Thread-3:26 Thread-2:26 Thread-1:24 Thread-0:23 Thread-3:22 Thread-1:22 Thread-2:22 Thread-0:19 Thread-3:18 Thread-2:17 Thread-1:16 Thread-0:15 Thread-3:14 Thread-2:13 Thread-1:12 Thread-0:11 Thread-3:10 Thread-2:9 Thread-1:8 Thread-0:7 Thread-2:6 Thread-3:6 Thread-1:4 Thread-0:3 Thread-3:2 Thread-2:2 Thread-2: no ticket Thread-1:0 Thread-1: no ticket Thread-0:-1 Thread-0: no ticket Thread-3:-2 Thread-3: no ticket
|
1.5. 创建线程
区别(继承Thread类, 实现Runable接口)
- 实现的方式避免了单继承的局限性
- 线程代码存放位置不一样
- Thread 存放在Thread子类的run方法中
- Runable 存放在接口子类的run方法中
1.6. 多线程的安全问题
出问题的原因
- 当多条语句在操作同一个线程的共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程就参与进来执行,导致共享数据错误
解决办法:利用”同步代码块”
1 2 3
| synchronized(对象){ 需被同步的代码 }
|
- 上面的“对象”其实是一个同步锁,持有锁的线程可以在同步中执行,没有持有锁的线程即使获得CPU执行权,也进不去
- 如何找到“需同步的代码”? –> 看哪些语句在操作共享数据
代码实现:解决售票问题的安全性,避免出现0,-1,-2号票
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| package com.leeeyou.ticket;
public class SaleTicketSafe { public static void main(String[] args) { TicketSafe t = new TicketSafe();
Thread t1 = new Thread(t); Thread t2 = new Thread(t); Thread t3 = new Thread(t); Thread t4 = new Thread(t);
t1.start(); t2.start(); t3.start(); t4.start(); } }
class TicketSafe implements Runnable { public int ticket = 50; private boolean flag = true;
@Override public void run() { while (flag) { synchronized (this) { if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "...." + ticket--); } else { flag = false; System.out.println(Thread.currentThread().getName() + ": no ticket"); } } } }
}
|
结果如下所示:
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 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
| Thread-0....50 Thread-0....49 Thread-3....48 Thread-3....47 Thread-3....46 Thread-3....45 Thread-3....44 Thread-3....43 Thread-3....42 Thread-3....41 Thread-3....40 Thread-3....39 Thread-3....38 Thread-3....37 Thread-3....36 Thread-3....35 Thread-3....34 Thread-3....33 Thread-3....32 Thread-3....31 Thread-3....30 Thread-3....29 Thread-3....28 Thread-3....27 Thread-3....26 Thread-3....25 Thread-3....24 Thread-3....23 Thread-3....22 Thread-3....21 Thread-3....20 Thread-3....19 Thread-3....18 Thread-3....17 Thread-3....16 Thread-3....15 Thread-3....14 Thread-3....13 Thread-3....12 Thread-3....11 Thread-3....10 Thread-3....9 Thread-3....8 Thread-3....7 Thread-3....6 Thread-3....5 Thread-3....4 Thread-3....3 Thread-3....2 Thread-3....1 Thread-3: no ticket Thread-2: no ticket Thread-1: no ticket Thread-0: no ticket
|
同步的3个前提
- 两个或两个以上的线程
- 必须多个线程使用同一个锁
- 必须保证同步中只能有一个线程在运行
同步的优缺点
- 优点:解决的多线的安全问题
- 缺点:多个线程需要判断锁,较为消耗资源
1.7. 同步函数
情景:两个储户分别向银行存钱300元,每次存100,存3次
分析:该线程是否有安全问题,如何找到问题并解决?
- 明确哪些代码是多线程运行的代码
- 明确共享数据
- 明确多线程运行中哪些语句是操作共享数据的
代码实现
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 29 30 31 32 33 34
| package com.day11.bank;
public class SaveMoney { public static void main(String[] args) { Cus c = new Cus(); Thread t1 = new Thread(c); Thread t2 = new Thread(c); t1.start(); t2.start(); } } class Bank { private int count = 0 ; public synchronized void add(int n){ count += n ; try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("count = " + count); } } class Cus implements Runnable{ Bank bank = new Bank(); @Override public void run() { for(int i = 0 ; i<3; i++){ bank.add(100); } } }
|
同步的两种形式
静态同步函数
1.8. 单例设计模式
设计模式之单例模式
1.9. 死锁
出现原因:同步中嵌套同步
写一个死锁程序
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| package com.day11.deadLock;
public class DeadLock { public static void main(String[] args) { Thread t1 = new Thread(new DeadLock2(false)); Thread t2 = new Thread(new DeadLock2(true)); t1.start(); t2.start(); } }
class DeadLock2 implements Runnable{ boolean flag = true; DeadLock2(boolean flag){ this.flag = flag; } @Override public void run() { if(flag){ synchronized (LockObject.lo1) { System.out.println("if lo1"); synchronized (LockObject.lo2) { System.out.println("if lo2"); } } }else{ synchronized (LockObject.lo2) { System.out.println("else lo2"); synchronized (LockObject.lo1) { System.out.println("else lo1"); } } } } }
class LockObject { static LockObject lo1 = new LockObject(); static LockObject lo2 = new LockObject(); }
|
2. 多线程进阶
2.1. 线程间的通信
情景:利用多线程,交替显示员工的姓名性别到控制台
可能出现的问题
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| package com.day12.threadCorrespond;
public class InputOutputDemo { public static void main(String[] args) { Res r = new Res(); new Thread(new Input(r)).start(); new Thread(new Output(r)).start(); } }
class Input implements Runnable{ private Res r ; Input(Res r){ this.r = r ; } @Override public void run() { int i = 0 ; while(true){ if(i==0){ r.name = "Maroon5"; r.sex = "male"; }else{ r.name = "凯蒂佩里"; r.sex = "女女女"; } i = (i+1)%2; } } }
class Output implements Runnable{ private Res r ; Output(Res r){ this.r = r ; } @Override public void run() { while(true) System.out.println(r.name + "..." + r.sex); } }
class Res { String name; String sex; }
|
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 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
| package com.day12.threadCorrespond;
public class InputOutputDemo { public static void main(String[] args) { Res r = new Res(); new Thread(new Input(r)).start(); new Thread(new Output(r)).start(); } }
class Input implements Runnable{ private Res r ; Input(Res r){ this.r = r ; } @Override public void run() { int i = 0 ; while(true){ synchronized(r){ if(i==0){ r.name = "Maroon5"; r.sex = "male"; }else{ r.name = "凯蒂佩里"; r.sex = "女女女"; } i = (i+1)%2; } } } }
class Output implements Runnable{ private Res r ; Output(Res r){ this.r = r ; } @Override public void run() { while(true) { synchronized (r) { System.out.println(r.name + "..." + r.sex); } } } }
class Res { String name; String sex; }
|
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 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
| package com.day12.threadCorrespond;
public class InputOutputDemo { public static void main(String[] args) { Res r = new Res(); new Thread(new Input(r)).start(); new Thread(new Output(r)).start(); } }
class Input implements Runnable{ private Res r ; Input(Res r){ this.r = r ; } @Override public void run() { int i = 0 ; while(true){ synchronized(r){ if(r.flag){ try { r.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } if(i==0){ r.name = "Maroon5"; r.sex = "male"; }else{ r.name = "凯蒂佩里"; r.sex = "女女女"; } i = (i+1)%2; r.flag = true; r.notify(); } } } }
class Output implements Runnable{ private Res r ; Output(Res r){ this.r = r ; } @Override public void run() { while(true) { synchronized (r) { if(!r.flag){ try { r.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(r.name + "..." + r.sex); r.flag = false; r.notify(); } } } }
class Res { String name; String sex; boolean flag = false; }
|
代码优化
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 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 com.day12.threadCorrespond;
public class InputOutputDemo { public static void main(String[] args) { Res r = new Res();
new Thread(new Input(r)).start(); new Thread(new Output(r)).start(); } }
class Input implements Runnable { private Res r;
Input(Res r) { this.r = r; }
@Override public void run() { int i = 0; while (true) { if (i == 0) { r.set("Maroon5", "male"); } else { r.set("凯蒂佩里", "女女女"); } i = (i + 1) % 2; } } }
class Output implements Runnable { private Res r;
Output(Res r) { this.r = r; }
@Override public void run() { while (true) { r.print(); } } }
class Res { private String name; private String sex; boolean flag = false;
public void set(String name, String sex) { synchronized (this) { if (flag) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } this.name = name; this.sex = sex; flag = true; notify(); } }
public void print() { synchronized (this) { if (!flag) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(name + "..." + sex); flag = false; notify(); } } }
|
小结
- wait()
- notify()
- notifyAll()
三个方法都使用在同步中
- 原因:要对持有监视器(锁)的线程操作而监视器(锁)只在同步中出现
- 注意:等待和唤醒必须是对同一个锁
- 思考:为什么都定义在Object类中?因为监视器(锁)可以是任意对象
2.2. 生产者消费者问题
重复消费和未消费
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 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 86 87 88
| package com.day12.proCus;
public class ProducerCustomerDemo { public static void main(String[] args) { Res r = new Res();
Producer p = new Producer(r); Customer c = new Customer(r);
Thread t1 = new Thread(p); Thread t2 = new Thread(c); Thread t3 = new Thread(p); Thread t4 = new Thread(c); t1.start(); t2.start(); } }
class Producer implements Runnable { private Res r;
Producer(Res r) { this.r = r; }
@Override public void run() { r.set("iPhone5s"); } }
class Customer implements Runnable { private Res r;
Customer(Res r) { this.r = r; }
@Override public void run() { r.print(); } }
class Res { private String name; private int count = 5588; private boolean flag = false;
public synchronized void set(String name) { while (true) { if (flag) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } this.name = name + "..编号" + count++; System.out.println(Thread.currentThread().getName() + "- 生产 -" + this.name); flag = true; this.notify(); } }
public synchronized void print() { while (true) { if (!flag) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + "-消费-" + name); flag = false; this.notify(); } } }
|
生产和消费逐一交替运行(利用JDK1.5 新特性)
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 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 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
| package com.leeeyou.produceCustomer;
import java.util.ArrayList; import java.util.List; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;
public class ProducerCustomerDemo2 { public static void main(String[] args) { Res2 r = new Res2();
Producer2 p = new Producer2(r); Customer2 c = new Customer2(r);
Thread t1 = new Thread(p); Thread t2 = new Thread(c); Thread t3 = new Thread(p); Thread t4 = new Thread(c); Thread t5 = new Thread(p); Thread t6 = new Thread(c);
t1.setName("T1 - "); t2.setName("T2 - "); t3.setName("T3 - "); t4.setName("T4 - "); t5.setName("T5 - "); t6.setName("T6 - ");
t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); t6.start(); } }
class Producer2 implements Runnable { private Res2 r;
Producer2(Res2 r) { this.r = r; }
@Override public void run() { r.set("iPhone5s"); } }
class Customer2 implements Runnable { private Res2 r;
Customer2(Res2 r) { this.r = r; }
@Override public void run() { r.print(); } }
class Res2 { private String name;
private volatile List<String> mList = new ArrayList<>();
private Lock lock = new ReentrantLock(); private Condition pCondition = lock.newCondition(); private Condition cCondition = lock.newCondition();
public void print() { while (true) { lock.lock(); if (mList.size() > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } String remove = mList.remove(0); System.out.println(Thread.currentThread().getName() + "消费了:" + remove); pCondition.signal(); } else { System.out.println("消费光了:" + mList.size()); try { cCondition.await(); } catch (InterruptedException e) { e.printStackTrace(); } } lock.unlock(); } }
public void set(String name) { while (true) { lock.lock(); if (mList.size() < 50) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } String product = "一个手机 "; mList.add(product); System.out.println(Thread.currentThread().getName() + "生产了:" + product); cCondition.signal(); } else { System.out.println("生产满了:" + mList.size()); try { pCondition.await(); } catch (InterruptedException e) { e.printStackTrace(); } } lock.unlock(); } } }
|
2.3. 停止线程(彩蛋链接)
情况分析
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 29 30 31 32 33 34 35 36
| package com.day12.stopThread;
public class StopThread01 { public static void main(String[] args) { Thread01 t1 = new Thread01(); Thread t = new Thread(t1);
t.start();
for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + ".." + i); if(i==99){ t1.changeFlag(); } } } }
class Thread01 implements Runnable { private boolean flag = true;
@Override public void run() { while (flag) { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+".."+i); } } }
public void changeFlag() { flag = false; } }
|
特殊情况
- 当线程处于冻结状态,即使设置了标记,线程也不会结束
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 29 30 31 32 33 34 35 36 37
| package com.day12.stopThread;
public class StopThread02 { public static void main(String[] args) { Thread02 t2 = new Thread02(); Thread t = new Thread(t2);
t.start(); for (int i = 0; i < 100; i++) { if(i==99){ t2.changeFlag(); } System.out.println(Thread.currentThread().getName() + ".." + i); } } }
class Thread02 implements Runnable { private boolean flag = true;
@Override public synchronized void run() { while (flag) { try { wait(); } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName()+"..Exception"); } System.out.println(Thread.currentThread().getName()+"..run"); } }
public void changeFlag() { flag = false; } }
|
- 这时需要对冻结状态进行清除,强制让其恢复到运行状态(其实就是获取CPU执行权),此时便可以操作标记来结束线程
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 29 30 31 32 33 34 35 36 37 38
| package com.day12.stopThread;
public class StopThread02 { public static void main(String[] args) { Thread02 t2 = new Thread02(); Thread t = new Thread(t2);
t.start(); for (int i = 0; i < 100; i++) { if(i==99){ t.interrupt(); } System.out.println(Thread.currentThread().getName() + ".." + i); } } }
class Thread02 implements Runnable { private boolean flag = true;
@Override public synchronized void run() { while (flag) { try { wait(); } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName()+"..Exception"); flag = false; } System.out.println(Thread.currentThread().getName()+"..run"); } }
public void changeFlag() { flag = false; } }
|
2.4. 守护线程
使用情况:在启动线程前调用
特点
- 开启后,和前台线程共同抢夺CPU执行权并运行
- 结束时,当所有前台线程都结束后,后台线程会自动结束(JAVA VM退出)
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 29 30 31 32
| package com.day12.deamonThread;
public class DeamonThreadDemo { public static void main(String[] args) { DeamonThread dt = new DeamonThread(); Thread t1 = new Thread(dt); Thread t2 = new Thread(dt);
t1.setDaemon(true); t2.setDaemon(true);
t1.start(); t2.start();
for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + ".." + i); } } }
class DeamonThread implements Runnable {
@Override public void run() { while (true) { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + ".." + i); } } }
}
|
2.5. join()
用途:临时加入线程
特点
- 当A线程执行到了B线程的join()方法,A就会等待B线程都执行完,A才会执行
- 通俗一点说,这个方法就是用来抢夺CPU执行权
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 29 30 31 32
| package com.day12.deamonThread;
public class DeamonThreadDemo { public static void main(String[] args) throws InterruptedException { DeamonThread dt = new DeamonThread(); Thread t1 = new Thread(dt); Thread t2 = new Thread(dt);
t1.start(); t1.join(); t2.start();
for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + ".." + i); } } }
class DeamonThread implements Runnable {
@Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + ".." + i); } }
}
|
2.6. 优先级以及yield()
yield 暂停当前正在执行的线程对象,并执行其他线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package com.day12;
public class LastProblem { public static void main(String[] args) throws InterruptedException { LastThread dt = new LastThread(); Thread t1 = new Thread(dt); Thread t2 = new Thread(dt);
t1.start(); t2.start(); } }
class LastThread implements Runnable { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().toString() + ".." + i); Thread.yield(); } } }
|