多线程拾遗-2

Synchronize、volatile、lock相关

Synchronize修饰方法:

1,方法内变量不存在线程不安全问题,因为在内存模型中,每一个线程对应一个栈,而里方法里面的变量会在每个栈里有一个,不存在互相干扰的问题。
2,实例变量非线程安全。其实就是,当不同的线程对同一块内存上的对象进行操作时,因为cpu的随机执行,可能会出现a线程执行到一半被b线程抢占了cpu,导致b拿到的不是最新的状态,或者b线程的操作又被后来的a操作覆盖。
因此,当多个线程访问同一个对象时,需要加上synchronize关键字来修饰该对象,表示当某线程进入synchronize修饰的方法时,其他线程必须等待该线程执行完成,才可以继续执行同样被synchronize修饰的方法。
需要注意,必须是synchronize修饰的同一个对象,才会发生某线程等待的情况。如果a线程占有的objectA锁而b线程占有的是objecyB锁,他们直接会是异步执行。是否锁的同一个对象可以通过:
1, 是否synchronize()中是同一个对象来判断。
2, 当修饰方法时,this是不是同一个东西来判断。

Synchronize是可重入锁:

public class SynchronizedRelock {

    synchronized public void service1(){
        System.out.println("service1");
        service2();
    }

    synchronized public void service2(){
        System.out.println("service2");
        service3();
    }

    synchronized public void service3(){
        System.out.println("service3");
    }

    public static void main(String[] args) {
        new TestThread(new SynchronizedRelock()).start();
    }
}

class TestThread extends Thread{

    private SynchronizedRelock synchronizedRelock;

    public TestThread(SynchronizedRelock synchronizedRelock){
        this.synchronizedRelock = synchronizedRelock;
    }

    @Override
    public void run(){
        synchronizedRelock.service1();
    }
}

可以看到,尽管该线程在service1()中已经锁住了this对象,即new synchronizeRelock()实例,但是依然是可以访问service2()和service3()。即可重入锁是可以自由访问任何自己已经取得锁的地方。

一个例子:

class FatherClass{
    synchronized public void fatherService(){
        System.out.println("father menthod");
    }
}

class SonClass extends FatherClass{
    synchronized public void sonService(){
        System.out.println("son methof");
        fatherService();
    }
}
public class SynchronizedRelock {

    public static void main(String[] args) {
        new TestThread(new SonClass()).start();
    }
}

class TestThread extends Thread{
    private SonClass sonClass;
    public TestThread(SonClass sonClass){
        this.sonClass = sonClass;
    }

    @Override
    public void run(){
        sonClass.sonService();
    }
}

可以看到,子类在锁定一个对象锁时,通过对象调用父类方法也是可以的,因为父类的方法锁定的也是该子类对象。但是要注意,父类和子类均需要用synchronize来修饰,synchronize关键字并不能继承。

Synchronize修饰代码块:

Synchronize修饰方法有一个弊端,就是当方法体内除了锁对象的改变,还有许多大量的非所对象的操作,由于synchronize修饰了整个方法,会导致效率低下,这个时候可以考虑使用synchronize修饰代码块来代替。
比如:

synchronize public void test(){
        Thread.sleep(10000);
        This.change();  
    }

当有两个线程同时访问该方法时,会导致他们同步执行,也就是说两个线程分别sleep 10000毫秒。
改为:

    public void test(){
    Thread.sleep(10000);
synchronized(this){
    this.change();
}
} 

这样,就可以保证两个线程异步sleep,效率提升了一倍。

脏读问题:

public class DirtyR{
    private int i;

    synchronized public void add(){
        System.out.println(i++);
    }

    synchronized public void sub(){
        System.out.println(i--);
    }

    public static void main(String[] args) {
        DirtyR dirtyR = new DirtyR();
        new AddThread(dirtyR).start();
        new SubThread(dirtyR).start();
    }
}

class AddThread extends Thread{
    DirtyR dirtyR;
    public AddThread(DirtyR dirtyR){
        this.dirtyR = dirtyR;
    }

    @Override
    public void run(){
        for (int i = 0; i < 100; i++) {
            dirtyR.add();
        }
    }

}

class SubThread extends Thread{
    DirtyR dirtyr;
    public SubThread(DirtyR dirtyR){
        this.dirtyr = dirtyR;
    }

    @Override
    public void run(){
        for (int i = 0; i < 100; i++) {
            dirtyr.sub();
        }
    }
}

运行该代码,虽然没有出现脏读的现象,但可以虽然用了synchronize来修饰,但是并不是按照加到100再减到1的顺序执行,这就为脏读提供了环境。

静态同步synchronized方法和synchronized(class)代码块

要分清楚静态方法锁的是类本身,即在内存中代表该类的一个class对象。即使使用实例来调用静态方法,也是会锁住类本身。即当a实例和b实例同时调用被synchronized修饰的静态方式时,也是被同步执行的。

public class DirtyR{
    private static int i;

    synchronized public static void add(){
        System.out.println(i++);
    }


