基本概念

  • 当多个线程同时访问同一种共享资源时,可能会造成数据的覆盖等不一致性问题,此时就需要对线程之间进行通信和协调,该机制就叫做线程的同步机制。
  • 多个线程并发读写同一个临界资源时会发生线程并发安全问题。
  • 异步操作:多线程并发的操作,各自独立运行。
  • 同步操作:多线程串行的操作,先后执行的顺序。 ```java package com.lagou.task18;

/**

  • @author lijing
  • @date 2020/10/15 15:05
  • @description */ public class AccountRunnableTest implements Runnable{ private int balance; public AccountRunnableTest() { }

    public AccountRunnableTest(int balance) {

    1. this.balance = balance;

    }

    public int getBalance() {

    1. return balance;

    }

    public void setBalance(int balance) {

    1. this.balance = balance;

    }

    @Override public void run() {

    1. System.out.println("线程" + Thread.currentThread().getName() + "已启动");
    2. ///1.模拟从后台查询账户余额的过程
    3. int temp=getBalance();
    4. //2.模拟取款200元的过程
    5. if(temp>=200){
    6. System.out.println("正在出钞中,请骚等..");
    7. temp -=200;
    8. try {
    9. Thread.sleep(5000);
    10. } catch (InterruptedException e) {
    11. e.printStackTrace();
    12. }
    13. System.out.println("请取走你的钞票");
    14. }else{
    15. System.out.println("余额不足,请核对的账户余额");
    16. }
    17. //3.模拟将最新的账户余额写入到后台
    18. setBalance(temp);

    }

    public static void main(String[] args){

    1. AccountRunnableTest account=new AccountRunnableTest(1000);
    2. Thread t1=new Thread(account);
    3. Thread t2=new Thread(account);
    4. t1.start();
    5. t2.start();
    6. System.out.println("主线程开始等待...");
    7. try {
    8. t1.join();

    // t2.start();//也就是等待线程一取款操作结束后再启动线程二—串行 没有多线程

    1. t2.join();
    2. } catch (InterruptedException e) {
    3. e.printStackTrace();
    4. }
    5. System.out.println("最终的账户余额为"+account.getBalance());

    } }

  1. <a name="E9JcA"></a>
  2. # 解决方案
  3. 由程序结果可知:当两个线程同时对同一个账户进行取款时,导致最终的账户余额不合理。<br />引发原因:线程一执行取款时还没来得及将取款后的余额写入后台,线程二就已经开始取款。<br />解决方案:让线程一执行完毕取款操作后,再让线程二执行即可,将线程的并发操作改为串行操作。<br />经验分享:在以后的开发尽量减少串行操作的范围,从而提高效率。
  4. <a name="WenYy"></a>
  5. # 实现方式
  6. 在Java语言中使用synchronized关键字来实现同步/对象锁机制从而保证线程执行的原子性,具体方式如下:<br />使用同步代码块的方式实现部分代码的锁定,格式如下:<br />synchronized(类类型的引用) {<br />编写所有需要锁定的代码;<br />}<br />使用同步方法的方式实现所有代码的锁定。直接使用synchronized关键字来修饰整个方法即可<br />该方式等价于:<br />synchronized(this) { 整个方法体的代码 }
  7. ```java
  8. package com.lagou.task18;
  9. /**
  10. * @author lijing
  11. * @date 2020/10/15 15:05
  12. * @description
  13. */
  14. public class AccountRunnableTest implements Runnable{
  15. private int balance;
  16. private Demo dm=new Demo();
  17. public AccountRunnableTest() {
  18. }
  19. public AccountRunnableTest(int balance) {
  20. this.balance = balance;
  21. }
  22. public int getBalance() {
  23. return balance;
  24. }
  25. public void setBalance(int balance) {
  26. this.balance = balance;
  27. }
  28. @Override
  29. public void run() {
  30. System.out.println("线程" + Thread.currentThread().getName() + "已启动");
  31. synchronized (dm) {
  32. //synchronized(new Demo()){ 锁不住 需要同一个锁
  33. ///1.模拟从后台查询账户余额的过程
  34. int temp=getBalance();
  35. //2.模拟取款200元的过程
  36. if(temp>=200){
  37. System.out.println("正在出钞中,请骚等..");
  38. temp -=200;
  39. try {
  40. Thread.sleep(5000);
  41. } catch (InterruptedException e) {
  42. e.printStackTrace();
  43. }
  44. System.out.println("请取走你的钞票");
  45. }else{
  46. System.out.println("余额不足,请核对的账户余额");
  47. }
  48. //3.模拟将最新的账户余额写入到后台
  49. setBalance(temp);
  50. }
  51. }
  52. public static void main(String[] args){
  53. AccountRunnableTest account=new AccountRunnableTest(1000);
  54. Thread t1=new Thread(account);
  55. Thread t2=new Thread(account);
  56. t1.start();
  57. t2.start();
  58. System.out.println("主线程开始等待...");
  59. try {
  60. t1.join();
  61. // t2.start();//也就是等待线程一取款操作结束后再启动线程二--串行 没有多线程
  62. t2.join();
  63. } catch (InterruptedException e) {
  64. e.printStackTrace();
  65. }
  66. System.out.println("最终的账户余额为"+account.getBalance());
  67. }
  68. }
  69. class Demo{}

