Java中的线程同步与同步器

举报
赵KK日常技术记录 发表于 2023/08/14 17:43:00 2023/08/14
【摘要】 java、python面试题来自UC网盘app分享,打开手机app,额外获得1T空间https://drive.uc.cn/s/2aeb6c2dcedd4AIGC资料包https://drive.uc.cn/s/6077fc42116d4https://pan.xunlei.com/s/VN\_qC7kwpKFgKLto4KgP4Do\_A1?pwd=7kbv#https://yv4kfv1...

java、python面试题来自UC网盘app分享,打开手机app,额外获得1T空间

https://drive.uc.cn/s/2aeb6c2dcedd4

AIGC资料包

https://drive.uc.cn/s/6077fc42116d4

https://pan.xunlei.com/s/VN\_qC7kwpKFgKLto4KgP4Do\_A1?pwd=7kbv#

https://yv4kfv1n3j.feishu.cn/docx/MRyxdaqz8ow5RjxyL1ucrvOYnnH

字数统计:3147字

阅读时间:约12分钟

引言

在Java开发中,线程同步是一个常见且重要的问题。在多线程环境下,线程之间的协调与同步是确保程序正确执行的关键。Java提供了多种同步机制和同步器,本文将介绍如何让Java的线程彼此同步,并详细介绍了几种常用的同步器。

一、线程同步的概念

线程同步是指多个线程按照一定的规则来共享和访问共享资源,以保证线程安全性和数据一致性。在多线程环境下,如果多个线程同时访问共享资源,可能会导致数据竞争和并发问题。为了避免这些问题,我们需要使用同步机制来保证线程之间的协调与同步。

二、Java中的同步机制

Java提供了多种同步机制,包括关键字synchronized、Lock接口、volatile关键字以及各种同步器等。下面分别介绍这些同步机制的特点和使用方法。

1. synchronized关键字

synchronized关键字是Java中最基本的同步机制,它可以修饰方法或代码块,用于实现对共享资源的互斥访问。synchronized关键字的特点如下:

synchronized关键字修饰的方法或代码块在同一时刻只能被一个线程访问,其他线程需要等待。

synchronized关键字可以保证共享资源的可见性和原子性。

synchronized关键字可以用于修饰实例方法、静态方法和代码块。

下面是一个使用synchronized关键字实现线程同步的示例代码:

Copy