    public static void main(String[] args) {
        DirtyR dirtyR1 = new DirtyR();
        DirtyR dirtyR2 = new DirtyR();
        new AddThread(dirtyR1).start();
        new AddThread(dirtyR2).start();
    }
}

class AddThread extends Thread{
    DirtyR dirtyR;
    public AddThread(DirtyR dirtyR){
        this.dirtyR = dirtyR;
    }

    @Override
    public void run(){
        for (int i = 0; i < 100; i++) {
            dirtyR.add();
        }
    }

}

class SubThread extends Thread{
    DirtyR dirtyr;
    public SubThread(DirtyR dirtyR){
        this.dirtyr = dirtyR;
    }

    @Override
    public void run(){
        for (int i = 0; i < 100; i++) {
            dirtyr.add();
        }
    }
}

可以看到,最终的输出也是同步的。

用String作为锁时的常量池问题。

public class DirtyR{
    private int i;
    String lock1 = "123";
    String lock2 = "123";
    public void add1(){
        synchronized (lock1){
            for (int j = 0; j < 100; j++) {
                System.out.println(Thread.currentThread()+" "+i++);
            }
        }
    }

    public void add2() throws InterruptedException {
        synchronized (lock2){
            for (int j = 0; j < 100; j++) {
                System.out.println(Thread.currentThread()+" "+i+++"  "+j);
            }
        }
    }


    public static void main(String[] args) {
        DirtyR dirtyR1 = new DirtyR();
        new AddThread(dirtyR1).start();
        new SubThread(dirtyR1).start();
    }
}

class AddThread extends Thread{
    DirtyR dirtyR;
    public AddThread(DirtyR dirtyR){
        this.dirtyR = dirtyR;
    }

    @Override
    public void run(){
        dirtyR.add1();
    }

}

class SubThread extends Thread{
    DirtyR dirtyr;
    public SubThread(DirtyR dirtyR){
        this.dirtyr = dirtyR;
    }

