第8章:多线程

打印 上一主题 下一主题

主题 1011|帖子 1011|积分 3033

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
1、基本概念:程序、进程、线程

程序(program): 是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
进程(process): 是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:由它自身的产生、存在和消亡的过程,即生命周期。
1.当我们打开QQ或者视频播放器后,运行的QQ和视频播放器就是一个进程。
2. 程序是静态的,进程是动态的。
3. 进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域。
线程(thread): 进程可进一步细化为线程,是一个程序内部的一条执行路径(程序执行过程,可以用一条线连起来的即为一条执行路径)。
1. 若一个进程同一时间并行执行多个线程,就是支持多线程的。
2. 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小。
3. 一个进程中的多个线程共享相同的内存单元/内存地址空间,它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效(内存区域中的虚拟机栈和程序计数器对于每一个线程都有属于自己的一份,对于内存区域中的方法区和堆对于每一个进程都有属于自己的一份,进程中有多个线程,即多个线程共享一个进程中的方法区和堆)。但多个线程操作共享的系统资源可能就会带来安全隐患。
其他知识要点:
1.单核CPU和多核CPU的理解:
单核CPU其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。但由于CPU的主频比较高,时间单元特别短,因此,多个线程轮流依次执行无法被感觉出来(感觉是并发运行)。
如果是多核的话,才能更好的发挥多线程的效率。(现在的服务器都是多核)
对于一个Java的应用程序java.exe,其实至少有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程(如何程序运行中发生异常,会影响主线程)。
2.并行和并发:
并行:多个CPU同时执行多个任务。
并发:一个CPU(采用时间片)同时执行多个任务。
3.使用多线程的优点:
对于单核CPU来说,只使用单个线程先后完成多个任务的速度可定是比多个线程完成多个任务的速度更快(因为对于多个线程完成多个任务,需要考虑一个CPU切换不同线程时候的时间等)
但多线程程序能够提高应用程序的响应,对图形化界面更有意义,可增强用户体验;提高计算机系统CPU的利用率;改善程序结构,将既长又复杂的进程分为多个线程,独立运行,利于理解和修改(不同功能分成不同的模块)。
4.何时需要多线程:
(1)程序需要同时执行两个或多个任务。
(2)程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
(3)需要一些后台运行的程序时。
2、线程的创建和使用

2.1 引出:
Java语言的JVM允许程序运行多个线程(并发),它通过java.lang.Thread类来体现。
2.2 Thread的特性:

  • 每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体。
  • 通过该Thread对象的start()方法来启动这个线程,而非直接调用run()。
2.3 具体的多线程创建以及使用例子:
方法一:通过继承与Thread类实现多线程
  1. package com.java;
  2. /**
  3. * 多线程的创建,方式一:继承于Thread类
  4. * 步骤:
  5. *  1.创建一个继承于Thread类的子类
  6. *  2.重写Thread类中的run方法 --> 将此线程执行的操作声明在run()方法中
  7. *  3.创建Thread类的子类的对象
  8. *  4.通过此对象调用start()方法
  9. *     start作用: 1、启动当前线程 2、调用当前线程的run()方法(因为当前线程重写了父类中的run方法)
  10. *
  11. *
  12. *  例子:遍历100以内的所有的偶数
  13. * @author banana
  14. * @create 2023-02-15 19:18
  15. */
  16. public class ThreadTest {
  17.     public static void main(String[] args) {
  18.         //3.创建Thread类的子类的对象
  19.         MyThread t1 = new MyThread();
  20.         //4.调用start()方法
  21.         //问题一:我们不能通过直接调用run方法去启动线程
  22.         //如果直接a.run表示调用MyThread类对象a的run方法,并没有开启新的线程,还是在main线程中执行
  23.         t1.start();
  24.         //问题二:再启动一个线程,遍历100以内的偶数
  25.         //不可以还让已经start()的线程去执行,会报异常IllegalThreadStateException
  26.         //t1.start();
  27.         MyThread t2 = new MyThread();
  28.         t2.start();
  29.         //如下的操作仍然是在main线程中执行的
  30.         for(int i = 1; i <= 100; i ++){
  31.             if(i % 2 == 1)System.out.println("*" + i);
  32.         }
  33.     }
  34. }
  35. //1.创建一个继承与Thread类的子类
  36. class MyThread extends Thread{
  37.     //2.重写Thread类中的run方法
  38.     @Override
  39.     public void run() {
  40.         for(int i = 1; i <= 100; i ++){
  41.             if(i % 2 == 0)System.out.println(i);
  42.         }
  43.     }
  44. }
