# gmq-multithread-test **Repository Path**: gaomq/gmq-multithread-test ## Basic Information - **Project Name**: gmq-multithread-test - **Description**: gmq-multithread-test: java多线程编程核心技术demo - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2017-08-08 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README #gmq-multithread-test gmq-multithread-test: java多线程编程核心技术demo - 1、Java多线程技能 - 1.1 进程和多线程的概念及线程的优点 多线程是异步的,所以千万不要把eclipse里代码的顺序当成多线程执行的顺序,线程被调用的时机是随机的。 - 1.2 多线程的使用 - 1.2.1 继承Thread类 public class Thread implements Runnable Thread.java类中的start()方法通知“线程规划器”此线程已经准备就绪,等待调用线程对象的run()方法。 ( 这个过程就是让系统安排一个时间来调用Thread中的run()方法,也就是使线程得到运行,启动线程,具有异步调用的效果。 如果直接调用代码thread.run()就不是异步执行了,而是同步,那么次线程对象并不交给“线程规划器”来进行处理, 而是有main主线程来调用run()方法,也就是必须等待run()方法中的代码执行后才可以执行后面的代码。 ) 线程启动是start()方法,不是run()方法,如果直接run方法,就是顺序执行,就是main线程的单线程,不是多线程了。 注意:执行start()方法的顺序并不代表线程启动的的顺序。 - 1.2.2 实现Runnable接口 java不能多重继承,所以最好实现线程用这种方式。 使用Thread的构造方法来实现继承Runnable的线程启动。 - 1.2.3 实例变量与线程安全 非线程安全问题,使用: 非线程安全,加上synchronized,既可以了保证线程安全,排队进入 - 1.2.4 留意i--与System.out.println()的异常 例句: System.out.println("i=" + (i--) + "threadName=" + Thread.currentThread().getName()); 虽然println方法在内部是同步的,但i--的操作是println之前发生的,所以也存在费线程安全问题的概率。 所以为了防止这个问题,还是应该继续使用同步方法。 println(): public void println(String x) { synchronized (this) { print(x); newLine(); } } - 1.3 currentThread()方法 currentThread()方法可返回代码段正在被哪个线程调用的信息。 - 1.4 isAlive()方法 isAlive()的功能是判断当前的线程是否处于活动状态。 - 1.5 sleep()方法 方法sleep()的作用是指在指定的毫秒数内让当前“正在执行的线程”休眠(暂停执行)。 这个“正在执行的线程”是指this.currentThread()返回的线程。 - 1.6 getId()方法 getId()方法的作用是取得线程的唯一标识。 - 1.7 停止线程 Thread.stop()方法已经已经不可用或不被支持,是不安全的。 大多数的停止一个线程的操作使用Thread.interrupt()方法。 Thread.interrupt()方法的名称interrupt虽然是“停止、中止”的意思,但这个方法不会终止一个正在运行的线程,还需要加入一个判断才可以完成线程的停止。 在java中有一下3中方法可以终止正在运行的线程: 1)使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。 2)使用stop方法强制终止线程,但是不推荐这个使用这个方法,因为stop和suspend及resume一样,都是作废过期的方法,使用它们可能产生不可预料的结果。 3)使用interrupt方法中断线程。 - 1.7.1 停不了的线程 interrupt()方法的使用效果并不像for-break语句那样,马上就停止循环。 调用interrupt方法仅仅是在当前线程中搭一个停止的标记,并不是真正停止线程。 - 1.7.2 判断线程是否停止状态 在java的jdk中,Thread类里提供了两种方法: 1)this.interrupted(): 测试当前线程是否已经中断。 2)this.isInterrupted(): 测试线程是否已经中断。 - 1.7.3 能停止的线程--异常法 通过throw异常来进行中断:throw new InterruptedException(); - 1.7.4 在沉睡中停止 - 1.7.5 能停止的线程--暴力停止 - 1.7.6 方法stop()与java.lang.ThreadDeath异常 因为停止线程则可能使一些清理性的工作得不到完成。 另外一个情况就是对锁定对象进行了“解锁”,导致数据得不到同步的处理,出现数据不一致的问题。 - 1.7.7 释放锁的不良后果 使用stop释放锁将会给数据造成不一致性的结果。 所以该方法已经不建议使用。 - 1.7.8 使用return停止线程 将方法interrupt()与return结合使用也能实现停止线程的效果。 不过还是建议使用“抛异常”法来实现线程的停止,因为在catch块中可以对异常信息进行相关的处理,而且使用异常流能更好、方便 地控制程序的运行流程,不至于代码中出现很多个return,造成污染。 public class ReturnMyThread extends Thread { @Override public void run() { while (true) { if (this.isInterrupted()) { System.out.println(" 停止了 "); return; } System.out.println(" timer= " + System.currentTimeMillis()); } } } - 1.8 暂停线程 暂停线程意味着此线程还可以恢复运行。 使用suspend()方法暂停线程,使用resume()方法恢复线程的执行。 - 1.8.1 suspend()与resume()方法的使用 - 1.8.2 suspend()与resume()方法的缺点--独占 在使用suspend()与resume()方法的时候,如果使用不当,极易造成公共的同步对象的独占,使得其他线程无法访问公共同步对象。 如果线程中有println方法,因为是synchronized的,所以会锁定资源,独占,导致main方法中的println方法无法工作。 (参考SuspendLockRun.java 和SuspendLockThread.java) - 1.8.3 suspend()与resume()方法的缺点---不同步 所以在使用这两个方法的时候要格外注意。 - 1.9 yield方法 yield()方法的作用是放弃当前的CPU资源,将它让给其他的任务去占用CPU的执行时间。但放弃的时间不确定,有可能刚刚放弃,马上又 获得CPU时间片。 - 1.10 线程的优先级 在操作系统中,线程可以划分优先级,优先级较高的线程得到的CPU资源较多,也就是CPU优先执行优先级较高的线程对象中的任务。 设置线程优先级有助于帮“线程规划器”确定下一次选择哪一个线程来优先执行。 使用setPriority()方法。 public final void setPriority(int newPriority) { ThreadGroup g; checkAccess(); if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) { throw new IllegalArgumentException(); } if((g = getThreadGroup()) != null) { if (newPriority > g.getMaxPriority()) { newPriority = g.getMaxPriority(); } setPriority0(priority = newPriority); } } 在Java中,线程的优先级分为1~10这10个等级,如果小于1或者大于10,则JDK抛出IllegalArgumentException()异常。 JDK中使用3个常量来预置定义优先级的值,代码如下: /** * The minimum priority that a thread can have. */ public final static int MIN_PRIORITY = 1; /** * The default priority that is assigned to a thread. */ public final static int NORM_PRIORITY = 5; /** * The maximum priority that a thread can have. */ public final static int MAX_PRIORITY = 10; - 1.10.1 线程优先级的继承特性 在Java中,线程的优先级具有继承性,比如A线程启动B线程,则B线程的优先级与A是一样的。 - 1.10.2 优先级具有规则性 结论: 验证了线程的优先级与代码执行顺序无关,说明线程的优先级具有一定的规则性: 也就是:CPU尽量将执行资源让给优先级比较高的线程。 - 1.10.3 优先级具有随机性 根据实验可以得出结论: 不要把线程的优先级与运行结果的顺序作为衡量的标准,优先级高的线程并不一定每一次都先执行完run()方法中的任务, 也就是说,线程优先级与打印顺序无关,不要将这两者的关系相关联,它们的关系具有不确定性和随机性。 - 1.10.4 看谁运行的快 优先级高的运行的快。 - 1.11 守护线程 在Java线程中有两种线程,一种是用户线程,另外一种就是守护线程(Daemon)。 守护线程(Daemon):是一种特殊的线程,它的特性有陪伴的含义,当进程中不存在非守护线程了,则守护线程自动销毁。 典型的守护线程就是垃圾回收线程,当进程中没有非守护线程了,则垃圾回收线程也就没有存在的必要了,自动销毁。 守护线程的作用是为其他线程的运行提供便利服务,最典型的应用就是GC(垃圾回收器),它就是一个很称职的守护者。 - 2 对象及变量的并发访问 本章主要介绍Java多线程中的同步,也就是如何在Java语言中写出线程安全的程序,如何在Java语言中解决非线程安全的相关 问题。多线程中的同步问题是学习多线程的重中之重。 本章应该着重掌握如下技术点: - synchronized对象监视器为Object时的使用。 - synchronized对象监视器为Class时的使用。 - 非线程安全是如何出现的。 - 关键字 volatile的主要作用。 - 关键字 volatile 与synchronized的区别及使用情况。 - 2.1 synchronized 同步方法 第一章中接触的“线程安全”与“非线程安全”相关的技术点,它们是学习多线程技术时一定会遇到的经典问题。 “非线程安全”其实会在多个线程对同一个对象中的实例变量进行并发访问时发生,产生的后果就是“脏读”,也就是取到的数据其实是被 更改过的。 而“线程安全”就是以获得的实例变量的值是经过同步处理的,不会出现脏读的现象。 本章将在细节上更多接触在并发时变量值的处理方法。 - 2.1.1 方法内的变量为线程安全 “非线程安全”问题存在于“实例变量”中,如果是方法内部的私有变量,则不存在该问题,所得结果也就是“线程安全”的了。 可见,方法中的变量不存在非线程安全问题,永远都是线程安全的。这是方法内部的变量是私有特性造成的。 - 2.1.2 实例变量非线程安全 如果两个线程同时操作业务对象中的实例变量,则有可能会出现“非线程安全”问题。只需要在方法前加上 synchronized关键字即可。 实验结论:在两个线程访问同一个对象中的同步方法时,一定是线程安全的。本实验由于是同步方法,所以先执行完一个线程,然后在执行另一个线程。 - 2.1.3 多个对象多个锁 上面的事例是两个线程分别访问同一个类的两个不同实例的相同名称的同步方法,效果却是以异步的方式运行的。本例由于创建了2个业务对象, 在系统中产生了2个锁,所以运行结果是异步的,打印的效果就是先打印b,然后打印a。 结论: 1、关键字synchronized取得的锁都是对象锁,而不是把一段代码或方法(函数)当做锁,所以在上面的实例中,哪个线程先执行带 synchronized关键字的方法,哪个线程就持有该方法所属对象的锁Lock,那么其他线程只能呈等待状态,前提是多个线程访问的是 同一个对象。 2、但如果多个线程访问多个对象,则JVM会创建多个锁。 (同步的单词是 synchronized, 异步的单词为 asynchronized) - 2.1.4 synchronized 方法与锁对象 实验一: 调用synchronized声明的方法一定是排队运行的。 (另外需要牢牢记住“共享”这两个字,只有共享资源的读写才需要同步化,如果不是共享资源,那么根本就没有同步的必要) 结论: 1)A线程先持有object对象的Lock锁,B线程可以以异步的方式调用object对象中的非synchronized类型的方法。 2)A线程先持有object对象的Lock锁,B线程如果在这时调用object对象中的synchronized类型的方法则需要等待,也就是同步。 - 2.1.5 脏读 发生脏读的情况是在读取实例变量时,此值已经被其他线程更改过了。 通过t3中的案例结果显示: 出现脏读是因为setValue()方法虽然是同步的synchronized,但是getValue()方法却不是同步的,导致程序可以在任意时候进行调用。 解决的办法当然是在getValue方法上也加上synchronized关键字。 加上之后显示: 方法setValue和getValue被依次执行。 通过本案例不仅要知道脏读是通过synchronized关键字解决的,还要知道如下的内容: 1)当A线程调用anyObject对象加入synchronized关键字的X方法时,A线程就获得了X方法锁,更准确的讲,是获得了对象的锁,所以其他 线程必须等A线程执行完毕才可以调用X方法,但B线程可以随意调用其他的非synchronized同步方法。 2)当A线程调用anyObject对象加入synchronized关键字的X方法时,A线程就获得了X方法锁,更准确的讲,是获得了对象的锁,所以其他 线程必须等A线程执行完毕才可以调用X方法,而B线程如果调用声明了synchronized关键字的非X方法时,也必须等A线程将X方法执行完,也就是 释放对象锁后才可以调用。这时A线程已经执行了一个完整的任务,也就是username和password这两个实例变量已经同时被赋值,不存在脏读的基本环境。 脏读一定会出现操作实例变量的情况下,这就是不同线程“争抢”实例变量的结果。 - 2.1.6 synchronized 锁重入 示例:synLockIn_1和synLockIn_2 “可重入锁”的概念是:自己可以再次获取自己的内部锁。 比如有1条线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。 可重入锁也支持在父子类继承的环境中。 实验二说明,当存在父子类继承关系时,子类是完全可以通过“可重入锁”调用父类的同步方法的。 - 2.1.7 出现异常,锁自动释放