多线程并发编程(三):多线程同步互斥Synchronized

怼烎@ 2022-07-30 15:23 389阅读 0赞

前言

其实就是针对线程安全问题进行的,最经典的问题就是银行转账的问题,A向B转账的同时,A也在存钱,比如A有1000元,向B转200元,A自己存300元,按道理最后是1100元,如果没有同步机制,那么就可能发生问题,如果在A转账的时候,余额还没有开始减,然后A存钱的时候,拿到的余额是没有减去的,然后A的余额开始减去,但是存钱的线程拿到的余额是没有减去的,那么最后计算赋值可能变成了1000+300=1300了,同样,有可能在存钱的时候还没有计算赋值,另外一个转账线程进来,拿到的值是还没有加起来的,也就是1000元,那么转账线程执行完,1000-200=800,按道理是1100的,所以可能出现钱多了,或者钱少了的问题。

线程安全问题

  1. package test01;
  2. public class TraditionalThreadSychronized {
  3. class Outputer{
  4. // 这里打印,采取一个字母一个字母的打印,打印完一个名字再换行
  5. public void output(String name){
  6. int len = name.length();
  7. for (int i = 0; i < len; i++) {
  8. System.out.print(name.charAt(i));
  9. }
  10. System.out.println();
  11. }
  12. }
  13. public void init(){
  14. // 注意这里要申明为final
  15. final Outputer out = new Outputer();
  16. // 线程1:打印zhangsan
  17. new Thread(new Runnable() {
  18. @Override
  19. public void run() {
  20. while(true){
  21. try {
  22. Thread.sleep(50);
  23. } catch (InterruptedException e) {
  24. e.printStackTrace();
  25. }
  26. out.output("zhangsan");
  27. }
  28. }
  29. }).start();
  30. // 线程2:打印lisi
  31. new Thread(new Runnable() {
  32. @Override
  33. public void run() {
  34. while(true){
  35. try {
  36. Thread.sleep(50);
  37. } catch (InterruptedException e) {
  38. e.printStackTrace();
  39. }
  40. out.output("lisi");
  41. }
  42. }
  43. }).start();
  44. }
  45. public static void main(String[] args) {
  46. new TraditionalThreadSychronized().init();
  47. }
  48. }

如果不考虑线程安全问题的话,理想结果应该是zhangsan,lisi 循环打印,但是这里没有使用同步机制,输出结果中可以发现,出现了原本不想要的结果:
这里写图片描述
可以发现,打印出来的数据有些有问题。

解决办法: 采用同步机制,使用synchronized

使用同步解决线程安全问题

1、使用当前对象锁

  1. class Outputer{
  2. // 这里打印,采取一个字母一个字母的打印,打印完一个名字再换行
  3. public void output(String name){
  4. synchronized(this){
  5. int len = name.length();
  6. for (int i = 0; i < len; i++) {
  7. System.out.print(name.charAt(i));
  8. }
  9. System.out.println();
  10. }
  11. }
  12. }

2、使用当前对象属性的对象锁

  1. class Outputer{
  2. private String xxx = "";
  3. // 这里打印,采取一个字母一个字母的打印,打印完一个名字再换行
  4. public void output(String name){
  5. synchronized(xxx){
  6. int len = name.length();
  7. for (int i = 0; i < len; i++) {
  8. System.out.print(name.charAt(i));
  9. }
  10. System.out.println();
  11. }
  12. }
  13. }

3、使用同步方法

  1. class Outputer{
  2. private String xxx = "";
  3. // 这里打印,采取一个字母一个字母的打印,打印完一个名字再换行
  4. public synchronized void output(String name){
  5. int len = name.length();
  6. for (int i = 0; i < len; i++) {
  7. System.out.print(name.charAt(i));
  8. }
  9. System.out.println();
  10. }
  11. }

其实,使用同步方法,就是使用的当前对象的对象锁

输出结果:

这里写图片描述

打印结果中不再出现之前的打印问题,zhangsan,lisi都完整打印出来了,当然如果要实现轮流依次打印那种,之后会给出实现方案的,这里只讨论线程安全机制。

同步之间的互斥问题

