01 多线程

1. 线程的基本概念

1.1 进程

任何的软件存储在磁盘中,运行软件的时候,OS使用IO技术,将磁盘中的软件的文件加载到内存,程序在能运行。

进程的概念 : 应用程序(typerpa,word,IDEA)运行的时候进入到内存,程序在内存中占用的内存空间(进程).

1.2线程

线程(Thread) : 在内存和CPU之间,建立一条连接通路,CPU可以到内存中取出数据进行计算,这个连接的通路,就是线程.

一个内存资源 : 一个独立的进程,进程中可以开启多个线程 (多条通路)

并发: 同一个时刻多个线程同时操作了同一个数据

并行: 同一个时刻多个线程同时执行不同的程序

2. Java实现线程程序

2.1 java.lang.Thread

一切都是对象,线程也是对象,Thread类是线程对象的描述类

  • 实现线程程序的步骤 :

    • 定义类继承Thread
    • 子类重写方法run
    • 创建子类对象
    • 调用子类对象的方法start()启动线程
  1. public class SubThread extends Thread {
  2. public void run() {
  3. for (int i = 0; i < 50; i++) {
  4. System.out.println("run--------" + i);
  5. }
  6. }
  7. }
  1. public class ThreadTest {
  2. public static void main(String[] args) {
  3. SubThread subThread = new SubThread();
  4. subThread.start();
  5. // 打印的顺序开始无序了
  6. for (int i = 0; i < 50; i++) {
  7. System.out.println("main------" + i);
  8. }
  9. }
  10. }

2.2 获取线程的名字

  1. public class subThread extends Thread {
  2. public void run() {
  3. // 获取线程名字
  4. // 每个线程都有自己的名字:thread-xx数字
  5. System.out.println("线程名字:" + super.getName());
  6. }
  7. }
  1. public class threadTest extends Thread {
  2. public static void main(String[] args) {
  3. // 创建2个线程实例
  4. subThread subThread = new subThread();
  5. subThread.start();
  6. subThread subThread1 = new subThread();
  7. subThread1.start();
  8. subThread subThread2 = new subThread();
  9. subThread2.start();
  10. // 获取当前线程对象,拿到运行main方法的线程对象
  11. Thread thread = Thread.currentThread();
  12. System.out.println(thread.getName() + "-------");
  13. }
  14. }

2.3线程的优先级

  1. public class PriorityThread extends Thread {
  2. public void run() {
  3. for (int i = 0; i < 50; i++) {
  4. System.out.println(Thread.currentThread().getName() + "------" + i);
  5. }
  6. }
  7. }
  1. public class ThreadTest {
  2. public static void main(String[] args) {
  3. PriorityThread p1 = new PriorityThread();
  4. PriorityThread p2 = new PriorityThread();
  5. PriorityThread p3 = new PriorityThread();
  6. // 线程优先级最大是10,最小为1,默认为5,基本测不出来
  7. p1.setPriority(Thread.MAX_PRIORITY);
  8. p2.setPriority(Thread.MIN_PRIORITY);
  9. p3.setPriority(3);
  10. p1.start();
  11. p2.start();
  12. p3.start();
  13. }
  14. }

2.4线程让步

