认识多线程(二)、线程安全
在Java中,线程安全是指多个线程访问共享资源时,不会出现数据错乱、状态异常等问题,保证程序能够按照设计的预期进行正确的操作。在多线程环境下,线程安全是非常重要的,因为多个线程并发访问共享资源可能导致竞态条件(Race Condition)、死锁(Deadlock)、数据不一致等问题。
实现线程安全的方式通常包括以下几种:
- 互斥同步:通过互斥机制来确保在同一时刻只有一个线程访问共享资源。在Java中,可以使用
synchronized
关键字或者ReentrantLock
等锁机制来实现互斥同步。 - 原子操作:通过使用原子类(Atomic Class)来实现对共享资源的原子操作,避免了使用锁机制而带来的性能开销。
- 不可变对象:设计不可变对象可以避免并发环境下的数据竞争,因为不可变对象的状态不会改变。
- 线程本地存储(Thread-local storage):将共享的资源复制多份,每个线程都有自己的副本,从而避免了共享资源的竞争。
- 同步容器:使用
ConcurrentHashMap
、CopyOnWriteArrayList
等并发容器来代替同步容器,避免手动加锁。
解决线程安全的方式:锁
🔒
一、认识线程锁
线程锁(也称为互斥锁)是多线程编程中用于保护共享资源的一种同步机制。它可以确保在同一时刻只有一个线程可以访问被保护的资源,从而避免多个线程同时修改共享数据而导致数据不一致或其他意外情况的发生。
二、对象和this锁
类锁和对象锁(this锁)在Java中是实现多线程同步的两种不同方式,它们有以下区别:
-
作用范围:
- 类锁:类锁是针对整个类的,当一个线程获取了该类的类锁后,其他线程无法获取该类的类锁,无论是哪个实例调用该类的同步方法都会受到限制。
- 对象锁(this锁):对象锁是针对对象实例的,每个对象实例都有自己的对象锁。当一个线程获取了某个对象实例的对象锁后,其他线程无法获取该对象实例的对象锁,但其他对象实例的对象锁不受影响。
-
获取方式:
- 类锁:可以通过
synchronized
修饰静态方法或者使用类的Class对象来获取类锁。 - 对象锁(this锁):通过
synchronized
修饰非静态方法或者代码块,并以当前对象实例(this
)作为锁对象来获取对象锁。
- 类锁:可以通过
-
影响范围:
- 类锁:影响整个类的所有实例,因为类锁是针对整个类的。
- 对象锁(this锁):仅影响持有该对象锁的对象实例,不同对象实例之间的对象锁互不影响。
三、对象锁(this锁)
在Java中,this锁是一种对象级别的锁,也称为对象锁。当一个线程调用一个对象的同步方法时,它就获得了该对象实例的锁,其他线程必须等待该线程释放锁才能继续执行。这种锁定机制可以确保在同一时间内只有一个线程可以执行该对象的同步方法或者同步块。
使用this锁的常见方式有两种:
- 同步方法:通过在方法声明中使用
synchronized
关键字,可以将整个方法变成同步方法,这意味着该方法在被调用时会自动获取对应对象的锁。
java
javaCopy Code
public synchronized void someMethod() {
// 同步代码块
}
- 同步块:通过在代码块中使用
synchronized
关键字并指定锁对象为this,可以实现同步块。
java
javaCopy Code
public void someMethod() {
synchronized (this) {
// 同步代码块
}
}
需要注意的是,this锁是针对对象实例的,因此每个对象实例都有自己的this锁。不同对象实例之间的this锁互不影响,因为它们代表不同的对象。另外,this锁只在同一个对象实例内部起作用,不会对其他实例产生影响。
四、类锁
在 Java 中,类锁是一种用于控制对静态成员变量或静态方法的并发访问的锁机制。与对象锁不同,类锁是基于类的 Class 对象的,因此它作用于整个类而不是特定的对象实例。
类锁可以通过以下两种方式来获取:
- 使用
synchronized
关键字修饰静态方法:
Java
javaCopy Code
public class MyClass {
public static synchronized void staticMethod() {
// 静态同步方法中的同步代码块
}
}
- 使用类的 Class 对象作为锁对象进行同步控制:
java
javaCopy Code
public class MyClass {
public void method() {
synchronized (MyClass.class) {
// 同步代码块
}
}
}
类锁的特点包括:
- 类锁是针对整个类的,因此它作用于所有该类的实例以及静态成员。
- 类锁可以控制对静态成员变量和静态方法的并发访问。
- 与对象锁不同,类锁是全局唯一的,因此对该类的所有实例都起作用。
- 获取类锁的方式可以是使用
synchronized
关键字修饰静态方法,也可以是使用类的 Class 对象作为锁对象来进行同步控制。
五、this锁和类锁简单应用
实现一个简单的抢票demo
1、使用this锁实现
java
package com.example.mayikttest.Thread.Synchronize;
public class ThreadCount implements Runnable {
private static Integer count = 100; // 一百张票
private String lock = "lock";
@Override
public void run() {
while (count > 1) {
cal();
}
}
private void cal() { // 修饰非静态方法
synchronized (this) { // 修饰代码块
try {
Thread.sleep(10);
} catch (Exception e) {
}
count--;
System.out.println(Thread.currentThread().getName() + "," + count);
}
}
public static void main(String[] args) {
ThreadCount threadCount = new ThreadCount();
Thread thread1 = new Thread(threadCount);
Thread thread2 = new Thread(threadCount);
thread1.start();
thread2.start();
}
}
2、实现类锁实现
java
package com.example.mayikttest.Thread.Synchronize;
public class ThreadCount3 implements Runnable {
private static Integer count = 100;
private static Object lock = ThreadCount.class; // 类对象作为锁
@Override
public void run() {
while (count > 1) {
cal();
}
}
private void cal() {
synchronized (lock) { // 使用类对象作为锁
try {
Thread.sleep(10);
} catch (Exception e) {
// 异常处理
}
count--;
System.out.println(Thread.currentThread().getName() + "," + count);
}
}
public static void main(String[] args) {
ThreadCount threadCount = new ThreadCount();
Thread thread1 = new Thread(threadCount);
Thread thread2 = new Thread(threadCount);
thread1.start();
thread2.start();
}
}
3、线程池使用
java
package com.example.mayikttest.Thread.Synchronize;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadCount3 implements Runnable {
private static Integer count = 100;
private static Object lock = ThreadCount.class; // 类对象作为锁
@Override
public void run() {
while (count > 1) {
cal();
}
}
private void cal() {
synchronized (lock) { // 使用类对象作为锁
try {
Thread.sleep(10);
} catch (Exception e) {
// 异常处理
}
count--;
System.out.println(Thread.currentThread().getName() + "," + count);
}
}
public static void main(String[] args) {
ThreadCount threadCount = new ThreadCount();
// 使用线程池Executors
ExecutorService executor = Executors.newFixedThreadPool(8); // 创建固定大小的线
executor.execute(threadCount);
}
}