第一种情况:同步代码块与同步方法

  1. package test01;
  2. public class TraditionalThreadSychronized {
  3. class Outputer{
  4. // 同步代码块形式
  5. public void output(String name){
  6. synchronized(this){
  7. int len = name.length();
  8. for (int i = 0; i < len; i++) {
  9. System.out.print(name.charAt(i));
  10. }
  11. System.out.println();
  12. }
  13. }
  14. // 同步方法形式
  15. public synchronized void output2(String name){
  16. int len = name.length();
  17. for (int i = 0; i < len; i++) {
  18. System.out.print(name.charAt(i));
  19. }
  20. System.out.println();
  21. }
  22. }
  23. public void init(){
  24. // 注意这里要申明为final
  25. final Outputer out = new Outputer();
  26. // 线程1:打印zhangsan
  27. new Thread(new Runnable() {
  28. @Override
  29. public void run() {
  30. while(true){
  31. try {
  32. Thread.sleep(50);
  33. } catch (InterruptedException e) {
  34. e.printStackTrace();
  35. }
  36. // 调用的是同步代码块形式的方法output
  37. out.output("zhangsan");
  38. }
  39. }
  40. }).start();
  41. // 线程2:打印lisi
  42. new Thread(new Runnable() {
  43. @Override
  44. public void run() {
  45. while(true){
  46. try {
  47. Thread.sleep(50);
  48. } catch (InterruptedException e) {
  49. e.printStackTrace();
  50. }
  51. // 调用的是同步方法形式的方法output2
  52. out.output2("lisi");
  53. }
  54. }
  55. }).start();
  56. }
  57. public static void main(String[] args) {
  58. new TraditionalThreadSychronized().init();
  59. }
  60. }

结果:不会出现线程安全问题。

分析:第一个线程调用的是同步代码块的方法output,第二个线程调用的是同步方法形式的方法output2,那么结果会怎么样了,会出现线程安全问题吗?答案当然是不会,前面说到了,同步方法其实使用的就是当前对象的对象锁,而同步代码块中用的也是当前对象的对象锁,所以肯定是互斥的,即不能当一个线程拿到了这个对象的对象锁的时候,其他对象不能调用当前对象需要对象锁的方法,说的通俗点就是,线程1进入output方法的时候,拿到了当前对象的对象锁,线程2没有当前对象的对象锁,所以访问不了方法output2,同样线程2拿到之后,线程1也访问不了output方法。

特殊情况:当然如果同步代码块中使用的是其他对象锁,那么就会出现线程安全问题了,因为两个方法使用的不是同一个对象的对象锁。

第二种情况:同步代码块与静态同步方法

  1. package test01;
  2. public class TraditionalThreadSychronized {
  3. static class Outputer{
  4. // 同步代码块形式
  5. public void output(String name){
  6. synchronized(this){
  7. int len = name.length();
  8. for (int i = 0; i < len; i++) {
  9. System.out.print(name.charAt(i));
  10. }
  11. System.out.println();
  12. }
  13. }
  14. // 同步方法形式
  15. public synchronized void output2(String name){
  16. int len = name.length();
  17. for (int i = 0; i < len; i++) {
  18. System.out.print(name.charAt(i));
  19. }
  20. System.out.println();
  21. }
  22. // 静态同步代码块
  23. public static synchronized void output3(String name){
  24. int len = name.length();
  25. for (int i = 0; i < len; i++) {
  26. System.out.print(name.charAt(i));
  27. }
  28. System.out.println();
  29. }
  30. }
  31. public void init(){
  32. // 注意这里要申明为final
  33. final Outputer out = new Outputer();
  34. // 线程1:打印zhangsan
  35. new Thread(new Runnable() {
  36. @Override
  37. public void run() {
  38. while(true){
  39. try {
  40. Thread.sleep(50);
  41. } catch (InterruptedException e) {
  42. e.printStackTrace();
  43. }
  44. // 调用的是同步代码块形式的方法output
  45. out.output("zhangsan");
  46. }
  47. }
  48. }).start();
  49. // 线程2:打印lisi
  50. new Thread(new Runnable() {
  51. @Override
  52. public void run() {
  53. while(true){
  54. try {
  55. Thread.sleep(50);
  56. } catch (InterruptedException e) {
  57. e.printStackTrace();
  58. }
  59. // 调用的是静态同步方法形式的方法output3
  60. out.output3("lisi");
  61. }
  62. }
  63. }).start();
  64. }
  65. public static void main(String[] args) {
  66. new TraditionalThreadSychronized().init();
  67. }
  68. }

这里写图片描述

结果:会出现线程安全问题。
分析:同步代码块使用的是当前对象的对象锁,而静态同步方法使用的是当前静态类class的对象锁。
解决办法:同步代码块中使用当前类class的锁,如下:

  1. public void output(String name){
  2. synchronized(Outputer.class){
  3. int len = name.length();
  4. for (int i = 0; i < len; i++) {
  5. System.out.print(name.charAt(i));
  6. }
  7. System.out.println();
  8. }
  9. }

第三种情况:同步方法与静态同步方法

代码就省略了
结果:会出现线程安全问题
分析:同步方法使用的是当前类的对象的对象锁,而静态同步方法使用的是当前类的class对象的对象锁

发表评论

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

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

相关阅读

    相关 linux线编程——同步互斥

    我们在前面文章中已经分析了多线程VS多进程,也分析了线程的使用,现在我们来讲解一下linux多线程编程之同步与互斥。 现在,我们不管究竟是多线程好还是多进程好,先讲解一下,为