Thread类的方法 join()

  • 解释,执行join()方法的线程,他不结束,其它线程运行不了
  1. public class ThreadTest {
  2. public static void main(String[] args) {
  3. ThreadJoin j0 = new ThreadJoin();
  4. ThreadJoin1 j1 = new ThreadJoin1();
  5. j0.setPriority(10);
  6. j1.setPriority(1);
  7. j0.start();
  8. try {
  9. j0.join(); // 线程等待,只有j0完成,j1才开始做,只有在start()方法下面,执行join方法的线程,它不结束,其他线程运行不了,更换位置就不行了
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. j1.start();
  14. }
  15. }

Thread类的方法 static yield()

  • 线程让步,线程把执行权让出
  1. public class ThreadJoin extends Thread {
  2. public void run() {
  3. Thread.yield(); // 线程让步
  4. for (int i = 0; i < 50; i++) {
  5. System.out.println(super.getName() + "------" + i);
  6. }
  7. }
  8. }
  9. public class ThreadJoin1 extends Thread {
  10. public void run() {
  11. for (int i = 0; i < 50; i++) {
  12. System.out.println(super.getName() + "------" + i);
  13. }
  14. }
  15. }

3. 线程安全

出现线程安全的问题需要一个前提 : 多个线程同时操作同一个资源

线程执行调用方法run,同一个资源是堆内存的

3.1 售票例子

火车票的票源是固定的,购买渠道在火车站买,n个窗口

  1. public class Ticket implements Runnable {
  2. // 定义票源
  3. private int tickets = 100;
  4. private Object object = new Object();
  5. // 实现Runnable接口
  6. @Override
  7. public void run() {
  8. // 当一个线程没有完全完成操作时,其他线程不能操作
  9. while (true) {
  10. /*
  11. * 任意对象:在同步中这个对象称为对象锁,官方的稳定称呼叫做:对象监视器
  12. * 同步代码块的执行原理:关键点就是这个对象锁
  13. * 1.线程执行到同步,判读锁是否存在
  14. * 如果锁存在,获取到锁,进入到同步中执行
  15. * 执行完毕,线程出去同步代码块,将锁对象归还
  16. * 2.线程执行到同步,判断锁是否存在
  17. * 如果锁不存在,线程只能在同步代码块这里等待,锁的到来
  18. * 综上:使用同步,线程先判断锁,然后获取锁,出去同步要释放锁,增加了许多步骤,因此线程是安全,运行速度慢,牺牲性能,不能牺牲数据安全
  19. * 注意:任意的对象 一定要是运行时唯一的,否则进来的锁和出去的锁不一样,线程自然也不行
  20. * */
  21. synchronized (object) { // 使用同步代码块需要要的格式: synchronized(任意的对象){代码块}
  22. if (tickets > 0) {
  23. try {
  24. Thread.sleep(300); // 线程休眠,暂停执行
  25. } catch (InterruptedException e) {
  26. e.printStackTrace();
  27. }
  28. System.out.println(Thread.currentThread().toString() + "当前出售第:" + tickets + "张");
  29. tickets--;
  30. }
  31. }
  32. }
  33. }
  1. public class ThreadTest {
  2. public static void main(String[] args) {
  3. // 创建实现了runnable实例,
  4. Ticket t = new Ticket();
  5. // 创建3个窗口,3个线程
  6. Thread r = new Thread(t);
  7. Thread r1 = new Thread(t);
  8. Thread r2 = new Thread(t);
  9. // 使用的全是t实例的run方法
  10. r.start();
  11. r1.start();
  12. r2.start();
  13. }
  14. }

另一种实现方法,使用synchronized 同步方法

  1. public class Ticket implements Runnable {
  2. private int tickets = 100;
  3. @Override
  4. public void run() {
  5. while (tickets > 0) {
  6. num();
  7. }
  8. }
  9. // synchronized方法,当一个方法中,所有的代码都是线性操作的共享内容,可以在方法的定义添加同步的关键字,同步的方法,可以被称为同步的函数
  10. public synchronized void num() {
  11. // synchronized (Ticket.class){ 静态方法使用synchronized,那个任意对象就可以使用当前类的对象:xxx(当前的类).class
  12. //
  13. // }
  14. if (tickets > 0) {
  15. try {
  16. Thread.sleep(300);
  17. } catch (InterruptedException e) {
  18. e.printStackTrace();
  19. }
  20. System.out.println(Thread.currentThread().getName() + "当前的出售第:" + tickets + "张");
  21. tickets--;
  22. }
  23. }
  24. }
  1. public class ThreadTest {
  2. public static void main(String[] args) {
  3. Ticket t = new Ticket();
  4. Thread r1 = new Thread(t);
  5. Thread r2 = new Thread(t);
  6. Thread r3 = new Thread(t);
  7. r1.start();
  8. r2.start();
  9. r3.start();
  10. }
  11. }

4. 死锁

  1. public class LockA {
  2. public static LockA lockA = new LockA();
  3. }
  1. public class LockB {
  2. public static LockB lockB = new LockB();
  3. }
  1. public class ThreadDeadLock implements Runnable {
  2. private boolean flag;
  3. public ThreadDeadLock(boolean flag) {
  4. this.flag = flag;
  5. }
  6. @Override
  7. public void run() {
  8. while (true) {
  9. if (flag) {
  10. /* 在t1线程中,flag为true,因此执行同步方法synchronized (LockA.lockA),将线程锁住,直到代码块中内容全部运行完毕,
  11. 因为t2线程中flag为false,也执行了,将线程也锁住了,所以在t1中
  12. synchronized (LockA.lockA)还在等待synchronized (LockB.lockB)的完成,t2中synchronized(LockB.lockB)还在等待
  13. synchronized (LockA.lockA)的完成*/
  14. synchronized (LockA.lockA) {
  15. System.out.println("线程获取了A锁1");
  16. synchronized (LockB.lockB) {
  17. System.out.println("线程获取了B锁1");
  18. }
  19. }
  20. } else {
  21. synchronized (LockB.lockB) {
  22. System.out.println("线程获取了B锁2");
  23. synchronized (LockA.lockA) {
  24. System.out.println("线程获取了A锁2");
  25. }
  26. }
  27. }
  28. }
  29. }
  30. }
  1. public class ThreadTest {
  2. public static void main(String[] args) {
  3. /*
  4. * 死锁程序:多个线程同时争夺同一个锁资源,出现程序假死现象
  5. * 面试点:考察开发人员是否充分理解同步代码的执行原理
  6. * 同步代码块:线程判断锁,获取锁,释放锁,不出代码,锁不释放
  7. * 1.互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
  8. * 2.不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
  9. * 3.请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
  10. * 4.循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。*/
  11. ThreadDeadLock t1 = new ThreadDeadLock(true);
  12. ThreadDeadLock t2 = new ThreadDeadLock(false);
  13. new Thread(t1).start();
  14. /*try {
  15. Thread.sleep(20);
  16. } catch (InterruptedException e) {
  17. e.printStackTrace();
  18. }*/
  19. new Thread(t2).start();
  20. }
  21. }

5. JDK5新特性Lock锁

JDK5新的特性 : java.util.concurrent.locks包. 定义了接口Lock.

Lock接口替代了synchronized,可以更加灵活

  • Lock接口的方法

    • void lock() 获取锁
    • void unlock()释放锁
  • Lock接口的实现类ReentrantLock
  1. public class ThreadLock implements Runnable {
  2. private int num = 100;
  3. // ReentrantLock为实现类,继承接口Lock
  4. private Lock lock = new ReentrantLock();
  5. @Override
  6. public void run() {
  7. while (true) {
  8. count();
  9. }
  10. }
  11. public void count() {
  12. lock.lock(); // 获取锁
  13. if (num > 0) {
  14. try {
  15. Thread.sleep(30);
  16. } catch (InterruptedException e) {
  17. e.printStackTrace();
  18. }
  19. System.out.println(Thread.currentThread().getName() + "当前门票还剩:" + num);
  20. num--;
  21. }
  22. lock.unlock();
  23. }
  24. }
  1. public class ThreadTest {
  2. public static void main(String[] args) {
  3. ThreadLock t = new ThreadLock();
  4. Thread r1 = new Thread(t);
  5. Thread r2 = new Thread(t);
  6. Thread r3 = new Thread(t);
  7. r1.start();
  8. r2.start();
  9. r3.start();
  10. }
  11. }

6. 生产者和消费者案列

6.1 synchronized实现

  1. /*
  2. * 消费线程
  3. * 资源对象中变量输出打印*/
  4. public class Customer implements Runnable {
  5. private Resource r;
  6. public Customer(Resource r) {
  7. this.r = r;
  8. }
  9. @Override
  10. public void run() {
  11. while (true) {
  12. synchronized (r) {
  13. if (!r.flag) {
  14. try {
  15. r.wait();
  16. } catch (InterruptedException e) {
  17. e.printStackTrace();
  18. }
  19. }
  20. System.out.println(Thread.currentThread().getName() + "消费第:" + r.count);
  21. r.flag = false;
  22. r.notify();
  23. }
  24. }
  25. }
  26. }
  1. /*
  2. * 生产线程
  3. * 资源对象中变量增加*/
  4. public class Produce implements Runnable {
  5. private Resource r;
  6. public Produce(Resource r) {
  7. this.r = r;
  8. }
  9. @Override
  10. public void run() {
  11. while (true) {
  12. // 生产者
  13. /*
  14. * 1.创建了2个线程,进行创建和消费
  15. * 2.将Resource作为对象锁
  16. * 3.使用synchronized同步代码块
  17. * 4.在初始化Resource实例时,flag必定为false,因此,is中代码不生效,生产+1,打印语句,修改标志位为true,并唤醒对象锁的另一个线程(线程可以空唤醒)
  18. * 5.因为while为true,所以继续循环,这次flag为true,走if语句,wait当前线程等待
  19. * 6.因为唤醒了消费者线程,在消费者线程中对flag取反,也不走if,消费生产的数据,修改flag = false,并唤醒对象锁的另一个线程,
  20. * 7.因为while为true,所以继续循环,这次flag为false取反,所以走if语句,wait当前线程等待,创建线程开始运行
  21. * 8.4-7步依次循环,直到手动停止*/
  22. synchronized (r) {
  23. // 判断标志位,是否允许生产
  24. // flag为true,生产完成,等待消费
  25. if (r.flag) {
  26. try {
  27. r.wait(); // // 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法
  28. } catch (InterruptedException e) {
  29. e.printStackTrace();
  30. }
  31. }
  32. r.count++;
  33. System.out.println(Thread.currentThread().getName() + "生产第" + r.count + "个");
  34. // 修改标志位,已经生产了,需要消费
  35. r.flag = true;
  36. // 唤醒消费者线程
  37. r.notify(); // 唤醒正在等待对象监视器的单个线程。如果有多个线程在等待,随机挑选一个线程唤醒
  38. }
  39. }
  40. }
  41. }
  1. //资源
  2. public class Resource {
  3. public int count;
  4. public boolean flag;
  5. }
  1. public class ThreadTest {
  2. public static void main(String[] args) {
  3. Resource r = new Resource();
  4. Customer c = new Customer(r);
  5. Produce p = new Produce(r);
  6. Thread t1 = new Thread(c); // 消费者线程
  7. Thread t2 = new Thread(p); // 生产者线程
  8. t1.start();
  9. t2.start();
  10. }
  11. }

优化synchronized同步代码的生产者和消费者例子

  1. public class Customer implements Runnable {
  2. private Resource r;
  3. public Customer(Resource r) {
  4. this.r = r;
  5. }
  6. @Override
  7. public void run() {
  8. while (true) {
  9. this.r.getCount();
  10. }
  11. }
  12. }
  1. public class Produce implements Runnable {
  2. private Resource r;
  3. public Produce(Resource r) {
  4. this.r = r;
  5. }
  6. @Override
  7. public void run() {
  8. while (true) {
  9. this.r.setCount();
  10. }
  11. }
  12. }
  1. public class Resource {
  2. private int count = 0;
  3. private boolean flag = false;
  4. // 消费
  5. public void getCount() {
  6. synchronized (this) {
  7. if (!flag) {
  8. try {
  9. this.wait();
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. }
  14. System.out.println(Thread.currentThread().getName() + "消费为:" + count);
  15. flag = false;
  16. this.notify();
  17. }
  18. }
  19. // 生产
  20. public void setCount() {
  21. synchronized (this) {
  22. if (flag) {
  23. try {
  24. // sleep:在等待时间里,同步锁不会释放,不丢失
  25. // wait:在等待的时间里,会释放同步锁,被唤醒才能去取得同步锁,才能执行
  26. this.wait();
  27. } catch (InterruptedException e) {
  28. e.printStackTrace();
  29. }
  30. }
  31. count++;
  32. System.out.println(Thread.currentThread().getName() + "生产为:" + count);
  33. flag = true;
  34. this.notify();
  35. }
  36. }
  37. }
  1. public class ThreadTest {
  2. public static void main(String[] args) {
  3. Resource r = new Resource();
  4. Customer c = new Customer(r);
  5. Produce p = new Produce(r);
  6. Thread t1 = new Thread(c);
  7. Thread t2 = new Thread(p);
  8. t2.start();
  9. t1.start();
  10. }
  11. }

6.2 Lock实现

  1. public class Customer implements Runnable {
  2. private Resource r;
  3. public Customer(Resource r) {
  4. this.r = r;
  5. }
  6. @Override
  7. public void run() {
  8. while (true) {
  9. r.getCount();
  10. }
  11. }
  12. }
  1. public class Produce implements Runnable {
  2. private Resource r;
  3. public Produce(Resource r) {
  4. this.r = r;
  5. }
  6. @Override
  7. public void run() {
  8. while (true) {
  9. r.setCount();
  10. }
  11. }
  12. }
  1. public class Resource {
  2. private int count = 0;
  3. private boolean flag = false;
  4. // ReentrantLock为实现类
  5. Lock lock = new ReentrantLock();
  6. Condition prod = lock.newCondition(); // 生产者线程阻塞队列
  7. Condition cust = lock.newCondition(); // 消费者线程阻塞队列
  8. public void setCount() {
  9. lock.lock(); // 获取锁
  10. if (flag) {
  11. try {
  12. prod.await(); // 生产者线程等待
  13. } catch (InterruptedException e) {
  14. e.printStackTrace();
  15. }
  16. }
  17. count++;
  18. System.out.println(Thread.currentThread().getName() + "生产:" + count);
  19. flag = true;
  20. cust.signal(); // 唤醒消费者线程中的一个
  21. lock.unlock(); // 释放锁
  22. }
  23. public void getCount() {
  24. lock.lock(); // 获取锁
  25. if (!flag) {
  26. try {
  27. cust.await();
  28. } catch (InterruptedException e) {
  29. e.printStackTrace();
  30. }
  31. }
  32. System.out.println(Thread.currentThread().getName() + "消费:" + count);
  33. flag = false;
  34. prod.signal(); // 唤醒线程
  35. lock.unlock(); // 释放锁
  36. }
  37. }
  1. public class ThreadTest {
  2. /*
  3. * 线程方法wait和sleep的区别
  4. * wait:在等待中会释放锁,只有被唤醒才能去重新获取锁,才能执行
  5. * sleep:在等待中,没有释放锁,不丢失不释放*/
  6. public static void main(String[] args) {
  7. /*
  8. * wait()方法和notify()/notifyAll()方法,本地调用os的功能,和操作系统交互,JVM找os,把线程停止,频繁等待与唤醒,
  9. * 导致JVM和os交互的次数过多,notifyAll()唤醒全部的线程,也浪费线程资源,为了一个线程,不得不唤醒全部的线程
  10. * 为了更高的性能Lock接口替换了同步synchronized,提供了更加灵活,性能更好的锁定操作
  11. * Lock接口中的方法:newCondition()方法返回的生死接口 Condition
  12. * 例如: Lock lock = new Lock()
  13. * Condition xx1 = lock.NewCondition() =>返回Condition接口实现的类的对象,线程阻塞队列*/
  14. Resource r = new Resource();
  15. Produce p = new Produce(r);
  16. Customer c = new Customer(r);
  17. Thread t1 = new Thread(p);
  18. Thread t2 = new Thread(c);
  19. t1.start();
  20. t2.start();
  21. }
  22. }

7. 线程池

  1. public class MyRunnable implements Runnable {
  2. @Override
  3. public void run() { // 线程的run方法不能有返回值
  4. System.out.println(Thread.currentThread().getName() + "我是线程的run方法");
  5. }
  6. }
  1. public class MyCall implements Callable<String> {
  2. @Override
  3. public String call() throws Exception {
  4. return "我是线程返回值";
  5. }
  6. }
  1. public class ThreadPool {
  2. public static void main(String[] args) throws ExecutionException, InterruptedException {
  3. /*
  4. * 线程的缓冲池,目的就是为了提高效率,JDK5开始内置线程池
  5. * 1.Executors类
  6. * 静态方法 static newFixedThreadPool(int 线程的个数)
  7. * 该方法的返回值ExecutorService接口的实现类,管理池子里面的线程
  8. * 2.ExecutorService接口的方法
  9. * submit(Runnable r)提交线程执行的任务*/
  10. /*
  11. * 线程的生命周期有6个,在某一时刻,线程只能处于其中的一种状态,这种线程的状态反应的是JVM的线程状态和OS无关
  12. * 1.创建=>new,开始线程(执行中,受阻塞)2种
  13. * 2.执行中=>runnable运行过程中则会出现2种状态(受阻塞和结束)
  14. * 3.结束=>terminated
  15. * 4.受阻塞=>blocked,受阻塞也分2种(有时间的,和无限等待的)
  16. * 5.时间=>timed_waiting 如:sleep
  17. * 6.无限等待=>waiting 如 wait*/
  18. // 创建线程池,线程个数是3个
  19. ExecutorService es = Executors.newFixedThreadPool(3);
  20. // 线程池管理对象,线程池调用submit()方法才可以使用线程中的run方法,
  21. MyRunnable r = new MyRunnable();
  22. es.submit(r);
  23. es.submit(r);
  24. es.submit(r);
  25. es.submit(r);
  26. // 如果运行了但不销毁线程,该线程不会自己销毁,需要手动销毁
  27. // 如果需要线程任务的返回值,那么需要实现Callable接口实现类
  28. Future<String> future = es.submit(new MyCall());
  29. // 获取线程返回值
  30. String str = future.get();
  31. System.out.println("线程返回值为:" + str);
  32. es.shutdown(); // 销毁线程池
  33. }
  34. }