image.png

静态方法的锁定

  • 当我们对一个静态方法加锁,如:
  • public synchronized static void xxx(){….}
  • 那么该方法锁的对象是类对象。每个类都有唯一的一个类对象。获取类对象的方式:类名.class。
  • 静态方法与非静态方法同时使用了synchronized后它们之间是非互斥关系的。
  • 原因在于:静态方法锁的是类对象而非静态方法锁的是当前方法所属对象。 ```java package com.lagou.task18;

/**

  • @author lijing
  • @date 2020/10/15 15:26
  • @description */ public class AccountThreadTest extends Thread{ private int balance; //类属于类层级 private static Demo dm=new Demo();

    public AccountThreadTest() { }

    public AccountThreadTest(int balance) {

    1. this.balance = balance;

    }

    public int getBalance() {

    1. return balance;

    }

    public void setBalance(int balance) {

    1. this.balance = balance;

    }

    @Override public /synchronized/ void run() {

    1. System.out.println("线程" + Thread.currentThread().getName() + "已启动");
    2. synchronized (dm) { //每一个对象都有一个成员变量dm 所以锁不住 如果要锁住需要static关键字修饰dm
    3. ///1.模拟从后台查询账户余额的过程
    4. int temp=getBalance();
    5. //2.模拟取款200元的过程
    6. if(temp>=200){
    7. System.out.println("正在出钞中,请骚等..");
    8. temp -=200;
    9. try {
    10. Thread.sleep(5000);
    11. } catch (InterruptedException e) {
    12. e.printStackTrace();
    13. }
    14. System.out.println("请取走你的钞票");
    15. }else{
    16. System.out.println("余额不足,请核对的账户余额");
    17. }
    18. //3.模拟将最新的账户余额写入到后台
    19. setBalance(temp);
    20. }

    }

    public static void main(String[] args) {

    1. AccountThreadTest att1=new AccountThreadTest(1000);
    2. att1.start();
    3. AccountThreadTest att2=new AccountThreadTest(1000);
    4. att2.start();
    5. System.out.println("主线程开始等待...");
    6. try {
    7. att1.join();

    // t2.start();//也就是等待线程一取款操作结束后再启动线程二—串行 没有多线程

    1. att2.join();
    2. } catch (InterruptedException e) {
    3. e.printStackTrace();
    4. }
    5. System.out.println("最终的账户余额为"+att1.getBalance());

    } }

  1. <a name="g4gbx"></a>
  2. # 注意事项
  3. - 使用synchronized保证线程同步应当注意:
  4. - 多个需要同步的线程在访问同步块时,看到的应该是同一个锁对象引用。
  5. - 在使用同步块时应当尽量减少同步范围以提高并发的执行效率。
  6. <a name="a0oS8"></a>
  7. # 线程安全类和不安全类
  8. - StringBuffer类是线程安全的类,但StringBuilder类不是线程安全的类。
  9. - Vector类和 Hashtable类是线程安全的类,但ArrayList类和HashMap类不是线程安全的类。
  10. - Collections.synchronizedList() 和 Collections.synchronizedMap()等方法实现安全。
  11. <a name="IMOTQ"></a>
  12. # 死锁的概念
  13. 线程一执行的代码:<br />public void run(){<br />synchronized(a){ //持有对象锁a,等待对象锁b<br />synchronized(b){<br />编写锁定的代码;<br />}<br />}<br />}<br />线程二执行的代码:<br />public void run(){<br />synchronized(b){ //持有对象锁b,等待对象锁a<br />synchronized(a){<br />编写锁定的代码;<br />}<br />}<br />}<br />注意:<br />在以后的开发中尽量减少同步的资源,减少同步代码块的嵌套结构的使用!
  14. <a name="58TVb"></a>
  15. # 使用Lock(锁)实现线程同步
  16. <a name="IoLdy"></a>
  17. ## 基本概念
  18. 从Java5开始提供了更强大的线程同步机制—使用显式定义的同步锁对象来实现。<br />java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。<br />该接口的主要实现类是ReentrantLock类,该类拥有与synchronized相同的并发性,在以后的线程安全控制中,经常使用ReentrantLock类显式加锁和释放锁。
  19. <a name="AezvV"></a>
  20. ## 常用的方法
  21. | ReentrantLock() | 使用无参方式构造对象 |
  22. | --- | --- |
  23. | void lock() | 获取锁 |
  24. | void unlock() | 释放锁 |
  25. <a name="UvkJ0"></a>
  26. ## 与synchronized方式的比较
  27. - Lock是显式锁,需要手动实现开启和关闭操作,而synchronized是隐式锁,执行锁定代码后自动释放。
  28. - Lock只有同步代码块方式的锁,而synchronized有同步代码块方式和同步方法两种锁。
  29. - 使用Lock锁方式时,Java虚拟机将花费较少的时间来调度线程,因此性能更好。
  30. <a name="1lL0a"></a>
  31. ## Object类常用的方法
  32. | void wait() | 用于使得线程进入等待状态,直到其它线程调用notify()或notifyAll()方法 |
  33. | --- | --- |
  34. | void wait(long timeout) | 用于进入等待状态,直到其它线程调用方法或参数指定的毫秒数已经过去为止 |
  35. | void notify() | 用于唤醒等待的单个线程 |
  36. | void notifyAll() | 用于唤醒等待的所有线程 |
  37. ```java
  38. package com.lagou.task18;
  39. /**
  40. * @author lijing
  41. * @date 2020/10/15 15:55
  42. * @description
  43. */
  44. public class ThreadCommunicationTest implements Runnable{
  45. private int cnt=1;
  46. @Override
  47. public void run() {
  48. while (true){
  49. synchronized (this) {
  50. //每当有一个线程进来后先大喊一声,把其他喊起来,调用notify()
  51. notify();
  52. if(cnt<=100){
  53. System.out.println("线程:" + Thread.currentThread().getName() + "中,cnt=" + cnt);
  54. try {
  55. Thread.sleep(100);
  56. } catch (InterruptedException e) {
  57. e.printStackTrace();
  58. }
  59. cnt++;
  60. //当前线程打印完毕一个整数后,为了防止继续打印下一个数据,则调用wait方法
  61. try {
  62. wait();//当前线程进入阻塞状态,自动释放对象锁,必须在锁定的代码中调用
  63. } catch (InterruptedException e) {
  64. e.printStackTrace();
  65. }
  66. }else{break;}
  67. }
  68. }
  69. }
  70. public static void main(String[] args) {
  71. ThreadCommunicationTest tct=new ThreadCommunicationTest();
  72. Thread t1=new Thread(tct);
  73. t1.start();
  74. Thread t2=new Thread(tct);
  75. t2.start();
  76. }
  77. }