复制代码
补充2:
1、使用同步机制,将单例模式中的懒汉式改写成为线程安全的单例模式
原单例模式展示,以及其存在的问题分析:
  1. package com.java;
  2. /**
  3. * 较上一个例子,我们可以通过创建Thread类的匿名子类的方式去实现
  4. * 例子:遍历100以内的所有的偶数
  5. * @author banana
  6. * @create 2023-02-15 19:18
  7. */
  8. public class ThreadTest {
  9.     public static void main(String[] args) {
  10.          new Thread(){
  11.             @Override
  12.             public void run() {
  13.                 for(int i = 0; i <= 100; i ++){
  14.                     if(i % 2 == 1) System.out.println("*" + i);
  15.                 }
  16.             }
  17.         }.start();
  18.     }
  19. }
复制代码
解决方法一:同步方法
  1. package com.java;
  2. /**
  3. * 创建多线程的方式二:实现Runnable接口
  4. * 具体操作:
  5. * 1.创建一个实现Runable接口的类
  6. * 2.实现类去实现Runnable中的抽象方法:run()
  7. * 3.创建实现类的对象
  8. * 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
  9. * 5.通过Thread类的对象调用start();
  10. *
  11. * @author banana
  12. * @create 2023-02-16 8:52
  13. */
  14. //1.创建一个实现Runable接口的类
  15. class MyThread2 implements Runnable{
  16.     //2.实现类去实现Runnable中的抽象方法:run()
  17.     @Override
  18.     public void run() {
  19.         for(int i = 0; i < 100; i ++){
  20.             if(i % 2 == 0){
  21.                 //这里不能使用this.getName()
  22.                 //因为MyThread2不是继承Thread,是继承与Object,没有对应的方法
  23.                 //因此我们要通过THread中的静态方法currentThread先获取当前的线程对象
  24.                 //然后再通过当前的线程对象去调用Thread类中的getName方法
  25.                 System.out.println(Thread.currentThread().getName() + ": " + i);
  26.             }
  27.         }
  28.     }
  29. }
  30. public class ThreadTest2 {
  31.     public static void main(String[] args) {
  32.         //3.创建实现类的对象
  33.         MyThread2 myThread2 = new MyThread2();
  34.         //4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
  35.         Thread thread = new Thread(myThread2);
  36.         //5.通过Thread类的对象调用start(); 1.启动线程 2.调用当前线程的run()
  37.         /*
  38.         对于疑问这里调用的是Thread的run方法,而不是Runnable对象的run方法,为什么第二点说是调用当前线程的run方法的解释:
  39.         对于run的源码,根据放入构造器的Runnable对象赋值给Thread类中的target
  40.         因此当前线程的run调用了Runnable类型的target的run方法
  41.         public void run() {
  42.             if (target != null) {
  43.                 target.run();
  44.             }
  45.         }
  46.          */
  47.         thread.setName("线程1");
  48.         thread.start();
  49.         //再启动一个线程,遍历100以内的偶数
  50.         Thread thread2 = new Thread(myThread2);
  51.         thread2.setName("线程2");
  52.         thread2.start();
  53.     }
  54. }