    @Override
    public void run(){
        try {
            dirtyr.add2();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

可以看到,虽然两个方法锁的对象时lock1和lock2,但由于常量池的存在,lock1和lock2实际上是同一个对象,随意最终还是以同步的方法在执行。可以用Object对象来作为
死锁,即双方都在等在对方释放锁后自己释放锁。

当锁对象发生改变时:

public class DirtyR{
    private int i;
    String s = "lock";
    public void add1() throws InterruptedException {
        synchronized(s){
            s="newlock";
            Thread.sleep(50);
            for (int j = 0; j < 100; j++) {
                System.out.println(Thread.currentThread().getName());
            }
        }
    }

    public void add2(){
        synchronized(s){
            System.out.println(s);
            for (int j = 0; j < 100; j++) {
                System.out.println(Thread.currentThread().getName());
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        DirtyR dirtyR1 = new DirtyR();
        new AddThread(dirtyR1).start();
        Thread.sleep(50);
        new SubThread(dirtyR1).start();
    }
}

class AddThread extends Thread{

    private DirtyR dirtyR;

    public AddThread(DirtyR dirtyR){
        this.dirtyR = dirtyR;
    }

    @Override
    public void run(){
        try {
            dirtyR.add1();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

class SubThread extends Thread{

    private DirtyR dirtyr;

    public SubThread(DirtyR dirtyR){
        this.dirtyr = dirtyR;
    }

    @Override
    public void run(){
        dirtyr.add2();
    }

}

可以看到当lock对象发生改变时,便会异步执行。

volatile关键字 (等有时间专门写volatile的东西)

volatile的主要作用是,保证线程的对某变量的读取总是从公共内存中读取的,增加了实例变量在多个线程间的可见性。
和synchronized比较,volatile更多的是增加变量在线程间的可见性,销量会高过synchronized,但是synchronized可以解决线程间的同步问题,也保证原子性。
原子类也可以保证原子性,却不可以保证代码间的执行顺序,因此synchronized还是最保险的措施。
<think>我们正在处理用户关于C++中将Unicode字符串(通常指宽字符字符串)转换为多字节字符串(如UTF-8或ANSI)的请求。根据引用资料,我们可以提供几种方法。用户提供的引用中:-引用[2]展示了多字节(ANSI)到Unicode(宽字符)的转换,但我们需要的是反向转换(Unicode到多字节)。-引用[4]展示了多字节到宽字符的转换,同样不是我们需要的方向,但提供了使用`mbstowcs_s`的示例,我们可以类比使用`wcstombs_s`。因此,我们需要提供Unicode(宽字符)到多字节的转换示例。这里我们假设用户需要的是转换为ANSI(本地代码页)或者UTF-8。注意,在Windows环境下,我们通常使用Win32API函数,如`WideCharToMultiByte`。此外,也可以使用C标准库函数(如`wcstombs`)或C++库(如`std::wstring_convert`,但注意C++11中已弃用,C++17中移除,所以可能不是最佳选择)。根据引用[4]中提供的思路,我们可以使用`wcstombs_s`(安全版本)或者Win32API的`WideCharToMultiByte`。下面提供几种方法:方法1:使用C标准库函数`wcstombs_s`(适用于ANSI,但要注意本地编码设置)方法2:使用Win32API函数`WideCharToMultiByte`(更灵活,可指定编码,如CP_ACP(ANSI)或CP_UTF8(UTF-8))方法3:使用C++标准库(但考虑到弃用,这里不推荐,不过如果需要跨平台且不使用弃用特性,可以使用第三方库如Boost或Poco)由于用户没有指定目标多字节编码,我们将提供两种常见情况:转换为ANSI(本地代码页)和转换为UTF-8。注意:转换过程中可能会丢失信息(如果目标编码不能表示所有字符),因此需要检查错误。示例1:使用`WideCharToMultiByte`转换为ANSI(本地代码页)示例2:使用`WideCharToMultiByte`转换为UTF-8示例3:使用C标准库`wcstombs_s`转换为本地编码(类似于ANSI)我们将编写三个示例函数。注意:在Windows环境下,我们通常使用`wchar_t`作为宽字符(UTF-16LE),而多字节字符串使用`char`。参考引用[2]和[4]中的函数形式,我们定义类似的函数。示例代码:</think>### C++ Unicode 转换为多字节示例代码 在C++中,Unicode(宽字符)与多字节字符之间的转换主要涉及以下两种场景: 1. **UTF-16 ↔ ANSI**(本地代码页) 2. **UTF-16 ↔ UTF-8** 以下是三种实现方式的代码示例: --- #### 方法1:使用 WinAPI `WideCharToMultiByte`(推荐) ```cpp #include <windows.h> #include <string> // Unicode (UTF-16) 转 ANSI (本地代码页) std::string UnicodeToANSI(const std::wstring& wstr) { int len = WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), -1, NULL, 0, NULL, NULL); char* buffer = new char[len]; WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), -1, buffer, len, NULL, NULL); std::string result(buffer); delete[] buffer; return result; } // Unicode (UTF-16) 转 UTF-8 std::string UnicodeToUTF8(const std::wstring& wstr) { int len = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, NULL, 0, NULL, NULL); char* buffer = new char[len]; WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, buffer, len, NULL, NULL); std::string result(buffer); delete[] buffer; return result; } ``` **特点**: - 直接使用Windows API,效率高 - 支持错误处理(最后一个参数可指定替换字符) - 支持多种代码页(`CP_ACP`/`CP_UTF8`) --- #### 方法2:使用 C++11 `<locale>`(跨平台) ```cpp #include <locale> #include <codecvt> #include <string> // Unicode (UTF-16) 转 UTF-8 (C++11标准) std::string UnicodeToUTF8(const std::wstring& wstr) { std::wstring_convert<std::codecvt_utf8<wchar_t>> converter; return converter.to_bytes(wstr); } // 注意:ANSI转换需指定本地化环境 std::string UnicodeToANSI(const std::wstring& wstr) { std::wstring_convert<std::codecvt_byname<wchar_t, char, std::mbstate_t>> converter(new std::codecvt_byname<wchar_t, char, std::mbstate_t>("")); return converter.to_bytes(wstr); } ``` **注意事项**: - `<codecvt>`在C++17中弃用,但在多数编译器仍可用 - ANSI转换需系统支持本地语言环境 --- #### 方法3:使用 C 标准库函数(基础实现) ```cpp #include <cstdlib> #include <string> #include <vector> std::string UnicodeToANSI(const std::wstring& wstr) { size_t len = wcstombs(nullptr, wstr.c_str(), 0) + 1; std::vector<char> buffer(len); wcstombs(buffer.data(), wstr.c_str(), len); return std::string(buffer.data()); } ``` **局限**: - 仅支持当前区域设置的ANSI编码 - 无法处理UTF-8转换 - 线程安全性较低 --- ### 关键概念说明 1. **编码类型** - **Unicode**:Windows中通常指`wchar_t`(UTF-16) - **多字节**: - ANSI:本地代码页(如GBK中文、Shift-JIS日文) - UTF-8:兼容ASCII的Unicode编码 2. **内存差异** ```mermaid graph LR Unicode字符串 -->|每个字符 2/4字节| wchar_t[] 多字节字符串 -->|变长编码| char[] ``` 3. **使用场景** - Windows API交互 → 优先用`WideCharToMultiByte` - 跨平台数据交换 → 使用UTF-8编码 - 旧版系统兼容 → ANSI编码 > **重要提示**:转换时需确保: > 1. 目标缓冲区足够大(通过预计算长度) > 2. 处理转换失败情况(如非法字符) > 3. ANSI编码有字符集限制,中文推荐UTF-8 [^1]: 字节序解析(附C++代码示例) [^2]: C++多字节与Unicode之间的转化 [^3]: C/C++语言拾遗(三)-多字节字符和Unicode [^4]: windows unicode和多字节字符集转换示例
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值