第5章 多线程 - 多个线程之间共享数据的方式探讨

优质
小牛编辑
131浏览
2023-12-01

内容摘要

多个线程之间共享数据,按照每个线程执行代码是否相同,我们可以采取不同的处理方式,这里通过简单的卖票示例说明了当每个线程执行相同代码的情况,对于多个线程执行不同代码的情况,处理方式比较灵活,这里主要介绍了2种方式,通过2种方式的对比和归纳,我们可以总结出在多个线程执行不同的代码情况下,如何进行代码的设计

1. 如果每个线程执行的代码相同

可以使用同一个Runnable对象,这个Runnable对象中有那个共享数据,例如:卖票系统

1.1 简单的卖票系统示例

  1. class Ticket implements Runnable{
  2. private int tick = 20;
  3. Object obj = new Object();
  4. public void run(){
  5. while(true){
  6. synchronized(obj){
  7. if(tick>0){
  8. //只能try,因为run是复写了Runnable接口的run,接口的run没有抛
  9. //try{Thread.sleep(10);}catch(Exception e){}
  10. System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
  11. }
  12. }
  13. }
  14. }
  15. }
  16. class TicketDemo
  17. {
  18. public static void main(String[] args) {
  19. //只建立了一个Ticket对象,内存中只有一个tick成员变量,所以是共享数据
  20. Ticket t = new Ticket();
  21. Thread t1 = new Thread(t);
  22. Thread t2 = new Thread(t);
  23. Thread t3 = new Thread(t);
  24. Thread t4 = new Thread(t);
  25. t1.start();
  26. t2.start();
  27. t3.start();
  28. t4.start();
  29. }
  30. }
  1. Thread-0....sale : 20
  2. Thread-0....sale : 19
  3. Thread-0....sale : 18
  4. Thread-0....sale : 17
  5. Thread-0....sale : 16
  6. Thread-0....sale : 15
  7. Thread-0....sale : 14
  8. Thread-0....sale : 13
  9. Thread-0....sale : 12
  10. Thread-3....sale : 11
  11. Thread-3....sale : 10
  12. Thread-3....sale : 9
  13. Thread-3....sale : 8
  14. Thread-3....sale : 7
  15. Thread-3....sale : 6
  16. Thread-3....sale : 5
  17. Thread-3....sale : 4
  18. Thread-3....sale : 3
  19. Thread-3....sale : 2
  20. Thread-3....sale : 1

2. 如果每个线程执行的代码不同

这时候不需要用不同的Runnable对象,有如下两种方式来实现这些Runnable对象之间的数据共享。

2.1 方式1

将共享数据封装在另外一个对象中,然后将这个对象逐一传递给各个Runnable对象。每个线程对共享数据的操作方法也分配到那个对象身上去完成,这样容易实现针对该数据进行的各个操作的互斥和通信。

思想:一个类提供数据和操作数据的同步方法,另外定义两个线程通过构造函数接收并操作数据,在主函数中直接创建线程对象,即可完成操作

2.2 方式2

将这些Runnable对象作为某一个类中的内部类,共享数据作为这个外部类中的成员变量,每个线程对共享数据的操作方式也分配给外部类,以便实现对共享数据进行的各个操作的互斥和通信,作为内部类的各个Runnable对象调用外部类的这些方法。

思想:一个外部类里面有两个内部类,为了让这两个内部类共享数据,让它们都操作外部类的同一个成员,方法和数据都在这个成员身上,直接调用方法即可完成 数据的操作

2.3 方式3:将上面两种方式的组合

将共享数据封装在另外一个对象中,每个线程对共享数据的操作方法也分配到那个对象身上去完成,对象作为这个外部类中的成员变量或方法中的局部变量,每个线程的Runnable的对象作为外部类中的成员内部类或局部外部类。

2.4 技巧总结

要同步互斥的几段代码最好是分别放在几个独立的方法中,这些方法再放在同一个类中,这样比较容易实现它们之间的同步互斥或通信。极端且简单的方式,即在任意一个类中定义一个static的变量,这将被所有线程共享。

2.5 对于每个线程执行的代码不同下的3种方式,通过一个面试题来说明

需求:设计4个线程,其中两个线程每次对j增加1,另外两个线程每次对j减少1,写出程序

使用方式1实现