复制代码
解决方法二:同步代码块
  1. package com.java;
  2. /**
  3. * 例子:创建三个窗口卖票,总票数为100张
  4. * 使用继承Thread方式实现
  5. *
  6. * 存在线程的安全问题(待解决)
  7. * @author banana
  8. * @create 2023-02-16 8:40
  9. */
  10. public class WindowsTest {
  11.     public static void main(String[] args) {
  12.         Window w1 = new Window();
  13.         Window w2 = new Window();
  14.         Window w3 = new Window();
  15.         w1.setName("窗口1");
  16.         w2.setName("窗口2");
  17.         w3.setName("窗口3");
  18.         w1.start();
  19.         w2.start();
  20.         w3.start();
  21.     }
  22. }
  23. class Window extends Thread{
  24.     private static int ticket = 100;    //static:使得三个对象公用一份票
  25.     @Override
  26.     public void run() {
  27.         while(true){
  28.             //还有余票
  29.             if(ticket > 0){
  30.                 System.out.println(Thread.currentThread().getName() + ": 卖票的票号为" + ticket);
  31.                 ticket --;
  32.             }
  33.             else break;
  34.         }
  35.     }
  36. }
  37. package com.java;
  38. /**
  39. * 例子:创建三个窗口卖票,总票数为100张
  40. * 通过使用Runnable接口的方式实现
  41. * @author banana
  42. * @create 2023-02-16 9:20
  43. */
  44. public class WindowsTest2 {
  45.     public static void main(String[] args) {
  46.         Window2 window2 = new Window2();
  47.         Thread t1 = new Thread(window2);
  48.         Thread t2 = new Thread(window2);
  49.         Thread t3 = new Thread(window2);
  50.         t1.setName("窗口1");
  51.         t2.setName("窗口2");
  52.         t3.setName("窗口3");
  53.         t1.start();
  54.         t2.start();
  55.         t3.start();
  56.     }
  57. }
  58. class Window2 implements Runnable{
  59.     //为什么这里不用加static,也能使得之后的三个线程t1,t2,t3公用这100张票?
  60.     //因为对于三个线程都是new的同一个window2,因此线程中的ticket也是同一个。
  61.     private int ticket = 100;
  62.     @Override
  63.     public void run() {
  64.         while(true){
  65.             if(ticket > 0){
  66.                 System.out.println(Thread.currentThread().getName() + ": 票号为:" + ticket);
  67.                 ticket --;
  68.             }
  69.             else {
  70.                 break;
  71.             }
  72.         }
  73.     }
  74. }
复制代码
方法三、优化
  1. public class Thread implements Runnable
复制代码
2、同步机制中的锁:
(1)同步锁机制:在《Thinking in Java》中是这么说:对于并发工作,你需要某种方式来防止两个任务访问相同的资源(其实就是共享资源竞争)。防止这种冲突的方式就是当资源被一个任务使用时,在其上加锁。第一个访问某项资源的任务必须锁定这项资源,使其他任务在其被解锁之前,就无法访问它了,而其被解锁之时,另一个任务就可以锁定并使用它了。
(2)synchronized的锁是什么?
任意对象都可以作为同步锁,所有对象都是自动含有单一的锁(监视器)。
同步方法的锁:静态方法(类名.class)、非静态方法(this)
同步代码块:自己指定,很多时候也是可以指定为this或类名.class(即类的对象)
(3)注意:
必须确保使用同一个资源的多个线程共用一把锁,这个非常重要吗否则就无法保证共享资源的安全;
一个线程类的所有静态方法共用同一把锁(类名.class),所有非静态方法共用同一把锁(this),同步代码块(指定序谨慎)。
4.3 线程的死锁问题:
4.3.1 概念:
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。
对应的解决方法:
专门的算法、原则;
尽量减少同步资源的定义;
尽量避免嵌套同步;
4.3.2 死锁的举例:
  1. package com.java;
  2. /**
  3. * 测试Thread中的常用方法
  4. * 1. start():启动当前线程,调用当前线程的run()方法
  5. * 2. run(): 通常需要重写Thread类中的此方法,将创建的线程执行的操作声明在此方法中
  6. * 3. currentThread(): 【静态方法】返回执行当前代码的线程(返回类型为Thread)
  7. * 4. getName():获取当前线程的名字
  8. * 5. setName():设置当前线程的名字
  9. * 6. yield(): 释放当前cpu的执行权
  10. * 7. join(): 在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,
  11. *              线程a才结束阻塞状态。
  12. * 8.stop():强制线程生命期结束(不推荐使用,已过时)
  13. * 9.sleep(long millitime): 让当前线程“睡眠”指定的millitime毫秒数,在指定的millitime毫秒
  14. *                          时间内,当前线程是阻塞状态。
  15. * 10.isAlive() : 判断当前线程是否存活(run方法内容是否执行完毕)
  16. *
  17. *
  18. * @author banana
  19. * @create 2023-02-15 20:26
  20. */
  21. class Th extends Thread{
  22.     @Override
  23.     public void run() {
  24.         for(int i = 0; i <= 100; i ++){
  25.             if(i % 2 == 0){
  26.                 try {
  27.                     sleep(10);        //当前线程阻塞1s
  28.                 } catch (InterruptedException e) {
  29.                     e.printStackTrace();
  30.                 }
  31.                 System.out.println(Thread.currentThread().getName() + ":" + i);
  32.             }
  33.             //if(i % 20 == 0)this.yield();
  34.         }
  35.     }
  36.     //构造器:
  37.     public Th(String name){
  38.         super(name);
  39.     }
  40. }
  41. public class ThreadMethodTest {
  42.     public static void main(String[] args) {
  43.         //通过线程的构造器为线程命名
  44.         Th h1 = new Th("线程1");
  45.         //h1.setName("线程1");
  46.         h1.start();
  47.         //通过setName方法给主线程进行命名:
  48.         Thread.currentThread().setName("主线程");
  49.         for(int i = 0; i <= 100; i ++){
  50.             if(i % 2 == 0){
  51.                 System.out.println(Thread.currentThread().getName() + ":" + i);
  52.             }
  53.             //主线程执行到20时,h1调用join方法,使得该分线程参与进来
  54.             //当分线程执行完之后,主线程才开始执行
  55.             if(i == 20){
  56.                 try {
  57.                     h1.join();
  58.                 } catch (InterruptedException e) {
  59.                     e.printStackTrace();
  60.                 }
  61.             }
  62.         }
  63.         System.out.println(h1.isAlive());
  64.     }
  65. }
