东莞工商注册网站,成都公司建网站,网站建设上机课,百度热点榜单前言
在项目开发过程中#xff0c;当需要使用多线程去处理一些业务问题的时候#xff0c;尤其涉及到多线程读写数据同时发生的操作时#xff0c;就会产生一些线程安全的问题。那如何理解线程安全问题呢#xff1f;
线程安全问题是指多线程环境中#xff0c;由于存在数据…前言
在项目开发过程中当需要使用多线程去处理一些业务问题的时候尤其涉及到多线程读写数据同时发生的操作时就会产生一些线程安全的问题。那如何理解线程安全问题呢
线程安全问题是指多线程环境中由于存在数据共享一个线程访问的共享数据已经被其他线程修改导致数据异常的情况。那如何解决线程安全的问题呢
在Java中使用锁可以实现线程同步和互斥避免线程安全问题。通过锁机制可以确保一次只有一个线程访问某个特定的资源或代码区域避免了多个线程同时读写共享数据导致的冲突和错误。同时锁还可以用于实现线程之间的协作和通信例如通过条件变量和锁实现生产者-消费者模型中的线程协作等。
值得注意的是要解决线程安全问题需要深入理解多线程环境和数据共享的问题并采取适当的同步和互斥措施来保护共享数据。同时也需要理解不同的锁机制和适用场景以便根据具体情况选择合适的锁来实现线程同步和互斥。
在Java中常见的锁的代表名词包括
互斥锁 、信号量 、读写锁 、自旋锁、条件锁、递归锁、读写信号量、顺序锁、公平锁、非公平锁、乐观锁 、悲观锁 、原子类、并发容器、ThreadLocal
等等那这么多的种类掌握起来还是比较困难的接下来将会依次针对每种类型做出详细说明和代码样例。
正文
互斥锁
互斥锁是一种基本的线程同步机制它用于确保在任何时刻只有一个线程可以访问某个共享资源或代码区域。互斥锁通过互斥量mutex来实现当一个线程需要访问共享资源时它必须先获取互斥量。如果其他线程已经持有互斥锁并正在访问共享资源则该线程将被阻塞直到持有互斥锁的线程释放互斥锁。
在Java中可以通过synchronized关键字或ReentrantLock类来实现互斥锁。
通过synchronized关键字实现互斥锁的示例代码 public class Counter { private int count 0; public synchronized void increment() { count; } public synchronized void decrement() { count--; } public synchronized int getCount() { return count; } }
在上面的代码中synchronized关键字用于修饰increment()、decrement()和getCount()方法。这使得在同一时刻只有一个线程可以执行这些方法从而保证了count变量的线程安全性ps因为使用的是互斥锁所以多个线程无法同时调用incrementdecrementgetCount方法
通过ReentrantLock类实现互斥锁的实例代码 import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Counter { private int count 0; private Lock lock new ReentrantLock(); public void increment() { lock.lock(); try { count; } finally { lock.unlock(); } } public void decrement() { lock.lock(); try { count--; } finally { lock.unlock(); } } public int getCount() { return count; } }
在上面的代码中我们使用了ReentrantLock类来实现互斥锁。通过调用lock()方法获取锁并在finally块中调用unlock()方法释放锁以确保锁总是被释放。这种方式提供了更大的灵活性并避免了在发生异常时导致锁无法释放的问题。
当多个线程同时访问increment()和decrement()方法时由于互斥锁的存在一次只有一个线程能够获取到互斥量并执行方法。其他线程将会被阻塞直到持有互斥量的线程释放互斥量。因此多个线程无法同时执行increment()和decrement()方法。 嘿嘿这是我自己的公众号会分享一些项目管理的知识相信工作几年后的朋友对于自己的职业规划也会有自己的认识如果对项目管理感兴趣的朋友可以关注一下。
信号量
信号量Semaphore是一种同步机制用于控制多个线程对共享资源的访问。信号量是一个整数值通常用于表示可用资源的数量也就说控制多少个线程可以去访问资源。信号量可以有两种操作Pproberen尝试和Vverhogen增加。
P操作线程尝试减少信号量的值。如果信号量的值为0表示当前访问资源的线程数已经达到允许的阈值则线程被阻塞直到信号量的值增加。
V操作线程增加信号量的值并唤醒所有等待该信号量的线程。
在Java中没有内置的Semaphore类但可以使用java.util.concurrent.Semaphore来实现信号量。
下面是一个使用Semaphore实现线程安全的打印机的示例代码 import java.util.concurrent.Semaphore; public class Printer { private Semaphore semaphore new Semaphore(1); // 控制允许同时访问打印机的线程数量 public void print() throws InterruptedException { try { semaphore.acquire(); // 获取信号量如果信号量不可用则阻塞当前线程 } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw e; } //此处写业务代码try { // 模拟打印操作这里只是简单地休眠1秒钟 此处用于处理实际的业务 Thread.sleep(1000); } finally { semaphore.release(); // 释放信号量允许其他线程获取信号量 } } }
在上面的代码中我们使用了Semaphore类来控制同时访问打印机的线程数量。只有一个信号量初始值为1。当线程需要打印时它首先尝试获取信号量。如果信号量的值为0则线程被阻塞直到其他线程释放信号量。当线程成功获取信号量后它可以进行打印操作。在finally块中无论打印操作是否成功完成我们都要释放信号量以便其他线程可以获取信号量并执行打印操作。通过这种方式我们实现了线程安全的打印机访问控制。
读写锁
读写锁ReadWriteLock是一种同步机制用于控制多个线程对共享资源的访问。读写锁提供了两种类型的锁读锁和写锁。多个线程可以同时持有读锁但只能有一个线程持有写锁。写锁是互斥的即在一个线程持有写锁时其他线程无法获取读锁或写锁。
读写锁的主要优势在于当多个线程只是读取共享资源时它们可以并发地访问资源从而提高程序的性能。但如果有线程需要写入共享资源则其他线程无法访问资源保证了数据的一致性。
在Java中读写锁可以通过java.util.concurrent.locks包中的ReentrantReadWriteLock类来实现。
下面是一个使用Java中的ReentrantReadWriteLock实现读写锁的示例代码 import java.util.concurrent.locks.ReentrantReadWriteLock; public class SharedResource { private String data; private ReentrantReadWriteLock lock new ReentrantReadWriteLock(); public String readData() { lock.readLock().lock(); // 获取读锁 try { return data; } finally { lock.readLock().unlock(); // 释放读锁 } } public void writeData(String newData) { lock.writeLock().lock(); // 获取写锁 try { data newData; } finally { lock.writeLock().unlock(); // 释放写锁 } } }
在上面的代码中我们使用ReentrantReadWriteLock类来实现读写锁。readData()方法使用读锁来读取数据而writeData()方法使用写锁来写入数据。在获取锁时我们使用lock()方法而在释放锁时我们使用unlock()方法。通过这种方式我们可以确保在读取或写入数据时其他线程无法同时访问数据从而保证了数据的一致性。
自旋锁
当一个线程需要访问共享资源时它会尝试获取自旋锁。如果锁已经被其他线程持有则该线程会一直循环检查自旋直到锁被释放。
下面通过原生的代码实现一个自旋锁 public class SpinLock { private volatile boolean locked false; public void lock() { while (true) { if (locked) { continue; } else { locked true; break; } } } public void unlock() { locked false; } }
这是用java原生代码实现的一个自旋锁在这个示例中我们定义了一个SpinLock类它包含了一个locked变量来记录锁的状态。lock方法使用一个无限循环来检查锁的状态如果锁已经被其他线程持有则继续循环否则将锁设置为已持有状态并退出循环。unlock方法将锁的状态设置为未持有状态。
那下面通过代码使用一下这个自旋锁 public class SharedResource { private final SpinLock lock new SpinLock(); private int count 0; public void increment() { lock.lock(); // 获取自旋锁 try { count; System.out.println(Count after increment: count); } finally { lock.unlock(); // 释放自旋锁 } } }
在上面的示例中我们定义了一个SharedResource类它包含了一个自旋锁lock和一个共享资源count。在increment方法中我们首先调用lock.lock()方法获取自旋锁然后在代码块中增加count的值并输出结果。无论是否发生异常我们都在finally块中调用lock.unlock()方法来释放自旋锁。
要使用这个示例可以创建多个线程并在每个线程中调用increment方法来访问共享资源。由于使用了自旋锁来保护共享资源因此只有一个线程能够访问共享资源其他线程会等待直到锁被释放。
使用自旋锁时需要注意以下几点 自旋锁可能会导致CPU资源的浪费因为线程在等待获取锁时会一直循环检查。 自旋锁适用于短时间的等待如果等待时间较长则应该考虑使用其他同步机制例如阻塞队列或条件变量。 自旋锁适用于共享资源的访问非常频繁的情况如果共享资源的访问较少则使用自旋锁可能会影响性能。
其中ReentrantLock是本身就是一种自旋锁。ReentrantLock通过循环调用CAS操作来实现加锁性能比较好也是因为避免了使线程进入内核态的阻塞状态。
条件锁
条件锁Condition Lock是一种同步机制用于在多线程环境中实现线程之间的协调。它允许一个线程等待某个条件满足而其他线程可以修改这个条件以便满足等待中的线程。
条件锁通常与互斥锁Mutex一起使用以保护共享数据的访问。当一个线程需要访问共享数据时它首先获取互斥锁然后检查条件是否满足。如果条件不满足线程将等待条件锁。当其他线程修改了共享数据并改变了条件时它将释放条件锁从而使等待中的线程可以继续执行。
以下用代码演示如何使用条件锁实现线程之间的协调 class SharedResource { private final Lock lock new ReentrantLock(); private final Condition condition lock.newCondition(); private int count 0; public void increment() throws InterruptedException { lock.lock(); try { condition.await(lock, () - count 10); // 等待条件满足 count; System.out.println(Count: count); condition.signalOne(); // 通知其他线程条件已满足 } finally { lock.unlock(); } } public void decrement() throws InterruptedException { lock.lock(); try { condition.await(lock, () - count 0); // 等待条件满足 --count; System.out.println(Count: count); condition.signalOne(); // 通知其他线程条件已满足 } finally { lock.unlock(); } } }public class Test{public static void main(String[] args) { SharedResource example new SharedResource(); Thread t1 new Thread(() - { try { example.increment(); } catch (InterruptedException e) { e.printStackTrace(); } }); Thread t2 new Thread(() - { try { example.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } }); t1.start(); t2.start(); try { t1.join(); t2.join(); } catch (InterruptedException e) { e.printStackTrace(); } } }
在上述示例中SharedResource 类包含一个共享资源 count以及两个方法 increment() 和 decrement()分别用于增加和减少 count 的值。使用条件锁来确保在多线程环境下对 count 的操作是正确的并确保当 count 的值满足特定条件时等待的线程可以继续执行。 嘿嘿这是我自己的公众号会分享一些项目管理的知识相信工作几年后的朋友对于自己的职业规划也会有自己的认识如果对项目管理感兴趣的朋友可以关注一下。
递归锁可重入锁
递归锁也叫可重入锁是一种特殊类型的锁它允许同一线程多次获取同一个锁。这对于需要反复访问同一资源的情况非常有用例如递归算法或嵌套事务。
递归锁的实现通常基于一个计数器当线程尝试获取锁时计数器递增。当线程释放锁时计数器递减。只有当计数器为零时其他线程才能获取该锁。
在 Java 中可以使用 ReentrantLock 类实现递归锁可重入锁 import java.util.concurrent.locks.ReentrantLock;public class RecursiveTask implements Runnable {private final ReentrantLock lock new ReentrantLock();public void performTask(int count) {lock.lock();try {System.out.println(Thread Thread.currentThread().getId() : Count count);// 当计数小于5时递归调用if (count 5) {performTask(count 1);}} finally {// 确保锁在操作完成后释放lock.unlock();}}Overridepublic void run() {performTask(1);}public static void main(String[] args) {RecursiveTask task new RecursiveTask();Thread thread1 new Thread(task);Thread thread2 new Thread(task);thread1.start();thread2.start();}}
在这个例子中RecursiveTask 类实现了 Runnable 接口每个线程运行时都会从计数1开始执行 performTask 方法。每个线程都有自己的计数器因此即使一个线程完成了递归任务其他线程仍然可以独立地开始和完成自己的递归任务。但是多个线程走到performTask方法时都会尝试获取锁如果锁被其他线程占用则会继续等待锁释放。
读写信号量
读写信号量ReadWrite Semaphore是信号量的一种变种它允许多个线程同时读取共享资源但在写入时则需要独占式的访问。
下面通过一个简单的代码示例 import java.util.concurrent.Semaphore; public class ReadWriteSemaphoreDemo { public static void main(String[] args) throws InterruptedException { Semaphore readSemaphore new Semaphore(2); // 允许2个读线程同时访问 Semaphore writeSemaphore new Semaphore(1); // 允许1个写线程访问 // 模拟读线程 Thread readerThread1 new Thread(() - { try { readSemaphore.acquire(); // 获取读锁 System.out.println(Reader 1 is reading...); Thread.sleep(1000); // 模拟读取操作耗时 readSemaphore.release(); // 释放读锁 } catch (InterruptedException e) { e.printStackTrace(); } }); Thread readerThread2 new Thread(() - { try { readSemaphore.acquire(); // 获取读锁 System.out.println(Reader 2 is reading...); Thread.sleep(1000); // 模拟读取操作耗时 readSemaphore.release(); // 释放读锁 } catch (InterruptedException e) { e.printStackTrace(); } }); // 模拟写线程 Thread writerThread new Thread(() - { try { writeSemaphore.acquire(); // 获取写锁 System.out.println(Writer is writing...); Thread.sleep(1000); // 模拟写入操作耗时 writeSemaphore.release(); // 释放写锁 } catch (InterruptedException e) { e.printStackTrace(); } }); readerThread1.start(); readerThread2.start(); writerThread.start(); } }
这个示例中我们创建了一个readSemaphore和一个writeSemaphore分别用于控制读和写的访问。两个读线程可以同时获取读锁并执行读取操作但写线程在执行写入操作时需要独占写锁。
顺序锁
顺序锁是一种同步机制用于控制多个线程对共享资源的访问顺序。它通过维护一个线程的访问顺序列表来实现以确保线程按照一定的顺序访问共享资源避免出现竞态条件。
顺序锁的主要思想是当一个线程访问共享资源时需要先获取顺序锁并将自己添加到访问顺序列表的头部。其他线程在访问共享资源时需要等待顺序锁被释放并按照列表中的顺序进行访问。 import java.util.LinkedList; import java.util.Queue; public class SequentialLock { private QueueThread queue new LinkedList(); public synchronized void lock() throws InterruptedException { Thread currentThread Thread.currentThread(); while (true) { queue.add(currentThread); if (queue.peek() currentThread) { break; } else { wait(); } } } public synchronized void unlock() { Thread removedThread queue.poll(); if (removedThread ! null removedThread.equals(Thread.currentThread())) { notifyAll(); } } }
在这个示例中我们使用了一个队列来维护访问顺序列表。当一个线程需要访问共享资源时它首先调用lock()方法获取顺序锁。在lock()方法中线程将自己添加到队列中并检查队列头部是否是当前线程。如果不是当前线程则当前线程会等待直到它成为队列头部。当线程成为队列头部后它就可以访问共享资源了。
当线程访问完共享资源后它会调用unlock()方法释放顺序锁。在unlock()方法中我们将队列头部的线程移除并通知所有等待的线程。这样其他线程就可以按照队列中的顺序访问共享资源了。 public class SharedResource { private int resourceValue; private SequentialLock lock new SequentialLock(); public void setResourceValue(int value) { lock.lock(); // 获取锁 try { resourceValue value; } finally { lock.unlock(); // 释放锁 } } public int getResourceValue() { lock.lock(); // 获取锁 try { // 在这里执行对共享资源的访问操作 return resourceValue; } finally { lock.unlock(); // 释放锁 } } }
在这个例子中我们创建了一个SharedResource类它包含了一个SequentialLock对象来控制对共享资源的访问。在访问共享资源时线程需要先调用lock()方法获取锁并在访问完成后调用unlock()方法释放锁。这样可以确保同一时刻只有一个线程能够访问共享资源避免了多个线程同时访问共享资源的情况。
公平锁
公平锁是指多个线程按照申请锁的顺序来获取锁。公平锁通过维护一个等待队列来保证线程按照请求锁的顺序获取锁在并发环境中每个线程在获取锁时会先查看此锁维护的等待队列如果为空或者当前线程是等待队列的第一个就占有锁否则就会加入到等待队列中以后会按照FIFO的规则从队列中取出并占有锁。
在Java中java.util.concurrent.locks.ReentrantLock类提供了公平锁的实现。下面是一个使用公平锁的示例代码 import java.util.concurrent.locks.ReentrantLock; public class FairLockExample { private final ReentrantLock fairLock new ReentrantLock(true); // 创建公平锁 private final Object sharedResource new Object(); public void doSomething() { fairLock.lock(); // 获取锁 try { // 访问共享资源 // ... } finally { fairLock.unlock(); // 释放锁 } } }
在上面的代码中我们创建了一个公平锁fairLock并将其传递给ReentrantLock构造函数以指定为公平锁。然后在doSomething()方法中我们使用fairLock.lock()来获取锁并在访问共享资源后使用fairLock.unlock()释放锁。这样可以确保线程按照请求锁的顺序获取锁并避免了饥饿问题。
什么是线程饥饿问题在下面会用简洁明了的方式给大家介绍。
线程饥饿问题
针对线程饥饿问题是比较难理解的下面通过简单的比喻帮助大家理解线程饥饿问题 想象一下有一家餐厅里面有10个服务员。每当有顾客进来服务员就会去为他们服务。如果每个服务员都均匀地服务顾客那么餐厅的运行就会很顺畅。但是如果有些服务员总是忙不过来而其他服务员却闲着没事干那么那些闲着的服务员就会一直等因为他们没有顾客可以服务。这就好比线程饥饿问题。有些线程可能总是得不到它们需要的资源就像那些闲着的服务员一样。这种情况可能会导致那些得不到资源的线程长时间等待甚至无法执行。这就好比那些闲着的服务员一直等不到顾客他们就会一直闲着无法为餐厅创造价值。为了解决这个问题我们可以采取一些措施比如让服务员轮流休息或者增加服务员的数量这样就可以保证每个服务员都有顾客可以服务不会出现线程饥饿问题。 嘿嘿这是我自己的公众号会分享一些项目管理的知识相信工作几年后的朋友对于自己的职业规划也会有自己的认识如果对项目管理感兴趣的朋友可以关注一下。
非公平锁
非公平锁是一种线程同步机制与公平锁相对。在非公平锁中线程获取锁的顺序不一定按照申请锁的顺序而是按照线程的优先级或其他因素来决定。
在Java中java.util.concurrent.locks.ReentrantLock类提供了非公平锁的实现。下面是一个使用非公平锁的示例代码 import java.util.concurrent.locks.ReentrantLock; public class NonFairLockExample { private final ReentrantLock nonFairLock new ReentrantLock(false); // 创建非公平锁 private final Object sharedResource new Object(); public void doSomething() { nonFairLock.lock(); // 获取锁 try { // 访问共享资源 // sharedResource... } finally { nonFairLock.unlock(); // 释放锁 } } }
需要注意的是由于非公平锁的特性它可能会导致饥饿问题因为高优先级的线程可能会一直获取到锁而低优先级的线程则可能长时间等待。因此在使用非公平锁时需要谨慎考虑线程的优先级设置和资源竞争情况。
乐观锁
乐观锁会首先认为不会发生线程安全的问题然后在做数据更新的时候去和数据库中数据的版本号做比对来判断数据是否已经被修改。乐观锁的实现通常需要数据库的支持例如使用版本号version字段来表示数据的版本。当事务开始时将当前版本号加1并将新的版本号写入数据库。在更新数据时先读取当前版本号然后比较版本号和预期版本号如果相等则更新数据并将版本号加1如果不相等则说明数据已经被其他事务修改过此时可以抛出异常或进行其他处理。 public class OptimisticLocker { private static final Object lock new Object(); private int version 0; // 版本号 public class Entity { private int id; private int value; private int version; // 省略构造方法、getter和setter方法 } public Entity read(int id) { // 模拟从数据库中读取数据 Entity entity new Entity(); entity.setId(id); entity.setValue(value); // 假设从数据库中读取的值是10 entity.setVersion(version); // 假设从数据库中读取的版本号是0 return entity; } public boolean update(Entity entity) { synchronized (lock) { int currentVersion entity.getVersion(); // 获取当前版本号 try { // 模拟长时间运行的操作或其他线程修改数据的情况可能这期间其他的事务已经将数据修改了。 Thread.sleep(1000); Entity data read(entity.getId);//取出数据库中的数据做版本号的比对if (currentVersion ! data.getVersion()) { System.out.println(数据已被其他线程修改当前线程的更新失败); return false; // 更新失败 } else { entity.setValue(entity.getValue() 1); // 更新数据值 entity.setVersion(currentVersion 1); // 更新版本号 System.out.println(当前线程的更新成功); return true; // 更新成功 } } catch (InterruptedException e) { e.printStackTrace(); } } return false; // 异常情况更新失败 } }
在这个示例中update() 方法尝试对实体进行更新。它首先获取当前版本号并检查版本号是否与数据库中的版本号一致。如果一致则进行更新操作并返回 true 表示更新成功。如果版本号不一致则说明数据已被其他线程修改此时返回 false 表示更新失败。这样可以确保在多个线程并发访问时不会出现数据冲突的情况。
悲观锁
悲观锁其实是和乐观锁是相反的悲观锁思想认为一定会发生线程安全的问题所以不管是会不会存在线程安全的问题都给加上锁。
原子类
在Java中原子类主要用于实现线程安全的操作尤其是在对共享数据进行并发访问时。这些类提供了原子操作这些操作在多线程环境中是安全的。
Java提供了几个原子类如AtomicInteger、AtomicLong、AtomicBoolean等它们都是java.util.concurrent.atomic包的一部分。
下面是一个使用AtomicInteger的简单示例演示了如何使用原子类实现线程安全的计数器 import java.util.concurrent.atomic.AtomicInteger; public class AtomicCounter { private AtomicInteger counter new AtomicInteger(0); public void increment() { counter.incrementAndGet(); } public int getCount() { return counter.get(); } public static void main(String[] args) { AtomicCounter counter new AtomicCounter(); // 创建10个线程来增加计数器值 for (int i 0; i 10; i) { new Thread(() - { counter.increment(); }).start(); } // 等待所有线程执行完毕 try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } // 输出最终计数器值 System.out.println(Final counter value: counter.getCount()); } }
但此时如果将每个线程中操作数据的counter.increment(); 增加一个for循环。 import java.util.concurrent.atomic.AtomicInteger; public class Counter { private AtomicInteger count new AtomicInteger(0); public void increment() { count.incrementAndGet(); // 使用CAS操作原子性增加计数器 } public int getCount() { return count.get(); // 返回当前计数值 } public static void main(String[] args) { Counter counter new Counter(); // 启动多个线程增加计数器 for (int i 0; i 10; i) { new Thread(() - { for (int j 0; j 1000; j) { counter.increment(); } }).start(); } // 等待所有线程执行完毕 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // 输出最终计数值 System.out.println(Final count: counter.getCount()); } }
在多线程环境下由于线程的调度是异步的因此无法保证各个线程的执行顺序。在这种情况下如果有多个线程同时尝试增加计数器的值那么AtomicInteger会使用CASCompare-and-Swap操作来确保这些操作的原子性。CAS操作会检查计数器的当前值并与预期值进行比较如果相等则将计数器的值增加1并返回更新后的值否则重新尝试。由于这个操作的原子性即使多个线程同时尝试增加计数器的值最终的计数值仍然是正确的。但是由于无法保证线程的执行顺序因此可能会出现一些线程先于其他线程完成的情况导致最终的计数值大于预期的值。
所以使用AtomicInteger可以保证原子性更新但是它并不能直接保证线程安全。
ThreadLocal
ThreadLocal的主要目的是为了解决多线程中的数据同步问题。在多线程环境下当多个线程同时访问共享变量时就可能会出现数据不一致的情况。而ThreadLocal通过为每个线程提供独立的变量副本避免了这种情况的发生。每个线程都持有自己的数据副本互不干扰。 public class ThreadLocalExample { // 创建一个ThreadLocal对象来存储每个线程的执行时间 private static final ThreadLocalLong startTime new ThreadLocal(); public static void main(String[] args) { // 启动多个线程每个线程执行相同的任务 for (int i 0; i 10; i) { new Thread(() - { // 在每个线程开始时记录其开始时间 startTime.set(System.currentTimeMillis()); try { // 模拟一些工作 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // 在每个线程结束时输出其执行时间 Long startTime startTime.get(); long endTime System.currentTimeMillis(); long executionTime endTime - startTime; System.out.println(Thread Thread.currentThread().getId() executed in executionTime ms); }).start(); } } }
在这个示例中我们使用ThreadLocal来存储每个线程的开始时间。在每个线程启动时我们使用ThreadLocal的set方法将当前时间设置为该线程的开始时间。然后当线程结束时我们使用get方法获取该线程的开始时间并计算其执行时间。最后我们输出每个线程的执行时间。由于每个线程都有自己的开始时间副本因此计算出的执行时间是准确的并且不会与其他线程的执行时间混淆。 嘿嘿这是我自己的公众号会分享一些项目管理的知识相信工作几年后的朋友对于自己的职业规划也会有自己的认识如果对项目管理感兴趣的朋友可以关注一下。
结语
在本文中我们深入探讨了Java中的锁机制从互斥锁到读写锁再到乐观锁和悲观锁等概念。了解了各种锁的特点和代码样例以及它们在并发编程中的重要性和作用。通过实例代码我们分析了各种锁的实现和应用方式。
然而尽管锁为我们提供了同步和互斥的关键机制但在使用时仍需谨慎。过度使用或不恰当使用锁可能导致死锁、性能下降等问题。因此在实际应用中我们需要根据具体情况选择合适的锁类型和策略并时刻关注锁的使用情况确保系统的健壮性和性能。
此外随着技术的发展Java中的锁机制也在不断演进和优化。例如偏向锁、元锁等新型锁的出现为开发者提供了更多的选择和灵活性。
最后希望本文能为你提供关于Java锁的全面知识和实践经验帮助你在多线程编程中更加从容地应对挑战。