• JAVA线程与锁的笔记


    关于创建线程常见的三个方式:

    常见的Java线程的 4种创建方式分别为:继承Thread类、实现Runnable接口、通过ExecutorService和Callable<Class>实现有返回值的线程、基于线程池,如图 所示。

     

    一.关于继承Thread类

    继承Thread类
            Thread类实现了Runnable接口并定义了操作线程的一些方法,我们可以通过继承Thread类的方式创建一个线程。具体实现为创建一个类并继承Thread接口,然后实例化线程对象并调用start方法启动线程。start方法是一个native方法,通过在操作系统上启动一个新线程,并最终执行run方法来启动一个线程。run方法内的代码是线程类的具体实现逻辑。
    简单的例子:

    1. package com.test;
    2. public class Thread_test{
    3. public static void main(String[] args) {
    4. Thread_new thread = new Thread_new();
    5. thread.start();
    6. }
    7. }
    8. class Thread_new extends Thread{
    9. public void run() {
    10. try {
    11. Thread.sleep(2000);
    12. System.out.println("我在这里,我在这里");
    13. Thread.sleep(2000);
    14. System.out.println("2秒后,我在这里,我在这里");
    15. } catch (InterruptedException e) {
    16. e.printStackTrace();
    17. }
    18. }
    19. }

    结果如下:

    1. 我在这里,我在这里
    2. 2秒后,我在这里,我在这里

    二.关于实现Runnable接口

           基于Java编程语言规范,如果子类已继承(extends)了一个类,就无法再继承Thread类,此时可通过实现Runnable接口创建线程。具体的实现过程为:通过实现Runnable接口创建ChildrenClassThread线程,实例化名称为childrenThread的线程实例,创建Thread类的实例并传入childrenThread线程实例,调用线程的start方法启动线程。

    1. package com.test;
    2. public class Runnable_Test {
    3. public static void main(String[] args) {
    4. Runnable_Impl runnable_Impl = new Runnable_Impl();
    5. Thread thread = new Thread(runnable_Impl);
    6. thread.start();
    7. }
    8. }
    9. class Runnable_Impl implements Runnable{ //这里实现Runnable的方法就能创建新的线程
    10. @Override
    11. public void run() {
    12. try {
    13. Thread.sleep(2000);
    14. System.out.println("我这里是第一次啊");
    15. Thread.sleep(2000);
    16. System.out.println("2秒后,我这里是第二次啊");
    17. } catch (InterruptedException e) {
    18. e.printStackTrace();
    19. }
    20. }
    21. }

    结果如下:

    1. 我这里是第一次啊
    2. 2秒后,我这里是第二次啊

    三.关于通过ExecutorService和Callable<Class>实现有返回值的线程

            在主线程中开启多个线程并发执行一个任务,然后收集各个线程执行返回的结果并将最终结果汇总起来,这时就要用到Callable接口。具体的实现方法为:创建一个类并实现Callable接口,在call方法中实现具体的运算逻辑并返回计算结果。具体的调用过程为:创建一个线程池、一个用于接收返回结果的Future List及Callable线程实例,使用线程池提交任务并将线程执行之后的结果保存在Future中,在线程执行结束后遍历Future List中的Future对象,在该对象上调用get方法就可以获取Callable线程任务返回的数据并汇总结果,实现代码如下:
     

    1. package com.test;
    2. import java.util.ArrayList;
    3. import java.util.Iterator;
    4. import java.util.List;
    5. import java.util.concurrent.Callable;
    6. import java.util.concurrent.ExecutionException;
    7. import java.util.concurrent.ExecutorService;
    8. import java.util.concurrent.Executors;
    9. import java.util.concurrent.Future;
    10. public class MyCallBale_Test {
    11. public static void main(String[] args) {
    12. ExecutorService executorService = Executors.newFixedThreadPool(5);
    13. List<Future> list = new ArrayList<Future>();
    14. for (int i = 0; i < 5; i++) {
    15. Callable callable = new MyCallBale_Impl(i + " ");
    16. Future future = executorService.submit(callable);
    17. System.out.println("submit a callable thread:" + i);
    18. list.add(future);
    19. }
    20. executorService.shutdown();
    21. list.forEach(item -> {
    22. try {
    23. System.out.println(Thread.currentThread().getName() + " is running");
    24. System.out.println("get the result from callable thread:" + item.get().toString());
    25. } catch (InterruptedException | ExecutionException e) {
    26. e.printStackTrace();
    27. }
    28. });
    29. System.out.println("==================================================");
    30. /*线程是非常宝贵的计算资源,在每次需要时创建并在运行结束后销毁是非常浪费资源的。
    31. 我们可以使用缓存策略并使用线程池来创建线程,具体过程为创建一个线程池并用该线程池提交线程任务.*/
    32. ExecutorService pool = Executors.newFixedThreadPool(10);
    33. for (int i = 0; i < 10; i++) {
    34. pool.execute(new Runnable() {
    35. @Override
    36. public void run() {
    37. System.out.println(Thread.currentThread().getName()+" is running");
    38. }
    39. });
    40. }
    41. pool.shutdown();
    42. }
    43. }
    44. class MyCallBale_Impl implements Callable<String> {
    45. private String name;
    46. public MyCallBale_Impl(String name) {
    47. this.name = name;
    48. }
    49. @Override
    50. public String call() throws Exception {
    51. return name;
    52. }
    53. }

    结果如下:

    1. submit a callable thread:0
    2. submit a callable thread:1
    3. submit a callable thread:2
    4. submit a callable thread:3
    5. submit a callable thread:4
    6. main is running
    7. get the result from callable thread:0
    8. main is running
    9. get the result from callable thread:1
    10. main is running
    11. get the result from callable thread:2
    12. main is running
    13. get the result from callable thread:3
    14. main is running
    15. get the result from callable thread:4
    16. ==================================================
    17. pool-2-thread-1 is running
    18. pool-2-thread-3 is running
    19. pool-2-thread-4 is running
    20. pool-2-thread-2 is running
    21. pool-2-thread-5 is running
    22. pool-2-thread-6 is running
    23. pool-2-thread-7 is running
    24. pool-2-thread-9 is running
    25. pool-2-thread-10 is running
    26. pool-2-thread-8 is running

    四.关于synchronized关键字

    1.synchronized作用于成员变量和非静态方法时,锁住的是对象的实例

       synchronized的作用范围:
    ◎ synchronized作用于成员变量和非静态方法时,锁住的是对象的实例,即this对象。
    ◎ synchronized作用于静态方法时,锁住的是Class实例,因为静态方法属于Class而不属于对象。
    ◎ synchronized作用于一个代码块时,锁住的是所有代码块中配置的对象。
    示例代码:

    1. package com.test;
    2. public class Synchronied_Test {
    3. public static void main(String[] args) {
    4. final Synchronied_Impl synchronied_Impl = new Synchronied_Impl();
    5. new Thread(new Runnable() {
    6. @Override
    7. public void run() {
    8. try {
    9. synchronied_Impl.generalMethod1();
    10. } catch (InterruptedException e) {
    11. // TODO Auto-generated catch block
    12. e.printStackTrace();
    13. }
    14. }
    15. }).start();
    16. System.out.println("=====================");
    17. new Thread(new Runnable() {
    18. @Override
    19. public void run() {
    20. try {
    21. synchronied_Impl.generalMethod2();
    22. } catch (InterruptedException e) {
    23. // TODO Auto-generated catch block
    24. e.printStackTrace();
    25. }
    26. }
    27. }).start();
    28. }
    29. }
    30. class Synchronied_Impl {
    31. //synchronized修饰普通的同步方法 ,锁住的是当前的实例对象
    32. public synchronized void generalMethod1() throws InterruptedException {
    33. for (int i = 1; i < 3; i++) {
    34. System.out.println("GeneralMethod1 execute " +i+" time");
    35. Thread.sleep(3000);
    36. }
    37. }
    38. //synchronized修饰普通的同步方法 ,锁住的是当前的实例对象
    39. public synchronized void generalMethod2() throws InterruptedException {
    40. for (int i = 1; i < 3; i++) {
    41. System.out.println("GeneralMethod2 execute " +i+" time");
    42. Thread.sleep(3000);
    43. }
    44. }
    45. }

            synchronized作用于成员变量和非静态方法时,锁住的是对象的实例,具体的代码实现如上面的程序定义了两个使用synchronized修饰的普通方法,然后在main函数中定义对象的实例并发执行各个方法。可以看到,线程 1会等待线程 2执行完成才能执行,这是因为synchronized锁住了当前的对象实例synchronizedDemo导致的。具体的执行结果如下:

    1. =====================
    2. GeneralMethod1 execute 1 time
    3. GeneralMethod1 execute 2 time
    4. GeneralMethod2 execute 1 time
    5. GeneralMethod2 execute 2 time

    2.synchronized作用静态同步方法

            当创建两个类实体,便可以同步执行,但他们锁定的依然是当前对象。

    1. package com.test;
    2. import org.apache.commons.lang.StringUtils;
    3. public class Synchronied_Test2 {
    4. public static void main(String[] args) {
    5. final Synchronied_Impl2 synchronied_Impl = new Synchronied_Impl2();
    6. final Synchronied_Impl2 synchronied_Impl2 = new Synchronied_Impl2();
    7. new Thread(new Runnable() {
    8. @Override
    9. public void run() {
    10. try {
    11. synchronied_Impl.generalMethod1(true);
    12. } catch (InterruptedException e) {
    13. // TODO Auto-generated catch block
    14. e.printStackTrace();
    15. }
    16. }
    17. }).start();
    18. System.out.println("=====================");
    19. new Thread(new Runnable() {
    20. @Override
    21. public void run() {
    22. try {
    23. synchronied_Impl2.generalMethod1(false);
    24. } catch (InterruptedException e) {
    25. // TODO Auto-generated catch block
    26. e.printStackTrace();
    27. }
    28. }
    29. }).start();
    30. }
    31. }
    32. class Synchronied_Impl2 {
    33. //synchronized修饰普通的同步方法 ,锁住的是当前的实例对象
    34. public synchronized void generalMethod1(boolean flag) throws InterruptedException {
    35. for (int i = 1; i < 3; i++) {
    36. System.out.println((flag?"--":" ")+"GeneralMethod1 execute " +i+" time--");
    37. Thread.sleep(3000);
    38. }
    39. }
    40. //synchronized修饰普通的同步方法 ,锁住的是当前的实例对象
    41. public synchronized void generalMethod2() throws InterruptedException {
    42. for (int i = 1; i < 3; i++) {
    43. System.out.println("GeneralMethod2 execute " +i+" time");
    44. Thread.sleep(3000);
    45. }
    46. }
    47. }

            结果如下:

    1. =====================
    2. --GeneralMethod1 execute 1 time--
    3. GeneralMethod1 execute 1 time--
    4. --GeneralMethod1 execute 2 time--
    5. GeneralMethod1 execute 2 time--

    3.synchronized作用静态同步方法

            synchronized作用于静态同步方法,锁住的是当前类的Class对象。

    1. package com.test;
    2. import org.apache.commons.lang.StringUtils;
    3. public class Synchronied_Test3 {
    4. public static void main(String[] args) {
    5. final Synchronied_Impl3 synchronied_Impl = new Synchronied_Impl3();
    6. final Synchronied_Impl3 synchronied_Impl2 = new Synchronied_Impl3();
    7. new Thread(new Runnable() {
    8. @Override
    9. public void run() {
    10. try {
    11. synchronied_Impl.generalMethod1(true);
    12. } catch (InterruptedException e) {
    13. // TODO Auto-generated catch block
    14. e.printStackTrace();
    15. }
    16. }
    17. }).start();
    18. System.out.println("=====================");
    19. new Thread(new Runnable() {
    20. @Override
    21. public void run() {
    22. try {
    23. synchronied_Impl2.generalMethod1(false);
    24. } catch (InterruptedException e) {
    25. // TODO Auto-generated catch block
    26. e.printStackTrace();
    27. }
    28. }
    29. }).start();
    30. }
    31. }
    32. class Synchronied_Impl3 {
    33. //synchronized修饰静态同步方法 ,锁住的是当前Class(就是这个类Synchronied_Impl3)对象
    34. public static synchronized void generalMethod1(boolean flag) throws InterruptedException {
    35. for (int i = 1; i < 5; i++) {
    36. System.out.println((flag?"--":" ")+"GeneralMethod1 execute " +i+" time--");
    37. Thread.sleep(3000);
    38. }
    39. }
    40. //synchronized修饰静态同步方法 ,锁住的是当前Class对象
    41. public static synchronized void generalMethod2() throws InterruptedException {
    42. for (int i = 1; i < 5; i++) {
    43. System.out.println("GeneralMethod2 execute " +i+" time");
    44. Thread.sleep(3000);
    45. }
    46. }
    47. }

            结果如下:

    1. =====================
    2. --GeneralMethod1 execute 1 time--
    3. --GeneralMethod1 execute 2 time--
    4. --GeneralMethod1 execute 3 time--
    5. --GeneralMethod1 execute 4 time--
    6. GeneralMethod1 execute 1 time--
    7. GeneralMethod1 execute 2 time--
    8. GeneralMethod1 execute 3 time--
    9. GeneralMethod1 execute 4 time--

            通过日志看到,static方法是属于Class,Class的相关数据在JVM中是全局共享,因此静态方法锁相当于类的一个全局锁,会锁住所有调用该方法的线程。

    4.Synchronized作用于代码块中时。

    synchronized作用于一个代码块时,锁住的是在代码块中配置的对象:

    1. package com.test;
    2. public class Synchronied_Test4 {
    3. public static void main(String[] args) {
    4. final Synchronied_Impl4 synchronied_Impl = new Synchronied_Impl4();
    5. new Thread(new Runnable() {
    6. @Override
    7. public void run() {
    8. try {
    9. synchronied_Impl.generalMethod1(true);
    10. } catch (InterruptedException e) {
    11. // TODO Auto-generated catch block
    12. e.printStackTrace();
    13. }
    14. }
    15. }).start();
    16. System.out.println("=====================");
    17. new Thread(new Runnable() {
    18. @Override
    19. public void run() {
    20. try {
    21. synchronied_Impl.generalMethod2(false);
    22. } catch (InterruptedException e) {
    23. // TODO Auto-generated catch block
    24. e.printStackTrace();
    25. }
    26. }
    27. }).start();
    28. }
    29. }
    30. class Synchronied_Impl4 {
    31. String lockA = "lockA";
    32. //synchronized修饰方法快时 ,锁住的是括号里面配置的对象
    33. public void generalMethod1(boolean flag) throws InterruptedException {
    34. synchronized(lockA) {
    35. for (int i = 1; i < 3; i++) {
    36. System.out.println((flag?"--":" ")+"GeneralMethod1 execute " +i+" time--");
    37. Thread.sleep(3000);
    38. }
    39. };
    40. }
    41. //synchronized修饰方法快时 ,锁住的是括号里面配置的对象
    42. public void generalMethod2(boolean flag) throws InterruptedException {
    43. synchronized(lockA) {
    44. for (int i = 1; i < 3; i++) {
    45. System.out.println((flag?"--":" ")+"GeneralMethod2 execute " +i+" time");
    46. Thread.sleep(3000);
    47. }
    48. }
    49. }
    50. }

    以上代码的执行结果很简单,由于两个方法都需要获取名为lockA的锁,所以线程 1会等待线程2执行完成后才能获取该锁并执行,结果如下:
     

    1. =====================
    2. --GeneralMethod1 execute 1 time--
    3. --GeneralMethod1 execute 2 time--
    4. GeneralMethod2 execute 1 time
    5. GeneralMethod2 execute 2 time

    5.出现死锁的情况,作为参考对比

    写多线程程序时可能会出现A线程依赖B线程中的资源,而B线程又依赖于A线程中的资源的情况,这时就可能出现死锁。我们在开发时要杜绝资源相互调用的情况。如下所示就是一段典型的死锁代码:

    1. package com.test;
    2. public class Synchronied_Test5 {
    3. public static void main(String[] args) {
    4. final Synchronied_Impl5 synchronied_Impl = new Synchronied_Impl5();
    5. new Thread(new Runnable() {
    6. @Override
    7. public void run() {
    8. try {
    9. synchronied_Impl.generalMethod1(true);
    10. } catch (InterruptedException e) {
    11. // TODO Auto-generated catch block
    12. e.printStackTrace();
    13. }
    14. }
    15. }).start();
    16. System.out.println("=====================");
    17. new Thread(new Runnable() {
    18. @Override
    19. public void run() {
    20. try {
    21. synchronied_Impl.generalMethod2(false);
    22. } catch (InterruptedException e) {
    23. // TODO Auto-generated catch block
    24. e.printStackTrace();
    25. }
    26. }
    27. }).start();
    28. }
    29. }
    30. class Synchronied_Impl5 {
    31. String lockA = "lockA";
    32. String lockB = "lockB";
    33. //synchronized修饰方法快时 ,锁住的是括号里面配置的对象
    34. public void generalMethod1(boolean flag) throws InterruptedException {
    35. synchronized(lockA) {
    36. for (int i = 1; i < 3; i++) {
    37. System.out.println((flag?"--":" ")+"GeneralMethod1 execute " +i+" time--");
    38. Thread.sleep(3000);
    39. synchronized (lockB) {//3秒后,线程2执行了generalMethod2,同时想锁定lockB
    40. }
    41. }
    42. };
    43. }
    44. //synchronized修饰方法快时 ,锁住的是括号里面配置的对象
    45. public void generalMethod2(boolean flag) throws InterruptedException {
    46. synchronized(lockB) {
    47. for (int i = 1; i < 3; i++) {
    48. System.out.println((flag?"--":" ")+"GeneralMethod2 execute " +i+" time");
    49. Thread.sleep(3000);
    50. synchronized (lockA) {//线程2执行generalMethod2,同时想锁定lockA
    51. }
    52. }
    53. }
    54. }
    55. }

    通过以上代码可以看出,在blockMethod1方法中,synchronized(lockA)在第一次循环执行后想使用synchronized(lockB)锁住了lockB,但下次执行需等待lockA锁释放后才能继续;而在blockMethod2方法中,synchronized (lockB)在第一次循环执行后使用ynchronized(lockA)锁住了lockA,等待lockB释放后才能进行下一次执行。这样就出现blockMethod1等待blockMethod2释放lockA,而blockMethod2等待blockMethod1释放lockB的情况,就出现了死锁。执行结果是两个线程都挂起,等待对方释放资源,结果如下:
     

    1. =====================
    2. --GeneralMethod1 execute 1 time--
    3. GeneralMethod2 execute 1 time


     

  • 相关阅读:
    MySQL 及 jdbc 问题汇总
    【 OpenGauss源码学习 —— 列存储(update_pages_and_tuples_pgclass)】
    含文档+PPT+源码等]精品基于Nodejs实现的教育网站[包运行成功]Nodejs毕业设计计算机项目源码
    C++遍历(traversal)总结
    计算机毕业设计 基于SpringBoot的医院档案管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解
    【状语从句练习题】例句
    MyBatis如何使用delete标签删除数据呢?
    爬虫项目(四):批量下载高清美女桌面壁纸
    Java破坏单例模式的方式及原理
    期货十三篇 第十三篇 修行篇
  • 原文地址:https://blog.csdn.net/Mas1461261388/article/details/125431960