复制代码
4.4 锁(Lock):
  1. package com.java;
  2. /**
  3. *
  4. * 1.线程的优先级:
  5. * MAX _PRIORITY:10
  6. * MIN_PRIORITY:1
  7. * NORM_PRIORITY:5(默认)
  8. *
  9. * 2.如何获取和设置当前线程的优先级:
  10. * getPriorty(): 返回线程优先值
  11. * setPriority(int newPriority):改变线程的优先级
  12. *
  13. * @author banana
  14. * @create 2023-02-15 21:33
  15. */
  16. public class ThreadPriority {
  17.     public static void main(String[] args) {
  18.         Thr thr = new Thr();
  19.         thr.setPriority(Thread.MAX_PRIORITY);
  20.         thr.start();
  21.         Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
  22.         for(int i = 0; i <= 100; i ++) {
  23.             if (i % 2 == 1) {
  24.                 System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + i);
  25.             }
  26.         }
  27.     }
  28. }
  29. class Thr extends  Thread{
  30.     @Override
  31.     public void run() {
  32.         for(int i = 0; i <= 100; i ++) {
  33.             if (i % 2 == 0) {
  34.                 System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + i);
  35.             }
  36.         }
  37.     }
  38. }
复制代码
5、线程的通信

5.1 概念:
线程之间的交互我们就称之为线程通信。
5.2 使用:
[code]package Program2;/** * * 线程通信的例子:使用两个线程打印1-100,线程1,线程2交替打印 * 涉及到三个方法: * 1.wait():一旦执行此方法,当前线程进入阻塞状态,并释放同步监视器 * 2.notify():一旦执行此方法,就会唤醒被wait()的一个线程,如果有多个线程被wait(),就根据优先级来唤醒 * 3.notifyAll():一旦执行此方法,就会唤醒所有被wait()的线程 * * 注意点: * 1.wait()、notify()、notifyAll()使用的时候必须使用在同步代码块或同步方法中。 * 2.wait()、notify()、notifyAll()的调用者必须是同步代码块或同步方法中的同步监视器。 *  否则会出现IllegalMonitorStateException * 3.这三个方法是定义在java.lang.Object类中的,因为要保证任何一个对象都有该方法,可以让 *  任何对象去充当同步监视器。 * * @author banana * @create 2023-02-17 9:53 */class Number implements Runnable{    private int number = 1;    @Override    public void run() {        while(true){            synchronized (this) {                //唤醒一个阻塞的线程,加入就绪队列中                //notify():唤醒所有阻塞的线程                notify();                if(number
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

水军大提督

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表