NPTL 线程同步方式

  NPTL提供了互斥体 pthread_mutex_t 类型进行线程同步,防止由于多线程并发对全局变量造成的不正确操作。使用 pthread_mutext_t 对数据进行保护已经可以实现基本的数据同步,NPTL又提供了pthread_cond_t 条件变量与pthread_mutext_t一起使用实现高效的线程同步保护数据。有了互斥变量pthread_mutext_t为什么还要引入条件变量pthread_cond_t呢? 原因就是防止CPU空转,一个线程获得互斥量之后,另外一个线程如果想获取该互斥量,就会不断的去查询这个互斥量是否已经空闲可以被自己占用,于是浪费了CPU周期。引入条件变量pthread_cond_t之后,如果条件不满足,线程进入睡眠状态,不会浪费CPU周期。

  NPTL进行线程同步的一般结构如下:

thread 1:
    pthread_mutex_lock(&mutex);
    while (!condition)
        pthread_cond_wait(&cond, &mutex);
    /* 实际操作,修改condition为无效 */
    pthread_mutex_unlock(&mutex);

thread2:
    pthread_mutex_lock(&mutex);
    /* 实际操作,修改condition为有效 */
    pthread_cond_signal(&cond);
    pthread_mutex_unlock(&mutex);

  标准代码结构是像上面这样,针对上面的结构提几个问题?

  1. 为什么要将pthread_cond_wait 放在while(!condition)循环内呢,为什么要有while(!condition)的存在呢?

    2. pthread_cond_t 为什么要和 pthread_mutex_t 一起使用呢,使用pthread_cond_signal的线程不使用pthread_mutext 行不行? 

 

  在回答上面的问题之前先介绍一下最核心的pthread_cond_wait(&cond, &mutex)在不同情况下都会干些什么。

  1. 程序运行到pthread_cond_wait() 条件发生,代码继续向下执行。

  2. 程序运行到pthread_cond_wait() 条件未发生,函数调用首先会释放mutex(打开锁),并使当前线程进入睡眠状态。

  3. 睡眠在pthread_cond_wait()上的线程被signal唤醒,pthread_cond_wait()首先去获得锁(尝试重新获得该mutex直到获得)。

 

  pthread_cond_wait()的行为为下面的讨论做一个铺垫。现在来考虑回答上面的问题,我们可以从多线程乱序执行做为切入点,thread1 有可能比thread2 先执行,thread2 也有可能比thread1先执行。

  1.首先考虑,如果thread2先执行并且已经执行到 pthread_cond_signal() 但是thread1甚至都还没有运行,更别说进入到pthread_cond_wait()状态,这时候没有 while(!condition) 会怎么样?

    显然thread2已经发送了singal了,但是没有接收者,此时出现了丢信号的情况,即如果没有 while(!condition) 当thread1进入到pthread_cond_wait()的时候就会睡眠,唤醒信号丢失的情况发生,在这种情况下如果有 while(!condition) 的存在则不会执行pthread_cond_wait() 直接执行下面的代码。

    那么用 if(!condtion) 不是也可以解决上面的问题吗? 不错是可以解决上面的问题,但是会带来新的问题。考虑这种情况:如果signal同时唤醒了多个wait在该条件上的线程(pthread_cond_broadcast 或者出现传说中的“惊群”效应),那使用if(!condtion) 就是不行的。 这是因为,各个多个被唤醒的线程肯定会有一个会先进入被这个mutex保护的临界区(回忆上面介绍的pthread_cond_wait()函数在线程醒来之前会尝试去持有锁直到持有为止),Linux上规定是低优先级的线程先获得该mutex,然后进行了操作,并修改了condition变量,释放了mutex,此时另一个正在睡眠中但同时也在尝试获取该mutex的线程被唤醒,然后直接就向下执行,此时就会导致多线程同步失败。如果使用while(!condtion)再次进行检查则不会出现同步失败的问题。      

  2.第二个问题相对简单,如果cond不和mutex一起使用,那么任何可以访问cond的线程都可能唤醒睡眠在某个mutex上的线程,所以需要mutex对cond的保护,以确保有资格的线程才能对某个线程进行唤醒操作。

  理解条件变量的关键还是需要理解pthread_cond_wait()都干了些什么!

   

NPTL 多线程同步  条件变量 互斥变量 Linux

C++ 封装互斥对象

  多线程程序中为了防止线程并发造成的竞态,需要经常使用到Mutex进行数据保护。posix提供了phtread_mutex_t进行互斥保护数据。Mutex的使用需要初始化和释放对应(phtread_mutex_init() 和 phtread_mutex_destroy() 对应),上锁和解锁对应(phtread_mutex_lock 和 pthread_mutex_unlock对应)。lock和unlock的过程是设计逻辑的一部分一般都程序员都能正确的进行加锁和解锁对应,但是要防止lock之后程序出现异常或者提前return而没有unlock。初始化mutex之后不释放也会造成资源泄漏,也是很容易遗漏的地方。在实际开发中一般都需要自己封装一下Mutex。

class MutexLock 
{
 public:
  MutexLock() : holder_(0)
  {
    int ret = pthread_mutex_init(&mutex_, NULL);
    assert(ret == 0); 
  }

  ~MutexLock()
  {
    assert(holder_ == 0);
    int ret = pthread_mutex_destroy(&mutex_);
    assert(ret == 0); 
  }

  bool isLockedByThisThread() const
  {
    return holder_ == static_cast<pid_t>(::syscall(SYS_gettid));
  }

  void assertLocked() const
  {
    assert(isLockedByThisThread());
  }

  void lock()
  {
    pthread_mutex_lock(&mutex_);
    assignHolder();
  }

  void unlock()
  {
    unassignHolder();
    pthread_mutex_unlock(&mutex_);
  }

  pthread_mutex_t* getPthreadMutex()
  {
    return &mutex_;
  }

 private:
  
  MutexLock(const MutexLock &);
  MutexLock &operator=(const MutexLock &);

  void unassignHolder()
  {
    holder_ = 0;
  }

  void assignHolder()
  {
    holder_ = static_cast<pid_t>(::syscall(SYS_gettid));
  }

  pthread_mutex_t mutex_;
  pid_t holder_;
};


class MutexLockGuard 
{
 public:
  explicit MutexLockGuard(MutexLock& mutex)
    : mutex_(mutex)
  {
    mutex_.lock();
  }

  ~MutexLockGuard()
  {
    mutex_.unlock();
  }

 private:
  MutexLockGuard(const MutexLockGuard &);
  MutexLockGuard &operator=(const MutexLockGuard &);

  MutexLock& mutex_;
};

  为了提高MutextLock的易用性,增加了一个MutexLockGuard 类来封装MutextLock,实际使用的时候直接使用MutexLockGuard,这样就能防止忘记释放Mutex的情况出现(MutexLockGuard 超出作用域(一般是一个栈上变量)就会自动释放,调用析构函数,destroy掉mutex)。

  以上封装其实就是所谓的RAII的一个具体实践,C++中的智能指针shared_ptr,weak_ptr,unique_ptr 也是RAII的优秀实现。

  注:其实在C++11 线程库中已经有lock guard可以直接使用了(std::lock_guard ,只需要include<mutex>),不需要自己再写一遍,对于没有迁移到C++11上的项目可以使用自己封装的Mutex来提高易用性。

 

Java 常用字符串操作总结

1. String转ASCII码

    
public static String stringToAscii(String value) {
        StringBuffer sbu = new StringBuffer();
        char[] chars = value.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            if (i <= chars.length - 1) {
                int ii = chars[i];
                sbu.append(Integer.toHexString(ii));
            } else {
                sbu.append((int) chars[i]);
            }
        }
        return sbu.toString();
    } 

2. ASCII码转String

    
public static String asciiToString(String value) {
        if("".equals(value) || value == null){
            return "";
        }
        StringBuffer sBuffer = new StringBuffer();
        char[] chars = value.toCharArray();
        for (int i = 0; i < chars.length; i = i + 2) {
            if (i < chars.length - 1) {
                int ii = Integer.valueOf(
                    Character.toString(chars[i]) + Character.toString(chars[i + 1]), 16);
                sBuffer.append((char) (ii));
            }
        }
        return sBuffer.toString();
    }

 

3. 输入流转字节数组

    
public static String inputStream2Byte(InputStream inputStream) throws IOException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len = -1;
        while ((len = inputStream.read(buffer)) != -1) {
            bos.write(buffer, 0, len);
        }
        bos.close();
        return new String(bos.toByteArray(), "UTF-8");
    }

4. 输入流转字符串

 

    
public static String InputStreamTOString(InputStream in) throws Exception {
        
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        byte[] data = new byte[1024];
        int count = -1;
        while ((count = in.read(data, 0, 2048)) != -1)
            outStream.write(data, 0, count);
        
        data = null;
        return new String(outStream.toByteArray());
    }

 

5. 判断输入是不是包含中文

   
 public static boolean isChinese(char c) {
        Character.UnicodeBlock ub = Character.UnicodeBlock.of(c);
        if (ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS || ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS
                || ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A || ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B
                || ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION || ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS
                || ub == Character.UnicodeBlock.GENERAL_PUNCTUATION) {
            return true;
        }
        return false;
    }
 
    public static boolean isChinese(String strName) {
        char[] ch = strName.toCharArray();
        for (int i = 0; i < ch.length; i++) {
            char c = ch[i];
            if (isChinese(c)) {
                return true;
            }
        }
        return false;
    }

  

6. 检验子网掩码的合法性:子网掩码转化为二进制之后不能全是1,也不能全是0,必须是连续的1或者连续的0,也就是说1和0不能交替出现。

  实现方式,先将4个整数字段按照每八位拼接起来,软后转化为二进制显示方式,使用正则表达式进行匹配。

  
  public static boolean checkMask(String maskStr){
        String[] ips = maskStr.split("//.");  
        String binaryVal = "";  
        for (int i = 0; i < ips.length; i++)  
        {  
            String binaryStr = Integer.toBinaryString(Integer.parseInt(ips[i]));    
            Integer times = 8 - binaryStr.length();  
              
            for(int j = 0; j < times; j++)  
            {  
                binaryStr = "0" +  binaryStr;  //补齐八位,每次需要进行八位合并
            }  
            binaryVal += binaryStr;  
        }  
        Pattern regxPattern = Pattern.compile("^[1]*[0]*$");
        if(regxPattern.matcher(binaryVal).matches())  
        {  
            return true;  
        }else {
            return false;
        }
    }

 

7. 检查IP的合法性,并转化为整数表示

    
public static boolean validIP(String ip) {
        if (ip == null) {
            return false;
        }
     String IP_REGEX  = "//b((?!//d//d//d)//d+|1//d//d|2[0-4]//d|25[0-5])//."
                                                       + "((?!//d//d//d)//d+|1//d//d|2[0-4]//d|25[0-5])//."
                                                       + "((?!//d//d//d)//d+|1//d//d|2[0-4]//d|25[0-5])//."
                                                       + "((?!//d//d//d)//d+|1//d//d|2[0-4]//d|25[0-5])//b";
        Pattern     ipPattern           = Pattern.compile(IP_REGEX);
        Matcher matcher = ipPattern.matcher(ip);
        return matcher.matches();
    }

    public static Integer ipString2Int(String strIp){
        if(!validIP(strIp)) return null;

        int[] ip = new int[4];
        String[] ips = strIp.split("//.");

        ip[0] = Integer.parseInt(ips[0]);
        ip[1] = Integer.parseInt(ips[1]);
        ip[2] = Integer.parseInt(ips[2]);
        ip[3] = Integer.parseInt(ips[3]);
        return (ip[0] << 24) + (ip[1] << 16) + (ip[2] << 8) + ip[3];
    }

  

  

 

  

 

Android 开发有用代码积累

  Android开发需求变化快,开发周期要求尽量短,接下来一系列文章从实际使用出发总结一些常用的代码片段,便于查找,也为后来人提供一份参考。

1.获取Manifest的基本信息(升级页面和软件关于页面一般会使用到)


Context mContext = XXXApplication.getInstance().getApplicationContext(); //获取Application的Context ,当然也可以获取当前的Activity的Context, Application一般是单例 packageName = mContext.getPackageName(); //获取包名,也就是manifest中的package选项的值 PackageInfo info = mContext.getPackageManager().getPackageInfo( mContext.getPackageName(), 0); //

String versionName = info.versionName;
int versionCode = info.versionCode;

2.获取手机屏幕参数(对于屏幕适配很重要,毕竟Android手机的屏幕种类太多了)


Resources resources = XXXApplication.getInstance().getResources(); float scale = resources.getDisplayMetrics().density;//屏幕密度因子,用于在px与dp之间转化 float scaledDensity = resources.getDisplayMetrics().scaledDensity; float disPlayWidth = resources.getDisplayMetrics().widthPixels; float disPlayHeight = resources.getDisplayMetrics().heightPixels;

  public static int dip2px(float dp) {
    return (int) (dp * scale + 0.5f);
  }

  public static int px2dip(float px) {
  return (int) (px / scale + 0.5f);
  }

  后面专门用篇文章来介绍一下我对不同屏幕适配的心得,今天就先介绍上面的代码。

 

3. 隐藏软键盘

 
InputMethodManager imm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);


//在含有EditText的页面,有可能一进入该Activity软键盘就弹出来,可以通过以下方法来禁止

 getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);

  

4. 安装APK(比如说下载了升级软件的时候,需要安装替换旧版)


File apkfile = new File(apkFilePath); if (!apkfile.exists()) { return; } Intent i = new Intent(Intent.ACTION_VIEW); i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); i.setDataAndType(Uri.parse("file://" + apkfile.toString()), "application/vnd.android.package-archive"); mContext.startActivity(i);

 

5. 设置TextView的文字大小

  TextView通过getTextSize()返回的值是px,setTextSize()却是sp,所以一般不能直接用getTextSize()获取的值来设置。可以指定setTextSize()的单位:

  TypedValue.COMPLEX_UNIT_PX : PX

  TypedValue.COMPLEX_UNIT_SP : SP

  TypedValue.COMPLEX_UNIT_DIP : DIP

  可以这样使用:

 mTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension(R.dimen.text_size)); //使用getDimension 是为了屏幕适配,这里先点到为止

  

6. 手机震动功能

import android.app.Activity;  
import android.app.Service;  
import android.os.Vibrator;  
  
public class Myvibrator {   
    public static void Vibrate(final Activity activity, long milliseconds) {  
        Vibrator vib = (Vibrator) activity.getSystemService(Service.VIBRATOR_SERVICE);  
        vib.vibrate(milliseconds);  
    }  
     public static void Vibrate(final Activity activity, long[] pattern,boolean isRepeat) {  
         Vibrator vib = (Vibrator) activity.getSystemService(Service.VIBRATOR_SERVICE);  
         vib.vibrate(pattern, isRepeat ? 1 : -1);  
     }  
 }  

  在manifest文件中需要增加权限:<uses-permission android:name=”android.permission.VIBRATE” />  。通过上面操作,我们可以使用Myvibrator所定义的函数了。两个Vibrate函数的参数简单介绍如下:

  final Activity activity :调用该方法的Activity实例
  long milliseconds :震动的时长,单位是毫秒

  long[] pattern :自定义震动模式 。数组中数字的含义依次是[静止时长,震动时长,静止时长,震动时长。。。]时长的单位是毫秒

  boolean isRepeat : 是否反复震动,如果是true,反复震动,如果是false,只震动一次

7. 软键盘弹出不把整个布局顶起来

  在manifest文件对应的activity中设置:android:windowSoftInputMode=”adjustPan” 属性

8. 设置Activity为横屏或者竖屏

  在manifest文件对应的activity中设置:android:screenOrientation=”portrait” (竖屏,landscape 代表横屏)