多线程安全和线程同步

时间:2022-07-04 08:42:51

问题

线程不安全问题

在多个线程使用同一个资源的时候,有可能存在一个资源被一个线程占有,但一系列操作(原子操作:不可再分割的操作)并未执行完成,执行过程中的资源被其他线程拿去用了。

同步

在一个线程执行原子操作时,其他线程不能占有资源

1.同步代码块

同步锁在括号中,是线程共同享有的资源

@Override
public void run() {
for (int i = 0; i < 50; i++) {
String name = Thread.currentThread().getName();
synchronized (this) {//同步锁,线程共同享有的资源,这里的this是指Apple对象
if (num > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + "吃了第" + num-- + "个苹果");
}
}
}
}

2.同步方法(不能同步run方法)

使用synchronized来修饰方法,对于非静态方法,同步锁是this;对于非静态方法,同步锁是当前方法所在类的字节码对象 类名.class

@Override
public void run() {
for (int i = 0; i < 50; i++) {
test();
}
}

private synchronized void test() {
String name = Thread.currentThread().getName();
if (num > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + "吃了第" + num-- + "个苹果");
}
}

3.锁机制

synchronized采用了自动加锁和释放锁的机制,手动加锁的方法更加透明,功能更加强大

package java_study;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Apple3 implements Runnable {
public int num = 50;
//1.由于 Lock 是一个接口,使用其实现类 ReentrantLock 获得一个锁对象
private final Lock lock = new ReentrantLock();
@Override
public void run() {
for (int i = 0; i < 50; i++) {
eat();
}
}

private void eat() {
//2.在方法的开始加锁吗,需要使用try-finally保证释放锁的进行
lock.lock();
try {
if (num > 0) {
Thread.sleep(10);
String name = Thread.currentThread().getName();
System.out.println(name + "吃了第" + num-- + "个苹果");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//3.释放锁
lock.unlock();
}
}

}

public class ClockDemo {
public static void main(String[] args) {
Apple3 a = new Apple3();
new Thread(a, "A").start();
new Thread(a, "B").start();
new Thread(a, "C").start();
}
}

!同步的优缺点:

  • 使用synchronized提高了线程安全性,降低了性能,尽量减小作用域 如:StringBuffer类 ArrayList HashMap
  • 不适用synchronized提升了性能。降低了线程安全性 如:StringBuilder类 Vector Hashtable

!Sleep 对锁的影响

  • Sleep 不会导致锁丢失,会一直占用资源直到从 sleep 唤醒