将数据和操作共享数据的方法封装在一个类中,定义两个Runnable实现类,让两个Runnable都持有共享数据的引用,在Runnable的构造函数中,直接传入去操作,在实现类的run方法中调用封装类的方法

  1. public class MultyThreadShareMethod1 {
  2. public static void main(String[] args){
  3. //将数据封装到一个对象上,
  4. ShareData2 data1 = new ShareData2();
  5. //在runnable的构造函数中直接传入去操作
  6. for(int i=0;i<2;i++){
  7. new Thread(new MyRunnable1(data1)).start();
  8. new Thread(new MyRunnable2(data1)).start();
  9. }
  10. }
  11. }
  12. //封装共享数据和操作共享数据方法的类
  13. class ShareData2{
  14. private int j = 10;
  15. public synchronized void increment() {
  16. j++;
  17. System.out.println(Thread.currentThread().getName()+" inc : "+j);
  18. }
  19. public synchronized void decrement() {
  20. j--;
  21. System.out.println(Thread.currentThread().getName()+" dec : "+j);
  22. }
  23. }
  24. //增加的线程,需要传入一个共享数据
  25. class MyRunnable1 implements Runnable {
  26. private ShareData2 data;
  27. public MyRunnable1(ShareData2 data) {
  28. this.data = data;
  29. }
  30. @Override
  31. public void run() {
  32. for(int i=0;i<10;i++){
  33. data.increment();
  34. }
  35. }
  36. }
  37. //减少的线程,需要传入一个共享数据
  38. class MyRunnable2 implements Runnable {
  39. private ShareData2 data;
  40. public MyRunnable2(ShareData2 data) {
  41. this.data = data;
  42. }
  43. @Override
  44. public void run() {
  45. for(int i=0;i<10;i++){
  46. data.decrement();
  47. }
  48. }
  49. }

输出结果

  1. Thread-0 inc : 11
  2. Thread-0 inc : 12
  3. Thread-0 inc : 13
  4. Thread-0 inc : 14
  5. Thread-0 inc : 15
  6. Thread-0 inc : 16
  7. Thread-0 inc : 17
  8. Thread-0 inc : 18
  9. Thread-0 inc : 19
  10. Thread-0 inc : 20
  11. Thread-1 dec : 19
  12. Thread-3 dec : 18
  13. Thread-3 dec : 17
  14. Thread-3 dec : 16
  15. Thread-3 dec : 15
  16. Thread-3 dec : 14
  17. Thread-3 dec : 13
  18. Thread-3 dec : 12
  19. Thread-3 dec : 11
  20. Thread-3 dec : 10
  21. Thread-3 dec : 9
  22. Thread-2 inc : 10
  23. Thread-2 inc : 11
  24. Thread-1 dec : 10
  25. Thread-1 dec : 9
  26. Thread-1 dec : 8
  27. Thread-1 dec : 7
  28. Thread-1 dec : 6
  29. Thread-1 dec : 5
  30. Thread-1 dec : 4
  31. Thread-1 dec : 3
  32. Thread-1 dec : 2
  33. Thread-2 inc : 3
  34. Thread-2 inc : 4
  35. Thread-2 inc : 5
  36. Thread-2 inc : 6
  37. Thread-2 inc : 7
  38. Thread-2 inc : 8
  39. Thread-2 inc : 9
  40. Thread-2 inc : 10

使用方式2实现

将数据和操作共享数据的方法封装在一个类中

两个Runnable作为它的内部类,相对于方式1,这里没有将数据传给Runnable,而是让它们自己去取,在自己的run方法中调用操作数据的方法

这里的共享变量可以定义为静态类型的成员变量,也可以定义为final类型的局部变量。

  1. public class MultyThreadShareData {
  2. //共享数据作为外部类的成员变量
  3. //private static ShareData data = new ShareData();
  4. public static void main(String[] args){
  5. //也可以定义为final类型的局部变量
  6. final ShareData data = new ShareData();
  7. //开启4条线程
  8. for(int i=0;i<2;i++){
  9. //增加的线程
  10. new Thread(new Runnable(){
  11. @Override
  12. public void run() {
  13. for(int i=0;i<100;i++){
  14. data.increment();
  15. }
  16. }
  17. }).start();
  18. //减少的线程
  19. new Thread(new Runnable(){
  20. @Override
  21. public void run() {
  22. for(int i=0;i<100;i++){
  23. data.decrement();
  24. }
  25. }
  26. }).start();
  27. }
  28. }
  29. }
  30. //封装共享数据和操作共享数据方法的类
  31. class ShareData{
  32. private int j = 0;
  33. public synchronized void increment() {
  34. j++;
  35. System.out.println(Thread.currentThread().getName()+" inc : "+j);
  36. }
  37. public synchronized void decrement() {
  38. j--;
  39. System.out.println(Thread.currentThread().getName()+" dec : "+j);
  40. }
  41. }

两种方式的组合实现

  1. public class MultyThreadShareDataTest {
  2. private int j;
  3. public static void main(String args[]){
  4. MultyThreadShareDataTest tt = new MultyThreadShareDataTest();
  5. Inc inc=tt.new Inc();
  6. Dec dec=tt.new Dec();
  7. for(int i=0;i<2;i++){
  8. Thread t=new Thread(inc);
  9. t.start();
  10. t=new Thread(dec);
  11. t.start();
  12. }
  13. }
  14. private synchronized void inc(){
  15. j++;
  16. System.out.println(Thread.currentThread().getName()+"-inc:"+j);
  17. }
  18. private synchronized void dec(){
  19. j--;
  20. System.out.println(Thread.currentThread().getName()+"-dec:"+j);
  21. }
  22. class Inc implements Runnable{
  23. public void run(){
  24. for(int i=0;i<100;i++){
  25. inc();
  26. }
  27. }
  28. }
  29. class Dec implements Runnable{
  30. public void run(){
  31. for(int i=0;i<100;i++){
  32. dec();
  33. }
  34. }
  35. }
  36. }