java多線程 —— 兩種實際應用場景模擬


最近做的偏向並發了,因為以后消息會眾多,所以,jms等多個線程操作數據的時候,對共享變量,這些要很注意,以防止發生線程不安全的情況。

(一)

先說說第一個,模擬對信息的發送和接收。場景是這樣的:

就像筆者之前做的消息的發送,一個是服務器,一個是客戶端。發送的話,要保證信息100%的發送給客戶端,那么發給客戶端之后,客戶端返回一個消息告訴服務器,已經收到。當服務器一直沒有收到客戶端返回的消息,那么服務器會一直發送這個信息,直到客戶端發送回確認信息,這時候再刪除重復發送的這個信息。

為了模擬這個場景,這里寫兩個線程,一個是發送,一個是接收,把發送的信息,要保存到線程安全的對象里面,防止發生線程安全問題,這里采用concurrenthashmap。

發送代碼:

 

package com.TestThread;
/*
 * 
 * @author 薛定餓的貓
 * 
 * */
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;

public class PushThread extends Thread {

    @Override
    public void run() {
        // TODO Auto-generated method stub
        try {
            sleep(6000);
            while(MainThread.pushmessage.size()>0){
                //重發消息
                for(Entry<Integer, String> hashMap:MainThread.pushmessage.entrySet()){
                    System.out.println("消息id:"+hashMap.getKey()+"未發送成功,在此重發:"+hashMap.getValue());
                }
                sleep(1000);
            }
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
}

發送代碼,是不斷遍歷內存對象councurrenthashmap,從中取出信息,不斷的重發。其中MainThread.pushmessage是內存對象,在最后一段代碼中有定義。

當確認接收到信息后,另外一個線程來刪除內存對象。

刪除的代碼:

package com.TestThread;

/*
 * 
 * @author 薛定餓的貓
 * 
 * */
import java.util.Map.Entry;

public class RemoveThread extends Thread {

    @Override
    public void run() {
        // TODO Auto-generated method stub
        try {
            for (int i = 0; i < 10000; i++) {
                sleep(2000);
                for(Entry<Integer, String> map:MainThread.pushmessage.entrySet()){
                    if (map.getKey()==i) {
                        System.out.println("成功收到id為:"+map.getKey()+"返回的信息,刪除該元素");
                        MainThread.pushmessage.remove(map.getKey());
                    }
                }
                System.out.println("內存對象中的元素數量為:"+MainThread.pushmessage.size());
            }
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
}

這里是來刪除已收到的信息,然后從內存中刪除,不再發送。

然后寫一個主類入口:

package com.TestThread;
/*
 * 
 * @author 薛定餓的貓
 * 
 * */
import java.util.concurrent.ConcurrentHashMap;

public class MainThread {
    public static ConcurrentHashMap<Integer, String> pushmessage=new ConcurrentHashMap<Integer,String>();
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            pushmessage.put(i, "該消息是id為"+i+"的消息");
        }
        Thread pushThread=new PushThread();
        Thread remove=new RemoveThread();
        pushThread.start();
        remove.start();
        for (int i = 10; i < 20; i++) {
            pushmessage.put(i, "又一波到來,消息是id為"+i+"的消息");
        }
    }
}

這樣兩個線程可以輪流的進行各自的事情,並且不會造成數據安全的問題。用這種方式,再結合Androidpn的推送機制,會更加符合實際生產中的應用。

(二)多線程同步計數器

多線程同步計數器,按道理也是可以按照上面的方式來進行處理,定義一個像concurrenthashmap的變量。在java中,確實也有另外一種變量,原子變量Atomic,有AtomicLong,AtomicInteger,AtomicReference這些。

如果在多線程環境下要給一些值賦唯一id的話,這個時候,就要考慮這個id的安全性問題,也就是一致性的問題,不能重復。這里有兩個實現的代碼:

package com.test;

public class ThreadCount {
    public static void main(String[] args) {
        
        Thread[] threads=new Thread[10000];
        for (int i = 0; i < 10000; i++) {
            threads[i]=new AThread();
            threads[i].start();
        }
    }
}

class AThread extends Thread{

    @Override
    public void run() {
        // TODO Auto-generated method stub
        @SuppressWarnings("unused")
        Counter counter=new Counter();
        System.out.println(Counter.calNum());
    }
    
}

class Counter{
     private static long num;
     public Counter(){
         synchronized (Counter.class) {
            num++;
        }
     }
     public static synchronized long calNum(){
         return num;
     }
}

這里創建了10000個線程,每個線程都來訪問這個計數器,在構造方法中來進行值的遞增。

在計數器中,有兩次用到同步,很多人都說用同步,經常會對性能造成影響。於是,用第二種的原子變量,這個性能應該會更好。

代碼:

package com.test;

import java.util.concurrent.atomic.AtomicLong;

public class ThreadCount {
    public static void main(String[] args) {
        
        Thread[] threads=new Thread[10000];
        for (int i = 0; i < 10000; i++) {
            threads[i]=new AThread();
            threads[i].start();
        }
    }
}

class AThread extends Thread{

    @Override
    public void run() {
        System.out.println(MyCounter.calNum());
    }
    
}

class Counter{
     private static long num;
     public Counter(){
         synchronized (Counter.class) {
            num++;
        }
     }
     public static synchronized long calNum(){
         return num;
     }
}

class MyCounter{
    private static AtomicLong num=new AtomicLong();
    
    public static synchronized long calNum(){
        return num.incrementAndGet();
    }
}


這樣寫的話,在調用這個計數器的時候,直接不需要再new一個MyCounter對象。

這樣可以作為工具類,直接調用MyCounter的calNum方法。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM