高并发下线程安全的单例模式(最全最经典)

原文:http://blog.csdn.net/cselmu9/article/details/51366946

什么是单例模式?

在文章开始之前我们还是有必要介绍一下什么是单例模式。单例模式是为确保一个类只有一个实例,并为整个系统提供一个全局访问点的一种模式方法。

从概念中体现出了单例的一些特点:

(1)、在任何情况下,单例类永远只有一个实例存在

(2)、单例需要有能力为整个系统提供这一唯一实例  


各式各样的单例实现

温馨提示:本文叙述中涉及到的相关源码可以在这里进行下载源码,读者可免积分下载。

1、饿汉式单例

饿汉式单例是指在方法调用前,实例就已经创建好了。下面是实现代码:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. package org.mlinge.s01;  
  2.   
  3. public class MySingleton {  
  4.       
  5.     private static MySingleton instance = new MySingleton();  
  6.       
  7.     private MySingleton(){}  
  8.       
  9.     public static MySingleton getInstance() {  
  10.         return instance;  
  11.     }  
  12.       
  13. }  
以上是单例的饿汉式实现,我们来看看饿汉式在多线程下的执行情况,给出一段多线程的执行代码:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. package org.mlinge.s01;  
  2.   
  3. public class MyThread extends Thread{  
  4.       
  5.     @Override  
  6.     public void run() {   
  7.         System.out.println(MySingleton.getInstance().hashCode());  
  8.     }  
  9.       
  10.     public static void main(String[] args) {   
  11.           
  12.         MyThread[] mts = new MyThread[10];  
  13.         for(int i = 0 ; i < mts.length ; i++){  
  14.             mts[i] = new MyThread();  
  15.         }  
  16.           
  17.         for (int j = 0; j < mts.length; j++) {  
  18.             mts[j].start();  
  19.         }  
  20.     }  
  21. }  

以上代码运行结果:

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. 1718900954  
  2. 1718900954  
  3. 1718900954  
  4. 1718900954  
  5. 1718900954  
  6. 1718900954  
  7. 1718900954  
  8. 1718900954  
  9. 1718900954  
  10. 1718900954  

从运行结果可以看出实例变量额hashCode值一致,这说明对象是同一个,饿汉式单例实现了。

2、懒汉式单例

懒汉式单例是指在方法调用获取实例时才创建实例,因为相对饿汉式显得“不急迫”,所以被叫做“懒汉模式”。下面是实现代码:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. package org.mlinge.s02;  
  2.   
  3. public class MySingleton {  
  4.       
  5.     private static MySingleton instance = null;  
  6.       
  7.     private MySingleton(){}  
  8.       
  9.     public static MySingleton getInstance() {  
  10.         if(instance == null){//懒汉式  
  11.             instance = new MySingleton();  
  12.         }  
  13.         return instance;  
  14.     }  
  15. }  
这里实现了懒汉式的单例,但是熟悉多线程并发编程的朋友应该可以看出,在多线程并发下这样的实现是无法保证实例实例唯一的,甚至可以说这样的失效是完全错误的,下面我们就来看一下多线程并发下的执行情况,这里为了看到效果,我们对上面的代码做一小点修改:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. package org.mlinge.s02;  
  2.   
  3. public class MySingleton {  
  4.       
  5.     private static MySingleton instance = null;  
  6.       
  7.     private MySingleton(){}  
  8.       
  9.     public static MySingleton getInstance() {  
  10.         try {   
  11.             if(instance != null){//懒汉式   
  12.                   
  13.             }else{  
  14.                 //创建实例之前可能会有一些准备性的耗时工作   
  15.                 Thread.sleep(300);  
  16.                 instance = new MySingleton();  
  17.             }  
  18.         } catch (InterruptedException e) {   
  19.             e.printStackTrace();  
  20.         }  
  21.         return instance;  
  22.     }  
  23. }  
这里假设在创建实例前有一些准备性的耗时工作要处理,多线程调用:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. package org.mlinge.s02;  
  2.   
  3. public class MyThread extends Thread{  
  4.       
  5.     @Override  
  6.     public void run() {   
  7.         System.out.println(MySingleton.getInstance().hashCode());  
  8.     }  
  9.       
  10.     public static void main(String[] args) {   
  11.           
  12.         MyThread[] mts = new MyThread[10];  
  13.         for(int i = 0 ; i < mts.length ; i++){  
  14.             mts[i] = new MyThread();  
  15.         }  
  16.           
  17.         for (int j = 0; j < mts.length; j++) {  
  18.             mts[j].start();  
  19.         }  
  20.     }  
  21. }  

执行结果如下:

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. 1210420568  
  2. 1210420568  
  3. 1935123450  
  4. 1718900954  
  5. 1481297610  
  6. 1863264879  
  7. 369539795  
  8. 1210420568  
  9. 1210420568  
  10. 602269801  

从这里执行结果可以看出,单例的线程安全性并没有得到保证,那要怎么解决呢?

3、线程安全的懒汉式单例

要保证线程安全,我们就得需要使用同步锁机制,下面就来看看我们如何一步步的解决 存在线程安全问题的懒汉式单例(错误的单例)。

(1)、 方法中声明synchronized关键字

出现非线程安全问题,是由于多个线程可以同时进入getInstance()方法,那么只需要对该方法进行synchronized的锁同步即可:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. package org.mlinge.s03;  
  2.   
  3. public class MySingleton {  
  4.       
  5.     private static MySingleton instance = null;  
  6.       
  7.     private MySingleton(){}  
  8.       
  9.     public synchronized static MySingleton getInstance() {  
  10.         try {   
  11.             if(instance != null){//懒汉式   
  12.                   
  13.             }else{  
  14.                 //创建实例之前可能会有一些准备性的耗时工作   
  15.                 Thread.sleep(300);  
  16.                 instance = new MySingleton();  
  17.             }  
  18.         } catch (InterruptedException e) {   
  19.             e.printStackTrace();  
  20.         }  
  21.         return instance;  
  22.     }  
  23. }  

此时任然使用前面验证多线程下执行情况的MyThread类来进行验证,将其放入到org.mlinge.s03包下运行,执行结果如下:

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. 1689058373  
  2. 1689058373  
  3. 1689058373  
  4. 1689058373  
  5. 1689058373  
  6. 1689058373  
  7. 1689058373  
  8. 1689058373  
  9. 1689058373  
  10. 1689058373  

从执行结果上来看,问题已经解决了,但是这种实现方式的运行效率会很低。同步方法效率低,那我们考虑使用同步代码块来实现:

(2)、 同步代码块实现

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. package org.mlinge.s03;  
  2.   
  3. public class MySingleton {  
  4.       
  5.     private static MySingleton instance = null;  
  6.       
  7.     private MySingleton(){}  
  8.       
  9.     //public synchronized static MySingleton getInstance() {  
  10.     public static MySingleton getInstance() {  
  11.         try {   
  12.             synchronized (MySingleton.class) {  
  13.                 if(instance != null){//懒汉式   
  14.                       
  15.                 }else{  
  16.                     //创建实例之前可能会有一些准备性的耗时工作   
  17.                     Thread.sleep(300);  
  18.                     instance = new MySingleton();  
  19.                 }  
  20.             }  
  21.         } catch (InterruptedException e) {   
  22.             e.printStackTrace();  
  23.         }  
  24.         return instance;  
  25.     }  
  26. }  
这里的实现能够保证多线程并发下的线程安全性,但是这样的实现将全部的代码都被锁上了,同样的效率很低下

(3)、 针对某些重要的代码来进行单独的同步(可能非线程安全)

针对某些重要的代码进行单独的同步,而不是全部进行同步,可以极大的提高执行效率,我们来看一下:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. package org.mlinge.s04;  
  2.   
  3. public class MySingleton {  
  4.       
  5.     private static MySingleton instance = null;  
  6.       
  7.     private MySingleton(){}  
  8.        
  9.     public static MySingleton getInstance() {  
  10.         try {    
  11.             if(instance != null){//懒汉式   
  12.                   
  13.             }else{  
  14.                 //创建实例之前可能会有一些准备性的耗时工作   
  15.                 Thread.sleep(300);  
  16.                 synchronized (MySingleton.class) {  
  17.                     instance = new MySingleton();  
  18.                 }  
  19.             }   
  20.         } catch (InterruptedException e) {   
  21.             e.printStackTrace();  
  22.         }  
  23.         return instance;  
  24.     }  
  25. }  
此时同样使用前面验证多线程下执行情况的MyThread类来进行验证,将其放入到org.mlinge.s04包下运行,执行结果如下:
[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. 1481297610  
  2. 397630378  
  3. 1863264879  
  4. 1210420568  
  5. 1935123450  
  6. 369539795  
  7. 590202901  
  8. 1718900954  
  9. 1689058373  
  10. 602269801  
从运行结果来看,这样的方法进行代码块同步,代码的运行效率是能够得到提升,但是 没能保住线程的安全性 看来还得进一步考虑如何解决此问题。

(4)、 Double Check Locking 双检查锁机制(推荐)

为了达到线程安全,又能提高代码执行效率,我们这里可以采用DCL的双检查锁机制来完成,代码实现如下:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. package org.mlinge.s05;  
  2.   
  3. public class MySingleton {  
  4.       
  5.     //使用volatile关键字保其可见性  
  6.     volatile private static MySingleton instance = null;  
  7.       
  8.     private MySingleton(){}  
  9.        
  10.     public static MySingleton getInstance() {  
  11.         try {    
  12.             if(instance != null){//懒汉式   
  13.                   
  14.             }else{  
  15.                 //创建实例之前可能会有一些准备性的耗时工作   
  16.                 Thread.sleep(300);  
  17.                 synchronized (MySingleton.class) {  
  18.                     if(instance == null){//二次检查  
  19.                         instance = new MySingleton();  
  20.                     }  
  21.                 }  
  22.             }   
  23.         } catch (InterruptedException e) {   
  24.             e.printStackTrace();  
  25.         }  
  26.         return instance;  
  27.     }  
  28. }  
将前面验证多线程下执行情况的MyThread类放入到org.mlinge.s05包下运行,执行结果如下:
[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. 369539795  
  2. 369539795  
  3. 369539795  
  4. 369539795  
  5. 369539795  
  6. 369539795  
  7. 369539795  
  8. 369539795  
  9. 369539795  
  10. 369539795  
从运行结果来看,该中方法保证了多线程并发下的线程安全性。

这里在声明变量时使用了volatile关键字来保证其线程间的可见性;在同步代码块中使用二次检查,以保证其不被重复实例化。集合其二者,这种实现方式既保证了其高效性,也保证了其线程安全性

4、使用静态内置类实现单例模式

DCL解决了多线程并发下的线程安全问题,其实使用其他方式也可以达到同样的效果,代码实现如下:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. package org.mlinge.s06;  
  2.   
  3. public class MySingleton {  
  4.       
  5.     //内部类  
  6.     private static class MySingletonHandler{  
  7.         private static MySingleton instance = new MySingleton();  
  8.     }   
  9.       
  10.     private MySingleton(){}  
  11.        
  12.     public static MySingleton getInstance() {   
  13.         return MySingletonHandler.instance;  
  14.     }  
  15. }  
以上代码就是使用静态内置类实现了单例模式,这里将前面验证多线程下执行情况的MyThread类放入到org.mlinge.s06包下运行,执行结果如下:
[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. 1718900954  
  2. 1718900954  
  3. 1718900954  
  4. 1718900954  
  5. 1718900954  
  6. 1718900954  
  7. 1718900954  
  8. 1718900954  
  9. 1718900954  
  10. 1718900954  
从运行结果来看,静态内部类实现的单例在多线程并发下单个实例得到了保证。

5、序列化与反序列化的单例模式实现

静态内部类虽然保证了单例在多线程并发下的线程安全性,但是在遇到序列化对象时,默认的方式运行得到的结果就是多例的。

代码实现如下:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. package org.mlinge.s07;  
  2.   
  3. import java.io.Serializable;  
  4.   
  5. public class MySingleton implements Serializable {  
  6.        
  7.     private static final long serialVersionUID = 1L;  
  8.   
  9.     //内部类  
  10.     private static class MySingletonHandler{  
  11.         private static MySingleton instance = new MySingleton();  
  12.     }   
  13.       
  14.     private MySingleton(){}  
  15.        
  16.     public static MySingleton getInstance() {   
  17.         return MySingletonHandler.instance;  
  18.     }  
  19. }  
序列化与反序列化测试代码:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. package org.mlinge.s07;  
  2.   
  3. import java.io.File;  
  4. import java.io.FileInputStream;  
  5. import java.io.FileNotFoundException;  
  6. import java.io.FileOutputStream;  
  7. import java.io.IOException;  
  8. import java.io.ObjectInputStream;  
  9. import java.io.ObjectOutputStream;  
  10.   
  11. public class SaveAndReadForSingleton {  
  12.       
  13.     public static void main(String[] args) {  
  14.         MySingleton singleton = MySingleton.getInstance();  
  15.           
  16.         File file = new File("MySingleton.txt");  
  17.           
  18.         try {  
  19.             FileOutputStream fos = new FileOutputStream(file);  
  20.             ObjectOutputStream oos = new ObjectOutputStream(fos);  
  21.             oos.writeObject(singleton);  
  22.             fos.close();  
  23.             oos.close();  
  24.             System.out.println(singleton.hashCode());  
  25.         } catch (FileNotFoundException e) {   
  26.             e.printStackTrace();  
  27.         } catch (IOException e) {   
  28.             e.printStackTrace();  
  29.         }  
  30.           
  31.         try {  
  32.             FileInputStream fis = new FileInputStream(file);  
  33.             ObjectInputStream ois = new ObjectInputStream(fis);  
  34.             MySingleton rSingleton = (MySingleton) ois.readObject();  
  35.             fis.close();  
  36.             ois.close();  
  37.             System.out.println(rSingleton.hashCode());  
  38.         } catch (FileNotFoundException e) {   
  39.             e.printStackTrace();  
  40.         } catch (IOException e) {   
  41.             e.printStackTrace();  
  42.         } catch (ClassNotFoundException e) {   
  43.             e.printStackTrace();  
  44.         }  
  45.           
  46.     }  
  47. }  
运行以上代码,得到的结果如下:

[sql]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. 865113938  
  2. 1442407170  
从结果中我们发现,序列号对象的hashCode和反序列化后得到的对象的hashCode值不一样,说明反序列化后返回的对象是重新实例化的,单例被破坏了。那怎么来解决这一问题呢?

解决办法就是在反序列化的过程中使用readResolve()方法,单例实现的代码如下:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. package org.mlinge.s07;  
  2.   
  3. import java.io.ObjectStreamException;  
  4. import java.io.Serializable;  
  5.   
  6. public class MySingleton implements Serializable {  
  7.        
  8.     private static final long serialVersionUID = 1L;  
  9.   
  10.     //内部类  
  11.     private static class MySingletonHandler{  
  12.         private static MySingleton instance = new MySingleton();  
  13.     }   
  14.       
  15.     private MySingleton(){}  
  16.        
  17.     public static MySingleton getInstance() {   
  18.         return MySingletonHandler.instance;  
  19.     }  
  20.       
  21.     //该方法在反序列化时会被调用,该方法不是接口定义的方法,有点儿约定俗成的感觉  
  22.     protected Object readResolve() throws ObjectStreamException {  
  23.         System.out.println("调用了readResolve方法!");  
  24.         return MySingletonHandler.instance;   
  25.     }  
  26. }  
再次运行上面的测试代码,得到的结果如下:

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. 865113938  
  2. 调用了readResolve方法!  
  3. 865113938  
从运行结果可知,添加readResolve方法后反序列化后得到的实例和序列化前的是同一个实例,单个实例得到了保证。

6、使用static代码块实现单例

静态代码块中的代码在使用类的时候就已经执行了,所以可以应用静态代码块的这个特性的实现单例设计模式。

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. package org.mlinge.s08;  
  2.   
  3. public class MySingleton{  
  4.        
  5.     private static MySingleton instance = null;  
  6.        
  7.     private MySingleton(){}  
  8.   
  9.     static{  
  10.         instance = new MySingleton();  
  11.     }  
  12.       
  13.     public static MySingleton getInstance() {   
  14.         return instance;  
  15.     }   
  16. }  
测试代码如下:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. package org.mlinge.s08;  
  2.   
  3. public class MyThread extends Thread{  
  4.       
  5.     @Override  
  6.     public void run() {   
  7.         for (int i = 0; i < 5; i++) {  
  8.             System.out.println(MySingleton.getInstance().hashCode());  
  9.         }  
  10.     }  
  11.       
  12.     public static void main(String[] args) {   
  13.           
  14.         MyThread[] mts = new MyThread[3];  
  15.         for(int i = 0 ; i < mts.length ; i++){  
  16.             mts[i] = new MyThread();  
  17.         }  
  18.           
  19.         for (int j = 0; j < mts.length; j++) {  
  20.             mts[j].start();  
  21.         }  
  22.     }  
  23. }  
运行结果如下:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. 1718900954  
  2. 1718900954  
  3. 1718900954  
  4. 1718900954  
  5. 1718900954  
  6. 1718900954  
  7. 1718900954  
  8. 1718900954  
  9. 1718900954  
  10. 1718900954  
  11. 1718900954  
  12. 1718900954  
  13. 1718900954  
  14. 1718900954  
  15. 1718900954  
从运行结果看,单例的线程安全性得到了保证。

7、使用枚举数据类型实现单例模式

枚举enum和静态代码块的特性相似,在使用枚举时,构造方法会被自动调用,利用这一特性也可以实现单例:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. package org.mlinge.s09;  
  2.   
  3. public enum EnumFactory{   
  4.       
  5.     singletonFactory;  
  6.       
  7.     private MySingleton instance;  
  8.       
  9.     private EnumFactory(){//枚举类的构造方法在类加载是被实例化  
  10.         instance = new MySingleton();  
  11.     }  
  12.           
  13.     public MySingleton getInstance(){  
  14.         return instance;  
  15.     }  
  16.       
  17. }  
  18.   
  19. class MySingleton{//需要获实现单例的类,比如数据库连接Connection  
  20.     public MySingleton(){}   
  21. }  
测试代码如下:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. package org.mlinge.s09;  
  2.   
  3. public class MyThread extends Thread{  
  4.       
  5.     @Override  
  6.     public void run() {   
  7.         System.out.println(EnumFactory.singletonFactory.getInstance().hashCode());  
  8.     }  
  9.       
  10.     public static void main(String[] args) {   
  11.           
  12.         MyThread[] mts = new MyThread[10];  
  13.         for(int i = 0 ; i < mts.length ; i++){  
  14.             mts[i] = new MyThread();  
  15.         }  
  16.           
  17.         for (int j = 0; j < mts.length; j++) {  
  18.             mts[j].start();  
  19.         }  
  20.     }  
  21. }  
执行后得到的结果:
[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. 1481297610  
  2. 1481297610  
  3. 1481297610  
  4. 1481297610  
  5. 1481297610  
  6. 1481297610  
  7. 1481297610  
  8. 1481297610  
  9. 1481297610  
  10. 1481297610  
运行结果表明单例得到了保证,但是这样写枚举类被完全暴露了,据说违反了“职责单一原则”,那我们来看看怎么进行改造呢。

8、完善使用enum枚举实现单例模式

不暴露枚举类实现细节的封装代码如下:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. package org.mlinge.s10;  
  2.   
  3. public class ClassFactory{   
  4.       
  5.     private enum MyEnumSingleton{  
  6.         singletonFactory;  
  7.           
  8.         private MySingleton instance;  
  9.           
  10.         private MyEnumSingleton(){//枚举类的构造方法在类加载是被实例化  
  11.             instance = new MySingleton();  
  12.         }  
  13.    
  14.         public MySingleton getInstance(){  
  15.             return instance;  
  16.         }  
  17.     }   
  18.    
  19.     public static MySingleton getInstance(){  
  20.         return MyEnumSingleton.singletonFactory.getInstance();  
  21.     }  
  22. }  
  23.   
  24. class MySingleton{//需要获实现单例的类,比如数据库连接Connection  
  25.     public MySingleton(){}   
  26. }  
验证单例实现的代码如下:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. package org.mlinge.s10;  
  2.   
  3. public class MyThread extends Thread{  
  4.       
  5.     @Override  
  6.     public void run() {   
  7.         System.out.println(ClassFactory.getInstance().hashCode());  
  8.     }  
  9.       
  10.     public static void main(String[] args) {   
  11.           
  12.         MyThread[] mts = new MyThread[10];  
  13.         for(int i = 0 ; i < mts.length ; i++){  
  14.             mts[i] = new MyThread();  
  15.         }  
  16.           
  17.         for (int j = 0; j < mts.length; j++) {  
  18.             mts[j].start();  
  19.         }  
  20.     }  
  21. }  
验证结果:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. 1935123450  
  2. 1935123450  
  3. 1935123450  
  4. 1935123450  
  5. 1935123450  
  6. 1935123450  
  7. 1935123450  
  8. 1935123450  
  9. 1935123450  
  10. 1935123450  
验证结果表明,完善后的单例实现更为合理。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在Linux系统下,可以使用线程实现串口通信例程。 首先,需要引入一些头文件,如`<stdio.h>, <stdlib.h>, <unistd.h>, <fcntl.h>, <termios.h>, <pthread.h>`,以便使用相关函数和数据结构。 接下来,打开串口设备文件,使用`open()`函数,并通过`<fcntl.h>`中的`O_RDWR`参数设置为可读写模式。例如,打开`/dev/ttyS0`串口设备: ```c int fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY); if (fd == -1) { perror("打开串口失败"); exit(EXIT_FAILURE); } ``` 然后,配置串口属性,包括波特率、数据位、停止位等。首先需要获取当前串口属性,使用`tcgetattr()`函数,并通过`<termios.h>`中的数据结构`struct termios`进行配置。例如,设置波特率为115200: ```c struct termios attr; if (tcgetattr(fd, &attr) == -1) { perror("获取串口属性失败"); close(fd); exit(EXIT_FAILURE); } cfsetispeed(&attr, B115200); cfsetospeed(&attr, B115200); if (tcsetattr(fd, TCSANOW, &attr) == -1) { perror("设置串口属性失败"); close(fd); exit(EXIT_FAILURE); } ``` 接下来,创建一个线程,用于接收串口数据。使用`pthread_create()`函数,并编写线程函数。例如,以下为接收串口数据的线程函数: ```c void *receiveThread(void *arg) { char buffer[256]; int len; while (1) { len = read(fd, buffer, sizeof(buffer)); if (len > 0) { // 处理接收到的数据 // ... } } return NULL; } pthread_t tid; pthread_create(&tid, NULL, receiveThread, NULL); ``` 最后,主线程(或其他线程)可以通过`write()`函数向串口发送数据。例如,向串口发送一个字符串: ```c char *str = "Hello, Serial!"; write(fd, str, strlen(str)); ``` 整个程序运行时,主线程可以继续执行其他任务,而串口数据的接收则在单独的线程中进行。 这样,就完成了一个简单的Linux下线程实现串口通信的例程。 ### 回答2: 在Linux下,可以通过使用线程来实现串口通信。下面是一个简单的示例代码: ```c #include <stdio.h> #include <fcntl.h> #include <termios.h> #include <unistd.h> #include <pthread.h> int fd; // 串口文件描述符 pthread_t thread_id; // 线程ID void* read_thread(void* arg) { char buf[255]; while(1) { int len = read(fd, buf, sizeof(buf)); // 从串口读取数据 if (len > 0) { buf[len] = '\0'; // 添加字符串结束符 printf("接收到的数据: %s\n", buf); } } } int main() { // 打开串口 fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NDELAY); if (fd == -1) { printf("无法打开串口\n"); return -1; } // 配置串口 struct termios options; tcgetattr(fd, &options); cfsetispeed(&options, B9600); cfsetospeed(&options, B9600); options.c_cflag |= (CLOCAL | CREAD); options.c_cflag &= ~PARENB; options.c_cflag &= ~CSTOPB; options.c_cflag &= ~CSIZE; options.c_cflag |= CS8; options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); tcsetattr(fd, TCSANOW, &options); // 创建读取数据的线程 pthread_create(&thread_id, NULL, read_thread, NULL); // 主线程继续执行其他任务 while(1) { // 发送数据到串口 char msg[] = "Hello, Serial Port!"; write(fd, msg, sizeof(msg)); usleep(1000000); // 等待1秒 } // 关闭串口 close(fd); return 0; } ``` 在上述代码中,通过`open`函数打开了串口设备文件`/dev/ttyS0`(请根据实际情况更改),然后使用`termios`结构体配置了串口的波特率、数据位、停止位等属性。接下来,通过`pthread_create`函数创建了一个线程,该线程负责读取串口数据。主线程则负责发送数据到串口。 需要注意的是,该例程只是一个简单的示例,仅用于说明线程实现串口通信的基本思路。实际应用中,还需考虑数据的解析、错误处理、线程同步等问题。 ### 回答3: 在Linux下,可以使用串口通信库来实现线程的串口通信例程。下面是一个简单的例子: 1. 首先,我们需要安装和配置串口通信库。常用的库包括`libserialport`和`termios`。你可以使用包管理工具来安装这些库。 2. 在程序中,我们需要引入相关的头文件和库: ``` #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <pthread.h> #include <serialport.h> ``` 3. 然后,创建一个线程来读取串口数据。可以定义一个函数作为线程入口点,例如`serial_read`: ``` void *serial_read(void *data) { struct sp_port *serial_port = (struct sp_port *)data; char buffer[256]; int n; while (1) { n = sp_blocking_read(serial_port, buffer, sizeof(buffer), 100); if (n > 0) { // 处理接收到的数据 printf("Received: %.*s\n", n, buffer); } else { // 读取数据出错或超时 printf("Serial read error or timeout\n"); } } return NULL; } ``` 4. 接下来,创建一个线程来发送串口数据。可以定义一个函数作为线程入口点,例如`serial_write`: ``` void *serial_write(void *data) { struct sp_port *serial_port = (struct sp_port *)data; char message[] = "Hello, Serial Port!\n"; while (1) { sp_nonblocking_write(serial_port, message, sizeof(message) - 1); usleep(1000000); // 暂停1秒钟 } return NULL; } ``` 5. 在主函数中,打开串口设备并创建两个线程: ``` int main() { struct sp_port *serial_port; pthread_t read_thread, write_thread; // 打开串口设备(例如:/dev/ttyS0) sp_get_port_by_name("ttyS0", &serial_port); sp_open(serial_port, SP_MODE_READ_WRITE); // 创建读取线程 pthread_create(&read_thread, NULL, serial_read, (void *)serial_port); // 创建写入线程 pthread_create(&write_thread, NULL, serial_write, (void *)serial_port); // 等待线程结束 pthread_join(read_thread, NULL); pthread_join(write_thread, NULL); // 关闭串口设备 sp_close(serial_port); sp_free_port(serial_port); return 0; } ``` 这是一个简单的例程,通过两个线程实现了线程的串口通信。读取线程通过不断调用`sp_blocking_read`函数读取串口数据,而写入线程通过不断调用`sp_nonblocking_write`函数发送串口数据。你可以根据需求来修改和扩展这个例程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值