- 进程:正在运行中的程序(直译)。
- 线程:进程中一个负责程序执行的控制单元(执行路径)。
- 多线程: 一个进程中可以多执行路径,称之为多线程。
- 多线程的优缺点:
- 1. Java中实现多线程的手段:
- 2. Thread类中的 start() 方法和 run() 方法的区别:
- 3. 多线程的状态 (重要)
- 4. 卖票(使用Runnable接口,将票封装在任务中,只有一个票对象)
- 5. 线程安全问题产生的原因:
- 6. 同步代码块可以解决以上问题
- 7. 同步代码块的好处和弊端:
- 8. 同步函数使用的锁是 this
- 9. 静态的同步函数使用的锁是 该函数所属字节码文件对象, 可以使用getClass()来获取, 也可以使用 当前类名.class
- 10. 单例中的懒汉式存在多线程安全隐患
- 11. 死锁
- 12. 等待唤醒机制
- 13. 等待唤醒中 多生产多消费问题 ☆☆☆☆
- 单生产单消费, 因为一共有两个线程, 所以唤醒时必定会唤醒对方的线程, 不会出现多生产多消费的问题
- 多生产多消费有什么问题?
- 1. 使用 if 来判断标记, 生产或者消费方唤醒的仍是本方的线程的话, 本方线程不会再去判断标记, 导致生产或者消费执行多次.
- 解决方式: 将 if 判断 改为 while 循环, 醒来之后仍能判断标记, 避免了多次生产或者消费
- 2. 将if改为while之后会出现的问题: 如果对方线程全部wait(), 只剩本的一个线程在运行, 执行完一次后唤醒的仍然是本方线程, 会导致所有线程全部被wait();
- 解决方式: 在唤醒时, 至少能够保证唤醒对方一个线程, 才不会出现该结果, 但无法指定唤醒, 所以只能将 notify() 改为 notifyAll();
- 3. JDK1.5 之后将同步和锁封装成了对象, 来解决多生产和多消费的问题。
- 14. wait 和 sleep 的区别:
- 15. 停止线程的方法
进程:正在运行中的程序(直译)。
线程:进程中一个负责程序执行的控制单元(执行路径)。
多线程: 一个进程中可以多执行路径,称之为多线程。
多线程的优缺点:
多线程的好处:解决了多部分同时运行的问题。
多线程的弊端:线程太多会导致效率的降低。
1. Java中实现多线程的手段:
1)、继承Thread类。
// 定义一个类,继承Thread,重写run方法class ThreadSub extends Thread {private String name; // 为了区分线程public ThreadSub(String name) {this.name = name;}@Overridepublic void run() {for (int i = 0; i < 3; i++) {System.out.println(name + "执行" + i);}}}// main函数public static void main(String[] args) {Demo1Sub sub1 = new Demo1Sub("多线程01号");Demo1Sub sub2 = new Demo1Sub("多线程02号");sub1.start();sub2.start();}打印结果:多线程01号执行0多线程02号执行0多线程02号执行1多线程02号执行2多线程01号执行1多线程01号执行2
2)、实现Runnable接口。
实现Runnable接口的好处:
1,将线程的任务从线程的子类中分离出来,进行了单独的封装。
按照面向对象的思想将任务的封装成对象。
2,避免了java单继承的局限性。
// Runnable接口详情, 只有一个抽象方法@FunctionalInterfacepublic interface Runnable {public abstract void run();}// 定义一个类,实现Runnable接口,实现未实现的run方法class ThreadSub2 implements Runnable {private String name; // 为了区分线程public ThreadSub2(String name) {this.name = name;}@Overridepublic void run() {for (int i = 0; i < 3; i++) {System.out.println(name + "执行" + i);}}}// main函数public static void main(String[] args) {Demo1Sub2 sub1 = new Demo1Sub2("多线程01号");Demo1Sub2 sub2 = new Demo1Sub2("多线程02号");Thread t1 = new Thread(sub1);Thread t2 = new Thread(sub2);t1.start();t2.start();}打印结果:多线程01号执行0多线程02号执行0多线程02号执行1多线程02号执行2多线程01号执行1多线程01号执行2
3)、匿名函数(其实就两种 这还是实现Runnable的一种)
// 定义一个类class ThreadSub {private String name; // 为了区分线程public ThreadSub(String name) {this.name = name;}// 该类中的方法public void show() {for (int i = 0; i < 3; i++) {System.out.println(name + "执行" + i);}}}// 主函数入口public static void main(String[] args) {Demo1Sub sub1 = new Demo1Sub("多线程01号");Demo1Sub sub2 = new Demo1Sub("多线程02号");// 使用匿名函数直接调用 start 方法new Thread(new Runnable() {@Overridepublic void run() {sub1.show();}}).start();Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {sub2.show();}});t2.start()}打印结果:多线程01号执行0多线程02号执行0多线程02号执行1多线程02号执行2多线程01号执行1多线程01号执行2
2. Thread类中的 start() 方法和 run() 方法的区别:
run()方法: 在本线程内调用该Runnable对象的run()方法,可以重复多次调用;
start()方法: 启动一个线程,调用该Runnable对象的run()方法,不能多次启动一个线程
直接调用run()方法,相当于没有使用多线程技术,需要在run()方法体执行完毕后才会执行下面的代码,还是只有一个主线程的执行路径,按照顺序执行代码。
调用start()方法后,会来启动一个线程,这时线程处于就绪状态,真正实现了多线程。
3. 多线程的状态 (重要)

