6-01 Java多线程编程

小咪咪 2022-02-27 07:51 195阅读 0赞

目录

一.线程的概念

二.创建线程

三.中断线程

四.线程常用方法

五.线程同步与交互

七.代码实现的同步和交互Lock

八.线程池

九.线程工具类


一.线程的概念

进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。

线程是指进程中的一个执行流程,一个进程中可以运行多个线程。

  1. 比如java.exe进程中可以运行很多线程。线程总是属于某个进程,进程中的多个线程共享进程的内存。

线程的五种状态:

新生状态(New):当一个线程的实例被创建即使用new关键字和Thread类或其子类创建一个线程对象后,

  1. 此时该线程处于新生状态。 此时线程不是活着的(not alive);

就绪状态(Runnable):通过调用线程实例的start()方法来启动线程使线程进入就绪状态;

  1. 但还没有被分配到CPU,处于线程就绪队列;此时线程是活着的(alive);

运行状态(Running):一旦获取CPU,线程就进入运行状态,线程的run()方法才开始被执行;

  1. 如果在给定的时间内没有执行结束,就会被系统给换下来回到线程的就绪状态;
  2. 此时线程是活着的(alive);

阻塞状态(Blocked):通过调用join()、sleep()、wait()或者资源被暂用使线程处于阻塞状态;

  1. 此时线程是活着的(alive

死亡状态(Dead):当一个线程的run()方法运行完毕或被中断或被异常退出,该线程到达死亡状态。

  1. 处于Dead状态调用start()方法,会出现异常

二.创建线程

  1. 创建线程三种方式:
  2. 1. 继承Thread
  3. 2. 实现Runnable接口
  4. 3. 匿名类的方式
  5. //方法一 继承 Thread类方式:
  6. public class TestThread extends Thread {
  7. @Override
  8. public void run() {
  9. System.out.println("线程处理逻辑");
  10. }
  11. }
  12. TestThread testThread=new TestThread();
  13. testThread.start();
  14. //方法二 实现 Runnable接口方式:
  15. Runnable runnable=new Runnable() {
  16. @Override
  17. public void run() {
  18. System.out.println("线程处理逻辑..");
  19. }
  20. };
  21. Thread thread=new Thread(runnable);
  22. thread.start();
  23. //方法三 匿名类方式:
  24. Thread thread=new Thread(){
  25. @Override
  26. public void run() {
  27. System.out.println("线程处理逻辑...");
  28. }
  29. };
  30. thread.start();

三.中断线程

四.线程常用方法

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xkdXpoZW5saW4_size_16_color_FFFFFF_t_70

1.join 线程等待

所有进程,至少会有一个线程即主线程,即main方法开始执行,就会有一个看不见的主线程存在。
在执行t.join,即表明在主线程中加入该线程。主线程会等待该线程结束完毕, 才会往下运行。

  1. public static void main(String[] args){
  2. System.out.println(" main 线程开始执行...");
  3. Runnable runnable=new Runnable() {
  4. @Override
  5. public void run() {
  6. System.out.println(" t1开始执行..");
  7. try {
  8. Thread.sleep(2000);
  9. System.out.println(" t1执行完毕..");
  10. }catch (InterruptedException e){
  11. e.printStackTrace();
  12. }
  13. }
  14. };
  15. Thread t1=new Thread(runnable);
  16. t1.start();
  17. //代码执行到这里,一直是main线程在运行
  18. try{
  19. //t1线程加入到main线程中来,只有t1线程运行结束,才会继续往下走
  20. t1.join();
  21. }catch (Exception e){
  22. e.printStackTrace();
  23. }
  24. System.out.println("t1线程执行完了..main 线程又重新开始执行");
  25. }

2.守护线程

守护线程的概念是: 当一个进程里,所有的线程都是守护线程的时候,结束当前进程。
守护线程通常会被用来做日志,性能统计等工作。

  1. Runnable runnable=new Runnable() {
  2. @Override
  3. public void run() {
  4. System.out.println("线程处理逻辑..");
  5. }
  6. };
  7. Thread thread=new Thread(runnable);
  8. thread.setDaemon(true);
  9. thread.start();

五.线程同步与交互

1.在方法上加 synchronized

在recover前,直接加上synchronized ,其所对应的同步对象,就是this 和hurt方法达到的效果是一样
外部线程访问gareen的方法,就不需要额外使用synchronized 了

  1. //回血
  2. //直接在方法前加上修饰符synchronized
  3. //其所对应的同步对象,就是this
  4. //和hurt方法达到的效果一样
  5. public synchronized void recover(){
  6. hp=hp+1;
  7. }
  8. //掉血
  9. public void hurt(){
  10. //使用this作为同步对象
  11. synchronized (this) {
  12. hp=hp-1;
  13. }
  14. }

2.同步

  1. public class Count {
  2. private int n=0;
  3. //每次加一
  4. public void add(){
  5. this.n++;
  6. }
  7. //每次减一
  8. public void sub(){
  9. this.n--;
  10. }
  11. public int getN() {
  12. return n;
  13. }
  14. public void setN(int n) {
  15. this.n = n;
  16. }
  17. }
  18. //加线程
  19. public class AddThread extends Thread {
  20. private Count count;
  21. public AddThread(Count count){
  22. this.count=count;
  23. }
  24. @Override
  25. public void run() {
  26. System.out.println("加线程"+Thread.currentThread().getName()+"等待锁");
  27. synchronized (count){
  28. System.out.println("加线程"+Thread.currentThread().getName()+"获得锁");
  29. count.add();
  30. System.out.println("加线程"+Thread.currentThread().getName()+"释放锁");
  31. }
  32. try {
  33. Thread.sleep(1000);
  34. }catch (InterruptedException e){
  35. e.printStackTrace();
  36. }
  37. }
  38. }
  39. //减线程
  40. public class SubThread extends Thread {
  41. private Count count;
  42. public SubThread(Count count){
  43. this.count=count;
  44. }
  45. @Override
  46. public void run() {
  47. System.out.println("减线程"+Thread.currentThread().getName()+"等待锁");
  48. synchronized (count){
  49. System.out.println("减线程"+Thread.currentThread().getName()+"获得锁");
  50. count.sub();
  51. System.out.println("减线程"+Thread.currentThread().getName()+"释放锁");
  52. }
  53. try {
  54. Thread.sleep(500);
  55. }catch (InterruptedException e){
  56. e.printStackTrace();
  57. }
  58. }
  59. }
  60. public static void main(String[] args){
  61. System.out.println(" main 线程开始执行...");
  62. int n=1000;
  63. Thread[] addThreads = new Thread[n];
  64. Thread[] subThreads = new Thread[n];
  65. final Count count=new Count();
  66. for (int i=0;i<n;i++){
  67. AddThread t1=new AddThread(count);
  68. t1.start();
  69. addThreads[i]=t1;
  70. SubThread t2=new SubThread(count);
  71. t2.start();
  72. subThreads[i]=t2;
  73. }
  74. for (Thread addThread:addThreads){
  75. try {
  76. addThread.join();
  77. }catch (Exception e){
  78. e.printStackTrace();
  79. }
  80. }
  81. for (Thread subThread:subThreads){
  82. try {
  83. subThread.join();
  84. }catch (Exception e){
  85. e.printStackTrace();
  86. }
  87. }
  88. System.out.println("结果为:"+count.getN());
  89. }

3.交互 wait 和 notifyAll

这里需要强调的是,wait方法和notify方法,并不是Thread线程上的方法,它们是Object上的方法。
因为所有的Object都可以被用来作为同步对象,所以准确的讲,wait和notify是同步对象上的方法。
wait()的意思是: 让占用了这个同步对象的线程,临时释放当前的占用,并且等待。 所以调用wait是有前提条件的,一定是在synchronized块里,否则就会出错。
notify() 的意思是,通知一个等待在这个同步对象上的线程,你可以苏醒过来了,有机会重新占用当前对象了。
notifyAll() 的意思是,通知所有的等待在这个同步对象上的线程,你们可以苏醒过来了,有机会重新占用当前对象了。

  1. public static void main(String[] args){
  2. System.out.println(" main 线程开始执行...");
  3. final Object obj=new Object();//同步对象
  4. Hero hero=new Hero();
  5. Thread t1=new Thread(){
  6. @Override
  7. public void run() {
  8. while (true) {
  9. synchronized (obj) {
  10. if (hero.hp <= 1) {
  11. try {
  12. System.out.println("线程" + Thread.currentThread().getName() + "正在等待");
  13. obj.wait();
  14. System.out.println("线程" + Thread.currentThread().getName() + "等待结束");
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. }
  18. }
  19. hero.hurt();
  20. }
  21. try {
  22. Thread.sleep(10);
  23. } catch (InterruptedException e) {
  24. e.printStackTrace();
  25. }
  26. }
  27. }
  28. };
  29. t1.start();
  30. Thread t2=new Thread(){
  31. @Override
  32. public void run() {
  33. while (true) {
  34. synchronized (obj) {
  35. hero.recover();
  36. // 通知那些等待在this对象上的线程,可以醒过来了
  37. obj.notifyAll();
  38. }
  39. try {
  40. Thread.sleep(1000);
  41. } catch (InterruptedException e) {
  42. e.printStackTrace();
  43. }
  44. }
  45. }
  46. };
  47. t2.start();
  48. }

七.代码实现的同步和交互Lock

1.线程同步

与 synchronized (someObject) 类似的,lock()方法,表示当前线程占用lock对象,一旦占用,其他线程就不能占用了。
与 synchronized 不同的是,一旦synchronized 块结束,就会自动释放对someObject的占用。 lock却必须调用unlock方法进行手动释放,为了保证释放的执行,往往会把unlock() 放在finally中进行。

2.线程交互

使用synchronized方式进行线程交互,用到的是同步对象的wait,notify和notifyAll方法
Lock也提供了类似的解决办法,首先通过lock对象得到一个Condition对象,然后分别调用这个Condition对象的:await, signal,signalAll 方法
注意: 不是Condition对象的wait,nofity,notifyAll方法,是await,signal,signalAll

  1. //线程交互使用
  2. //condition.await();
  3. //condition.signal();
  4. //condition.signalAll();
  5. public static void main(String[] args){
  6. System.out.println(" main 线程开始执行...");
  7. final Object obj=new Object();
  8. Hero hero=new Hero();
  9. Lock lock=new ReentrantLock();
  10. Condition condition=lock.newCondition();
  11. Thread t1=new Thread(){
  12. @Override
  13. public void run() {
  14. while (true) {
  15. lock.lock();
  16. if (hero.hp <= 1) {
  17. try {
  18. System.out.println("线程" + Thread.currentThread().getName() + "正在等待");
  19. condition.await();
  20. System.out.println("线程" + Thread.currentThread().getName() + "等待结束");
  21. } catch (InterruptedException e) {
  22. e.printStackTrace();
  23. }
  24. }
  25. hero.hurt();
  26. lock.unlock();
  27. try {
  28. Thread.sleep(10);
  29. } catch (InterruptedException e) {
  30. e.printStackTrace();
  31. }
  32. }
  33. }
  34. };
  35. t1.start();
  36. Thread t2=new Thread(){
  37. @Override
  38. public void run() {
  39. while (true) {
  40. lock.lock();
  41. hero.recover();
  42. // 通知那些等待在this对象上的线程,可以醒过来了
  43. condition.signalAll();
  44. lock.unlock();
  45. try {
  46. Thread.sleep(1000);
  47. } catch (InterruptedException e) {
  48. e.printStackTrace();
  49. }
  50. }
  51. }
  52. };
  53. t2.start();
  54. }

八.线程池

线程池的思路和生产者消费者模型是很接近的。

  1. 准备一个任务容器
  2. 一次性启动10个 消费者线程
  3. 刚开始任务容器是空的,所以线程都wait在上面。
  4. 直到一个外部线程往这个任务容器中扔了一个“任务”,就会有一个消费者线程被唤醒notify
  5. 这个消费者线程取出“任务”,并且执行这个任务,执行完毕后,继续等待下一次任务的到来。
  6. 如果短时间内,有较多的任务加入,那么就会有多个线程被唤醒,去执行这些任务。

在整个过程中,都不需要创建新的线程,而是循环使用这些已经存在的线程

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xkdXpoZW5saW4_size_16_color_FFFFFF_t_70 1

  1. public class ThreadPool {
  2. int threadPoolSize;
  3. LinkedList<Runnable> tasks=new LinkedList<>();
  4. public ThreadPool(){
  5. threadPoolSize=10;
  6. synchronized (tasks) {
  7. for (int i = 0; i < 10; i++) {
  8. new TaskConsumerThread("任务消费者线程 " + i).start();
  9. }
  10. }
  11. }
  12. public void add(Runnable runnable){
  13. synchronized (tasks){
  14. tasks.add(runnable);
  15. // 唤醒等待的任务消费者线程
  16. tasks.notifyAll();
  17. }
  18. }
  19. class TaskConsumerThread extends Thread{
  20. public TaskConsumerThread(String name){
  21. super(name);
  22. }
  23. Runnable task;
  24. @Override
  25. public void run() {
  26. System.out.println("启动: " + this.getName());
  27. while (true){
  28. synchronized (tasks){
  29. while (tasks.isEmpty()){
  30. try {
  31. tasks.wait();
  32. }catch (InterruptedException e){
  33. e.printStackTrace();
  34. }
  35. }
  36. task=tasks.removeLast();
  37. // 允许添加任务的线程可以继续添加任务
  38. tasks.notifyAll();
  39. }
  40. System.out.println(this.getName() + " 获取到任务,并执行");
  41. task.run();
  42. }
  43. }
  44. }
  45. }
  46. public static void main(String[] args){
  47. System.out.println(" main 线程开始执行...");
  48. ThreadPool pools=new ThreadPool();
  49. int sleep=1000;
  50. while(true){
  51. Runnable runnable=new Runnable() {
  52. @Override
  53. public void run() {
  54. try {
  55. Thread.sleep(1000);
  56. }catch (InterruptedException e){
  57. e.printStackTrace();
  58. }
  59. }
  60. };
  61. pools.add(runnable);
  62. //
  63. try {
  64. Thread.sleep(sleep);
  65. sleep=sleep>100?sleep-100:sleep;
  66. }catch (InterruptedException e){
  67. e.printStackTrace();
  68. }
  69. }
  70. }

九.线程工具类

调用Thread.currentThread()获取当前线程。
JDK提供了ThreadLocal,在一个线程中传递同一个对象。
ThreadLocal表示线程的“局部变量”,它确保每个线程的ThreadLocal变量都是各自独立的。
ThreadLocal适合在一个线程的处理流程中保持上下文(避免了同一参数在所有方法中传递)使用ThreadLocal要用try … finally结构。

发表评论

表情:
评论列表 (有 0 条评论,195人围观)

还没有评论,来说两句吧...

相关阅读

    相关 [Java] 线编程

    0.前置知识 接口 1.基本概念 一个人如何同时写完语文、数学、英语作业? 如果先写完语文,再写完数学,最后做完英语,那就不叫“同时”了。 要造出“同时

    相关 Java 线编程

    目录 一:线程的状态 二:线程常用方法 > 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。 一:线程

    相关 Java 线编程

    Java 多线程编程 Java给多线程编程提供了内置的支持。一个多线程程序包含两个或多个能并发运行的部分。程序的每一部分都称作一个线程,并且每个线程定义了一个独立的执行路

    相关 Java 线编程

    Java 给多线程编程提供了内置的支持。 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。 多线程是多任务的一种特别的形...