public class SynchronizedExample {

private int count = 0;

public synchronized void increment() {

count++;

}

public synchronized int getCount() {

return count;

}

public static void main(String[] args) {

SynchronizedExample example = new SynchronizedExample();

Runnable task = () -> {

for (int i = 0; i < 1000; i++) {

example.increment();

}

};

Thread thread1 = new Thread(task);

Thread thread2 = new Thread(task);

thread1.start();

thread2.start();

try {

thread1.join();

thread2.join();

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println("Final count: " + example.getCount());

}

}

在上面的示例代码中,我们使用synchronized关键字修饰了increment()和getCount()方法,保证了对count变量的互斥访问。最终输出的结果应该是Final count: 2000,证明了线程同步的正确性。

2. Lock接口

除了使用synchronized关键字外,Java还提供了Lock接口及其实现类来实现线程同步。Lock接口提供了更加灵活和强大的线程同步机制,相比于synchronized关键字,Lock接口的特点如下:

Lock接口提供了更细粒度的锁控制,可以实现更灵活的线程同步。

Lock接口支持公平锁和非公平锁,可以根据实际需求选择合适的锁策略。

Lock接口提供了更多的功能,比如可中断的锁获取、超时的锁获取、锁的条件等待等。

下面是一个使用Lock接口实现线程同步的示例代码:

Copy

public class LockExample {

private int count = 0;

private Lock lock = new ReentrantLock();

public void increment() {

lock.lock();

try {

count++;

} finally {

lock.unlock();

}

}

public int getCount() {

lock.lock();

try {

return count;

} finally {

lock.unlock();

}

}

public static void main(String[] args) {

LockExample example = new LockExample();

Runnable task = () -> {

for (int i = 0; i < 1000; i++) {

example.increment();

}

};

Thread thread1 = new Thread(task);

Thread thread2 = new Thread(task);

thread1.start();

thread2.start();

try {

thread1.join();

thread2.join();

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println("Final count: " + example.getCount());

}

}

在上面的示例代码中,我们使用Lock接口及其实现类ReentrantLock来实现对count变量的互斥访问。通过调用lock()方法获取锁,使用unlock()方法释放锁,确保了线程同步的正确性。

3. volatile关键字

volatile关键字是Java中的另一个线程同步机制,它用于修饰变量,保证了变量的可见性和有序性。volatile关键字的特点如下:

volatile关键字修饰的变量对所有线程可见,每个线程都从主存中读取最新的值。

volatile关键字禁止了指令重排序优化,保证了变量的有序性。

volatile关键字不保证原子性,不适合对变量进行复合操作。

下面是一个使用volatile关键字实现线程同步的示例代码:

Copy

public class VolatileExample {

private volatile int count = 0;

public void increment() {

count++;

}

public int getCount() {

return count;

}

public static void main(String[] args) {

VolatileExample example = new VolatileExample();

Runnable task = () -> {

for (int i = 0; i < 1000; i++) {

example.increment();

}

};

Thread thread1 = new Thread(task);

Thread thread2 = new Thread(task);

thread1.start();

thread2.start();

try {

thread1.join();

thread2.join();

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println("Final count: " + example.getCount());

}

}

在上面的示例代码中,我们使用volatile关键字修饰了count变量,保证了对count变量的可见性。最终输出的结果应该是Final count: 2000,证明了线程同步的正确性。

三、常用的同步器

除了上述介绍的同步机制外,Java还提供了一些常用的同步器,用于实现更复杂的线程同步。下面介绍几种常用的同步器。

1. CountDownLatch

CountDownLatch是一种同步工具类,它可以让一个或多个线程等待其他线程完成后再继续执行。CountDownLatch内部维护了一个计数器,当计数器为0时,所有等待的线程将被释放。

下面是一个使用CountDownLatch实现线程同步的示例代码:

Copy

public class CountDownLatchExample {

public static void main(String[] args) {

int threadCount = 5;

CountDownLatch latch = new CountDownLatch(threadCount);

Runnable task = () -> {

try {

System.out.println(Thread.currentThread().getName() + " is running");

Thread.sleep(1000);

System.out.println(Thread.currentThread().getName() + " is done");

} catch (InterruptedException e) {

e.printStackTrace();

} finally {

latch.countDown();

}

};

for (int i = 0; i < threadCount; i++) {

new Thread(task).start();

}

try {

latch.await();

System.out.println(“All threads are done”);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

在上面的示例代码中,我们创建了5个线程,每个线程执行一些任务,并调用countDown()方法减少计数器的值。主线程调用await()方法等待计数器变为0,然后输出"All threads are done",实现了线程的同步。

2. CyclicBarrier

CyclicBarrier是一种同步工具类,它可以让一组线程互相等待,直到所有线程都达到某个公共屏障点后再继续执行。CyclicBarrier内部也维护了一个计数器,当计数器为0时,所有等待的线程将被释放。

下面是一个使用CyclicBarrier实现线程同步的示例代码:

Copy

public class CyclicBarrierExample {

public static void main(String[] args) {

int threadCount = 5;

CyclicBarrier barrier = new CyclicBarrier(threadCount, () -> {

System.out.println(“All threads are ready”);

});

Runnable task = () -> {

try {

System.out.println(Thread.currentThread().getName() + " is ready");

barrier.await();

System.out.println(Thread.currentThread().getName() + " is running");

} catch (InterruptedException | BrokenBarrierException e) {

e.printStackTrace();

}

};

for (int i = 0; i < threadCount; i++) {

new Thread(task).start();

}

}

}

在上面的示例代码中,我们创建了5个线程,每个线程调用await()方法等待其他线程都达到屏障点。当所有线程都达到屏障点时,触发屏障动作,输出"All threads are ready",然后所有线程继续执行。

3. Semaphore

Semaphore是一种同步工具类,它可以控制同时访问某个资源的线程数。Semaphore内部维护了一组许可证,每个线程在访问资源前需要获取许可证,如果许可证不足,则需要等待。

下面是一个使用Semaphore实现线程同步的示例代码:

Copy

public class SemaphoreExample {

public static void main(String[] args) {

int threadCount = 5;

Semaphore semaphore = new Semaphore(2);

Runnable task = () -> {

try {

semaphore.acquire();

System.out.println(Thread.currentThread().getName() + " is running");

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

} finally {

semaphore.release();

}

};

for (int i = 0; i < threadCount; i++) {

new Thread(task).start();

}

}

}

在上面的示例代码中,我们创建了5个线程,每个线程在执行任务之前调用acquire()方法获取许可证,如果许可证不足,则需要等待。任务执行完毕后调用release()方法释放许可证,其他线程可以继续获取许可证。

四、总结

本文介绍了Java中线程同步的概念和常用的同步机制。通过使用synchronized关键字、Lock接口和volatile关键字,我们可以实现对共享资源的线程安全访问。此外,Java还提供了一些同步器,如CountDownLatch、CyclicBarrier和Semaphore,用于实现更复杂的线程同步。

正确地处理线程同步问题对于保证程序的正确性和性能至关重要。在实际开发中,我们需要根据具体的需求选择合适的同步机制和同步器。同时,我们还需要注意避免死锁、饥饿和竞争等问题,保证线程同步的高效性和可靠性。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。