生产者消费者模型的实现

  1. package com.lagou.task18;
  2. /**
  3. * @author lijing
  4. * @date 2020/10/15 16:20
  5. * @description
  6. */
  7. public class StoreHouse {
  8. private int cnt=0;//用于记录产品的数量
  9. public synchronized void produceProduct() {
  10. notify();
  11. if(cnt <10){
  12. System.out.println("线程"+Thread.currentThread().getName()+"正在生产第"+(cnt+1)+"个产品...");
  13. cnt++;
  14. }else{
  15. try {
  16. wait();
  17. } catch (InterruptedException e) {
  18. e.printStackTrace();
  19. }
  20. }
  21. }
  22. public synchronized void consumerProduct() {
  23. notify();
  24. if(cnt>0){
  25. System.out.println("线程" + Thread.currentThread().getName() + "正在消费第" + cnt + "个商品..");
  26. cnt--;
  27. }else{
  28. try {
  29. wait();
  30. } catch (InterruptedException e) {
  31. e.printStackTrace();
  32. }
  33. }
  34. }
  35. }
  1. package com.lagou.task18;
  2. /**
  3. * @author lijing
  4. * @date 2020/10/15 16:21
  5. * @description
  6. */
  7. public class ProduceThread extends Thread{
  8. //声明一个仓库类型的引用作为成员变量,是为了能调用仓库类中的生产方法
  9. //合成复用原则
  10. private StoreHouse storeHouse;
  11. public ProduceThread(StoreHouse storeHouse){
  12. this.storeHouse=storeHouse;
  13. }
  14. @Override
  15. public void run() {
  16. while(true){
  17. storeHouse.produceProduct();
  18. try {
  19. Thread.sleep(100);
  20. } catch (InterruptedException e) {
  21. e.printStackTrace();
  22. }
  23. }
  24. }
  25. }
  1. package com.lagou.task18;
  2. /**
  3. * @author lijing
  4. * @date 2020/10/15 16:25
  5. * @description
  6. */
  7. public class ConsumerThread extends Thread{
  8. //声明一个仓库类型的引用作为成员变量,是为了能调用仓库类中的生产方法
  9. //合成复用原则
  10. private StoreHouse storeHouse;
  11. public ConsumerThread(StoreHouse storeHouse){
  12. this.storeHouse=storeHouse;
  13. }
  14. @Override
  15. public void run() {
  16. while(true){
  17. storeHouse.consumerProduct();
  18. try {
  19. Thread.sleep(1000);
  20. } catch (InterruptedException e) {
  21. e.printStackTrace();
  22. }
  23. }
  24. }
  25. }
  1. package com.lagou.task18;
  2. /**
  3. * @author lijing
  4. * @date 2020/10/15 16:26
  5. * @description
  6. */
  7. public class StoreHouseTest {
  8. public static void main(String[] args) {
  9. //创建仓库类的对象
  10. StoreHouse storeHouse=new StoreHouse();
  11. //创建线程类对象并启动
  12. ProduceThread t1=new ProduceThread(storeHouse);
  13. ConsumerThread t2=new ConsumerThread(storeHouse);
  14. t1.start();
  15. t2.start();
  16. }
  17. }