4. 卖票(使用Runnable接口,将票封装在任务中,只有一个票对象)
package se.thread;/** 需求: 卖票** 一共有100张票,使用四个线程同时卖票。* */public class SaleTicket {public static void main(String[] args) {// 使用继承Thread 类// 既然创建多个线程会有多个车票对象,是否可以创建一个线程,开启四次?// 答案是否定,会在main函数中抛出异常,非法的线程状态异常IllegalThreadStateException// 一个线程只能被开启一次/*Ticket t1 = new Ticket();Ticket t2 = new Ticket();Ticket t3 = new Ticket();Ticket t4 = new Ticket();t1.start();t2.start();t3.start();t4.start();*/// 使用实现Runnable 接口Ticket t = new Ticket(); // 创建一个线程任务。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();}}// 继承Thread后创建了四个线程,导致每个线程对象都有一个自己的num,就会导致有400张票。// 实现Runnable创建一个线程任务对象,该对象中有num属性,而其他线程将此对象作为参数,都会使用此对象的num属性class Ticket /*extends Thread*/ implements Runnable {// 继承时使用static可以解决此问题,但是很多情况我们是不想数据共享的。// private static int num = 100;private int num = 100;/*@Overridepublic void run() {while (true) {if(num > 0) {// 注意!!!!!这里存在安全隐患// 即A线程通过了num>0的判断后,CPU不再执行A,执行B也通过了num>0的判断System.out.println(Thread.currentThread().getName()+ "......sale....." + (--this.num));}}}*/@Overridepublic void run() {while (true) {// 同步 来解决安全隐患synchronized(Ticket.class){if(num > 0) {// sleep 用来演示临时阻塞状态下的安全隐患// Thread.sleep(time),存在InterruptedException异常// 由于实现的Runable接口,并且该接口没有声明过InterruptedException异常,// 所以run方法中的异常只能catchtry { Thread.sleep(20); } catch (InterruptedException e) { }System.out.println(Thread.currentThread().getName()+ "......sale....." + (--this.num));}}}}}
5. 线程安全问题产生的原因:
(1)多个线程在操作共享的数据。
(2)操作共享数据的线程代码有多条。(例如售票的,run方法中需要判断 num是否大于零再进行售票,就会有安全隐患)
当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算。就会导致线程安全问题的产生。
6. 同步代码块可以解决以上问题
解决思路;
就是将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程时不可以参与运算的。
必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。
7. 同步代码块的好处和弊端:
同步的好处:解决了线程的安全问题。
同步的弊端:相对降低了效率,因为同步外的线程的都会判断同步锁。
同步的前提:同步中必须有多个线程并使用同一个锁。
8. 同步函数使用的锁是 this
9. 静态的同步函数使用的锁是 该函数所属字节码文件对象, 可以使用getClass()来获取, 也可以使用 当前类名.class
10. 单例中的懒汉式存在多线程安全隐患
11. 死锁
容易出现死锁的一种形式: 同步嵌套(着重看打印结果以及解释)
package com.lius.javase.demo.thread;/*** 死锁:* 容易出现死锁的一种形式: 同步嵌套*/public class DeathLock implements Runnable{public boolean flag;DeathLock(boolean flag) {this.flag = flag;}@Overridepublic void run() {if(flag) {synchronized (MyLock.LOCK_A) {System.out.println(Thread.currentThread().getName() + "...if...LOCK_A");synchronized (MyLock.LOCK_B) {System.out.println(Thread.currentThread().getName() + "...if...LOCK_B");}}} else {synchronized (MyLock.LOCK_B) {System.out.println(Thread.currentThread().getName() + "...else...LOCK_B");synchronized (MyLock.LOCK_A) {System.out.println(Thread.currentThread().getName() + "...else...LOCK_A");}}}}}class MyLock {public static final Object LOCK_A = new Object();public static final Object LOCK_B = new Object();}class Test {public static void main(String[] args) {DeathLock target1 = new DeathLock(true);DeathLock target2 = new DeathLock(false);Thread t1 = new Thread(target1);Thread t2 = new Thread(target2);t1.start();t2.start();}}死锁的打印结果:Thread-1...else...LOCK_BThread-0...if...LOCK_A对打印结果进行解释:线程1的标志位(flag)为false, 走了else分支, 拿着B锁进入了else里的第一个同步代码块,在想要拿A锁进入else的下一个代码块的时候, 线程1失去了CPU的执行权, 进入到阻塞状态, 此时线程2抢到执行权;线程2的标志位(flag)是true, 走了if分支, 拿着A锁进入了if里的第一个同步代码块,在想要拿B锁进入if的下一哥代码块的时候, B锁却还在线程1手里, 所以拿不到, 就发生了死锁现象.
12. 等待唤醒机制
代码示例(同一个资源, 一个线程负责存值, 另一个线程负责取值)
package com.lius.javase.demo.thread;/*** 同一个资源, 一个赋值, 一个取值** 使用等待唤醒机制:* 设计到的方法:* wait(): 让线程处于冻结状态, 被wait()的线程会被存放到线程池中.* notify(): 唤醒线程池中的一个线程(随机的).* notifyAll(): 唤醒线程池中的所有线程.** 这些方法都必须定义在同步中,* 因为这些方法都是改变线程状态的方法, 必须要明确操作的到底是哪个锁上的线程.*/public class ResourceDemo {public static void main(String[] args) {Resource r = new Resource();Input in = new Input(r);Output out = new Output(r);Thread t1 = new Thread(in);Thread t2 = new Thread(out);t1.start();t2.start();}}// 资源class Resource {private String name;private String sex;private boolean isExists = false; // 是否已有值public synchronized void set(String name, String sex) {if(isExists) {try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); }} /* 注意!!! 这里不能使用 else 代码块 */this.name = name;this.sex = sex;isExists = true;this.notify();}public synchronized void print() {if(!isExists) {try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); }} else {System.out.println(this.name + "..." + this.sex);isExists = false;this.notify();}}}// 赋值class Input implements Runnable{private final Resource r;Input(Resource r) {this.r = r;}boolean flag = false; // 控制赋值切换的@Overridepublic void run() {for(;;) {if (flag) {r.set("mike", "man");} else {r.set("丽丽", "女女女女女女女女女");}flag = !flag;}}}// 取值class Output implements Runnable {private final Resource r;Output(Resource r) {this.r = r;}@Overridepublic void run() {for(;;) {r.print();}}}
为什么操作线程的方法wait, notify, notifyAll 定义在了Object类中?
因为这些方法是监视器的方法。监视器其实就是锁。锁可以是任意的对象,任意的对象调用的方式一定定义在Object类中。
13. 等待唤醒中 多生产多消费问题 ☆☆☆☆
单生产单消费, 因为一共有两个线程, 所以唤醒时必定会唤醒对方的线程, 不会出现多生产多消费的问题
多生产多消费有什么问题?
1. 使用 if 来判断标记, 生产或者消费方唤醒的仍是本方的线程的话, 本方线程不会再去判断标记, 导致生产或者消费执行多次.
解决方式: 将 if 判断 改为 while 循环, 醒来之后仍能判断标记, 避免了多次生产或者消费
2. 将if改为while之后会出现的问题: 如果对方线程全部wait(), 只剩本的一个线程在运行, 执行完一次后唤醒的仍然是本方线程, 会导致所有线程全部被wait();
解决方式: 在唤醒时, 至少能够保证唤醒对方一个线程, 才不会出现该结果, 但无法指定唤醒, 所以只能将 notify() 改为 notifyAll();
package com.lius.javase.demo.thread;/*** 多生产者 -- 多消费者** 多生产多消费出现的问题:* 1. 使用 if 判断标记, 被 notify 后不会再判断标记了, 直接执行后面的代码, 需要重新判断标记, 将 if 改为 while* 2. 将 if 改为 while 后, 造成所有线程全部被 wait(), 即死锁的另外一种情况, 如何解决?* 因为至少需要唤醒一个对方的线程, 但是没有指定唤醒, 所以干脆全唤醒, 使用 notifyAll()*/public class ProducerConsumerDemo {public static void main(String[] args) {Resource r = new Resource();Producer producer = new Producer(r);Consumer consumer = new Consumer(r);Thread t0 = new Thread(producer);Thread t1 = new Thread(producer);Thread t2 = new Thread(consumer);Thread t3 = new Thread(consumer);t0.start();t1.start();t2.start();t3.start();}}class Resource {private String name;private int index = 1;private boolean flag = false;public synchronized void set(String name) {while (this.flag) { // 这里将 if 改为 while 是为了醒来之后再去判断标记, 以免本方线程多次执行try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}this.name = name + index;index++;System.out.println(Thread.currentThread().getName() + "...生产者..." + this.name);this.flag = true;// notify() 改为 notifyAll()// 因为使用了 while 会导致所有线程全部被 wait(),// 所以必须保证唤醒时可以至少唤醒对面一个, 但无法指定唤醒那个线程, 干脆全部唤醒this.notifyAll();}public synchronized void out() {while (!this.flag) { // 这里改为 while 与上同理try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() + "......消费者......" + this.name);this.flag = false;this.notifyAll(); // notify() 改为 notifyAll() 与上同理}}class Producer implements Runnable {private final Resource r;Producer(Resource r) {this.r = r;}@Overridepublic void run() {for(;;) {r.set("烤鸭");}}}class Consumer implements Runnable {private final Resource r;Consumer(Resource r) {this.r = r;}@Overridepublic void run() {for(;;) {r.out();}}}
3. JDK1.5 之后将同步和锁封装成了对象, 来解决多生产和多消费的问题。
package com.lius.javase.demo.thread;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;/*** 多生产多消费, 使用Lock接口替换同步代码块或者同步函数 (JDK1.5之后才有的)*/public class ProConLockDemo {public static void main(String[] args) {Resource r = new Resource();Pro producer = new Pro(r);Cons consumer = new Cons(r);Thread t0 = new Thread(producer);Thread t1 = new Thread(producer);Thread t2 = new Thread(consumer);Thread t3 = new Thread(consumer);t0.start();t1.start();t2.start();t3.start();}}class Resource {private String name;private int index = 1;private boolean flag = false;Lock lock = new ReentrantLock();// 通过一个锁上挂多组监视器, 来解决 notifyAll() 的效率问题Condition con_condition = lock.newCondition(); // 生产者监视器Condition pro_condition = lock.newCondition(); // 消费者监视器public void set(String name) {lock.lock();try {while (this.flag) {try {con_condition.await();} catch (InterruptedException e) {e.printStackTrace();}}this.name = name + index;index++;System.out.println(Thread.currentThread().getName() + "...生产者..." + this.name);this.flag = true;pro_condition.signal();} finally {lock.unlock();}}public void out() {lock.lock();try {while (!this.flag) {try {pro_condition.await();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() + "......消费者......" + this.name);this.flag = false;con_condition.signal();} finally {lock.unlock();}}}class Pro implements Runnable {private final Resource r;Pro(Resource r) {this.r = r;}@Overridepublic void run() {for(;;) {r.set("烤鸭");}}}class Cons implements Runnable {private final Resource r;Cons(Resource r) {this.r = r;}@Overridepublic void run() {for(;;) {r.out();}}}
