synchronized和lock比对

本文深入探讨Java中的Lock接口,包括其获取锁的多种方式、读锁和写锁的区别及应用场景,对比synchronized关键字,强调Lock的灵活性和优势。

前言:在上面的博客说了synchronized的一些用法,下面我们再来看看lock,这个出现频率也是非常高的一个。

1:获取Lock锁的几种方式

前面说了synchronized有锁对象和锁类对象,当某个线程获取锁其他线程必须等待执行完毕才可继续进行,比如线程A先获取锁,但是出现异常导致的后果就是线程B无法获取锁,会出现死锁的情况(http://www.cnblogs.com/LipeiNet/p/6475851.html),那么我们一起看看Lock是如何解决的。lock有4种方式来获取锁

1:lock.lock() 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁。此种模式和synchronized一样但是不会出现死锁

public class Lock1 {
    static int value = 0;
    static Lock lock = new ReentrantLock();

    static class Task1 implements Runnable {
        public void run() {
            System.out.println("线程" + Thread.currentThread().getName() + "开始执行");
            lock.lock();
            try {
                for (int i = 0; i < 1000000; i++) {
                    value++;
                }
                System.out.println(value);
            } finally {
                lock.unlock();
            }
        }
    }
    static class Task2 implements Runnable {
        public void run() {
            System.out.println("线程" + Thread.currentThread().getName() + "开始执行");
            lock.lock();
            try {
                for (int i = 0; i < 1000000; i++) {
                    value++;
                }
                System.out.println(value);
            } finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        ExecutorService service= Executors.newCachedThreadPool();
        service.execute(new Task1());
        service.execute(new Task2());
        service.shutdown();
    }
}

输出结果很明显其中一个value是1000000,一个是2000000,效果和synchronized是一样的。但是如果我们去掉lock以后的结果呢,很明显会错,如下图这样

为啥会出现这样情况呢,是由于cpu速度极快,每次处理完毕之后并没有立即把数值放入Java内存中,而是放在写缓存区,然后由写缓存区同步到Java内存中,这样一样,如果线程1计算结果是2,但是还是到内存中,导致线程2以为value值还是1所以会重复计算,还有从结果我们也可以看出value值并不是100000说明2个线程是同步执行的。

 2:lock.tryLock();

这个方法和synchronized有所不同,synchronized和lock都会等待直到获取锁。如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;当然我们可以利用while循环一直等待,直到获取锁然后进行。代码如下

public class Lock2 {
    public static void main(String[] args) {
        final Lock lock = new ReentrantLock();
        Thread t1 = new Thread(new Runnable() {
            public void run() {
                String tName = Thread.currentThread().getName();
                while (!lock.tryLock()) {
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("等待获取锁");
                }
                try {
                    for (int i = 0; i < 5; i++) {
                        System.out.println(tName + ":" + i);
                    }
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        });

        Thread t2 = new Thread(new Runnable() {
            public void run() {
                String tName = Thread.currentThread().getName();
                while (!lock.tryLock()) {
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("等待获取锁");
                }
                try {
                    for (int i = 0; i < 5; i++) {
                        System.out.println(tName + ":" + i);
                    }

                } catch (Exception e) {
                    System.out.println(tName + "出错了!!!");
                } finally {
                    System.out.println(tName + "释放锁!!");
                    lock.unlock();
                }
            }
        });
        t1.start();
        t2.start();
    }
}

3:lock.trylock(long time, TimeUnit unit)如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;unit是time的时间单位比如TimeUnit.SECONDS就是表示秒

4:lock.lockInterruptibly()如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断。也就是说如何线程没有被中断和lock.lock()的作用一样。但是如何线程被中断了,那么此时这个线程不会有任何的响应,想象这么一个场景,线程A和线程B同时执行任务,但是必须等待线程B先执行,但是执行过程中突然线程A突然被中断,那么这个时候就可能出现死锁,哪怕是在finally中加入unlock,这个时候我们就要采用lockInterruptibly()了。代码如下

public class Lock3 {
    public static void main(String[] args) {
        final Lock lock = new ReentrantLock();
        final Thread thread1 = new Thread(new Runnable() {
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println("等待被中断");
                    lock.lockInterruptibly();
                } catch (InterruptedException e) {
                    System.out.println("我被中断了");
                } finally {
                    lock.unlock();
                }
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            public void run() {
                lock.lock();
                try {
                    TimeUnit.SECONDS.sleep(4);
                } catch (InterruptedException e) {

                }
                thread1.interrupt();
                System.out.println("线程1已经被中断");
            }
        });
        thread1.start();
        thread2.start();
    }
}

2:读锁和写锁

 在开发中我们最好的愿望就是写的时候加锁,但是读的时候不加锁这样会大大的提升效率,但是采用synchronized却无法满足我们的要求,如果在读的方法面前加锁那么所有的读都需要等待,如果不加锁的话那么如果现在A,B2个线程读取,C线程写入可能导致的后果就是A,B2个线程取得数据不一致,明明同一种业务场景但是获取值却不同。好了lock的读锁和写锁帮助我们实现这种功能。

public class ReadWriteLockTest {
    private int value = 0;
    ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    public void add(int value) {
        Lock writeLock = readWriteLock.writeLock();
        writeLock.lock();
        try {
            TimeUnit.SECONDS.sleep(3);
            System.out.println("添加开始时间:" + new Date());
            this.value += value;
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            writeLock.unlock();
        }
    }

    public void getValue() {
        Lock readLock = readWriteLock.readLock();
        readLock.lock();
        try {
            TimeUnit.SECONDS.sleep(1);
            System.out.println("获取开始时间:" + new Date());
            System.out.println(value);
        } catch (InterruptedException e) {

        } finally {
            readLock.unlock();
        }
    }

    public static void main(String[] args) {
        final ReadWriteLockTest readWriteLockTest = new ReadWriteLockTest();
        Runnable task1 = new Runnable() {
            public void run() {
                readWriteLockTest.add(100);
            }
        };
        Runnable task2 = new Runnable() {
            public void run() {
                readWriteLockTest.getValue();
            }
        };
        ExecutorService service = Executors.newCachedThreadPool();
        for (int i=0;i<2;i++){
            service.execute(task1);
        }
        for (int i=0;i<2;i++){
            service.execute(task2);
        }
        for (int i=0;i<2;i++){
            service.execute(task1);
        }
        service.shutdown();
    }
}

运行结果:

从这个结果我们很明显的可以总结读锁和写锁

第一:如果执行写的时候,读和写必须等待

第二:如果执行读的时候,写必须等待,而读却不用等待

也就是说读和写必须存在先后顺序,不管是先读还是先写。

3:总结

相同点:lock能实现synchronized所有可以实现的

不同点:

1:lock不容易出现死锁,而synchronized如果某个线程出现异常就会产生死锁

2:lock更加灵活,可以通过tryLock来验证是否获取锁,在线程中断也同样可以处理

3:lock有读写锁在并发量大的时候具有很大的优势,因为读的情况一般会比写多很多

 

<think>我们被要求比较PythonJava的多线程机制及其在自动化测试中的应用。首先,我们需要理解两种语言在多线程实现上的根本区别,然后讨论这些区别如何影响它们在自动化测试中的使用。 核心区别: 1. **全局解释器锁(GIL)**:这是Python(特指CPython)中一个重要的概念。GIL确保同一时刻只有一个线程在执行Python字节码。这意味着在多核CPU上,Python的多线程并不能实现真正的并行计算(对于CPU密集型任务)。而Java没有这样的锁,它的线程可以被操作系统调度到多个CPU核心上真正并行执行。 2. **线程模型**: - Python:提供了`threading`模块,但由于GIL的存在,多线程更适合I/O密集型任务(如文件操作、网络请求等)。在自动化测试中,如果测试用例主要是I/O操作(如网络请求、数据库操作、等待页面加载等),Python的多线程仍然可以提升效率。 - Java:使用`java.lang.Thread`类`java.util.concurrent`包,线程是操作系统级别的线程(原生线程),可以充分利用多核处理器。 3. **线程池**: - Python:可以使用`concurrent.futures`模块中的`ThreadPoolExecutor`来管理线程池,或者使用第三方库(如`multiprocessing.dummy`提供的线程池)。 - Java:`java.util.concurrent.Executors`提供了丰富的线程池创建方法,如`newFixedThreadPool`、`newCachedThreadPool`等,并且功能更强大(如支持自定义线程工厂、拒绝策略等)。 4. **线程同步机制**:两者都提供了锁(Lock)、信号量(Semaphore)、条件变量(Condition)等同步原语,用法类似。 5. **异常处理**: - Python:线程中的异常需要在线程内部处理,否则会导致线程退出,但不会影响主线程。可以通过Future对象捕获异常。 - Java:线程中的异常可以通过设置未捕获异常处理器(UncaughtExceptionHandler)来处理。 6. **协程支持**:Python从3.5开始引入了`asyncio`库`async/await`语法,可以更高效地处理I/O密集型任务,这在一定程度上可以替代多线程。Java也有类似项目(如Loom),但尚未成为标准。 在自动化测试中的应用: - **Python多线程适用场景**: - 当测试用例主要是I/O密集型操作(如HTTP请求、数据库查询、Selenium等待页面元素加载等)时,使用多线程可以显著减少总执行时间,因为线程在等待I/O时会让出GIL,其他线程可以继续执行。 - 不适合CPU密集型的测试(如大量计算、图像处理等),因为GIL会限制并行效率。 - **Java多线程适用场景**: - 无论是I/O密集型还是CPU密集型的测试任务,Java多线程都能充分利用多核CPU。因此,对于复杂的测试场景(包含大量计算I/O),Java可能更有优势。 - 在大型企业级测试框架中,Java的多线程稳定性成熟度更高,适合构建高并发的测试系统。 总结: - 对于轻量级、快速开发的自动化测试(如Web UI测试、API测试),Python的多线程由于其简单性在I/O操作上的效率,是一个不错的选择。同时,Python的`asyncio`也可以作为另一种高效的并发选择。 - 对于需要高并发、高性能的测试场景(如压力测试、性能测试),或者测试用例中包含大量CPU计算,Java的多线程更为合适。 根据引用[2][3]的内容,PythonJava在自动化测试领域各有优势,选择哪种语言取决于项目需求个人偏好。如果项目需要快速开发易用性,Python更合适;如果项目是大型企业级项目,注重稳定性性能,则Java更合适。 示例代码: Python多线程示例(使用线程池执行测试用例): ```python import concurrent.futures import time def run_test(test_id): print(f"Start test {test_id}") time.sleep(2) # 模拟测试执行时间 print(f"End test {test_id}") return f"Test {test_id} passed" # 使用线程池执行5个测试用例 with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor: test_ids = [1, 2, 3, 4, 5] results = executor.map(run_test, test_ids) for result in results: print(result) ``` Java多线程示例(使用线程池执行测试用例): ```java import java.util.concurrent.*; public class TestRunner { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(3); for (int i = 1; i <= 5; i++) { final int testId = i; executor.submit(() -> { System.out.println("Start test " + testId); try { Thread.sleep(2000); // 模拟测试执行时间 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("End test " + testId); return "Test " + testId + " passed"; }); } executor.shutdown(); try { executor.awaitTermination(1, TimeUnit.HOURS); } catch (InterruptedException e) { e.printStackTrace(); } } } ``` 注意事项: - 在Selenium Grid中,虽然可以配置多个节点并行执行测试,但每个节点上的浏览器实例通常由单独的线程或进程处理。在Python中,由于GIL,每个节点最好使用进程(`multiprocessing`)来绕过GIL限制;而Java则可以直接使用线程。 相关问题: 1. 在Python中如何绕过GIL实现真正的并行计算? 2. 在自动化测试中,如何避免多线程导致的测试用例相互干扰? 3. JavaPython在异步编程(如协程)方面有何异同? 4. 在Selenium自动化测试中,如何设计多线程执行框架? 5. 如何选择线程池的大小以优化自动化测试的执行速度?</think>### Python与Java多线程机制对比及其在自动化测试应用 #### 一、核心机制差异 | 特性 | Python (CPython) | Java | |--------------|-----------------------------|-------------------------------| | **并发模型** | 伪并行 (GIL限制) | 真并行 (OS级线程) | | **线程类型** | 用户级线程 | 内核级线程 | | **CPU密集型** | 效率低下 (单核运行) | 高效利用多核 | | **I/O密集型** | 高效 (GIL在I/O时释放) | 高效 | | **内存模型** | 无原生内存屏障 | JMM内存模型 (volatile/synchronized) | | **线程开销** | 轻量 ($\approx$ 8KB/线程) | 较重 ($\approx$ 1MB/线程) | **关键差异公式**: $$ \text{Java并行度} = N_{\text{cores}} \quad \text{Python并行度} \leq 1 $$ #### 二、自动化测试应用场景对比 1. **I/O密集型测试** (API/UI测试) - **Python优势**: ```python # 使用ThreadPoolExecutor处理HTTP请求 with ThreadPoolExecutor(max_workers=10) as executor: futures = [executor.submit(api_test, url) for url in urls] results = [f.result() for f in futures] ``` - GIL在等待响应时自动释放,效率接近Java - 资源消耗低,适合容器化环境[^3] - **Java实现**: ```java ExecutorService executor = Executors.newFixedThreadPool(10); List<Future<Result>> futures = new ArrayList<>(); for (String url : urls) { futures.add(executor.submit(() -> apiTest(url))); } ``` 2. **CPU密集型测试** (数据验证/图像比对) - **Java优势**: - 原生支持多核并行计算 - 使用`ForkJoinPool`实现工作窃取: ```java public class ImageMatcher extends RecursiveTask<Boolean> { // 实现多核图像比对 } ``` - **Python替代方案**: - 使用`multiprocessing`绕过GIL: ```python with ProcessPoolExecutor() as executor: executor.map(cpu_intensive_task, data_chunks) ``` 3. **浏览器自动化** (Selenium) | 场景 | Python方案 | Java方案 | |------------------|----------------------------|-----------------------------| | 单浏览器多页面 | `threading` + 上下文隔离 | `Thread` + Driver实例隔离 | | 多浏览器并行 | `multiprocessing` | `ExecutorService` | | 跨OS测试 | 需进程级并行[^4] | 原生线程支持 | #### 三、选择建议 1. **优先选Python**: - 快速原型开发 - I/O主导的测试场景 - 资源受限环境 - 结合AI/ML的测试[^3] 2. **优先选Java**: - 企业级测试平台 - 性能/压力测试 - 复杂测试逻辑 - 需要JVM生态工具链 > **最佳实践**:Python适合$80\%$的自动化测试场景,剩余$20\%$高性能需求用Java补充[^2]。混合架构中可用Python驱动测试流程,Java处理计算密集型模块。 #### 四、趋势发展 1. **Python异步方案**: ```python async def run_tests(): await asyncio.gather(test_case1(), test_case2()) ``` - 协程替代线程减少上下文切换 - 适合高并发API测试 2. **Java虚拟线程** (Loom项目): ```java Thread.startVirtualThread(() -> seleniumTest()); ``` - 轻量级线程(≈2KB) - 突破传统线程数量限制 --- ### 相关问题 1. 如何绕过Python的GIL限制实现真并行计算? 2. 在Selenium Grid中如何优化多浏览器测试的资源分配? 3. Java虚拟线程将如何改变自动化测试架构? 4. Python异步框架(如FastAPI)对API测试有何影响? 5. 如何设计混合Python/Java的自动化测试框架? > 引用文献: > Python多线程在I/O密集型测试中的高效性[^1] > 企业级测试中Java的性能优势[^2] > 容器化测试环境中Python的资源优势[^3] > Selenium多浏览器测试的并行限制[^4]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值