1. 线程安全
当多个线程并行时,若这些线程对于同一个资源都有操作权限,则会造成同一时间内一个资源被多个线程操作后,出现一种混乱的结果,无法实现程序的既定功能。这就是线程安全问题。因此我们考虑在一个线程操作一个资源的时候,让其他资源无法操作该线程,这就是对这个资源加锁。当占有这个资源的线程结束后,才会解锁,然后其他线程才有操作它的机会。
public class TestLock {
public static void main(String[] args) {
TestLock2 t2 = new TestLock2();
new Thread(t2).start();
new Thread(t2).start();
new Thread(t2).start();
}
}
public class TestLock2 implements Runnable{
int Num = 10;
private final ReentrantLock lock = new ReentrantLock();
public void run() {
while (true){
if (Num>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Num--);
}else {
break;
}
}
}
}
线程对资源的操作有读取和写入两种状态,所以多个线程操作资源可大体分为:读读、写写、读写,只要有写入的情况那么就需要使用锁。
2. 锁的实现
使用锁有两种方法: synchronized 关键字 和 Lock 对象
2.1 synchronized 关键字
1.synchronized(this){代码块}
一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞
publid void run() {
while (true){
synchronized(this){ //加锁
if (Num>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Num--);
}else {
break;
}
}
}
}
2.public synchronized void method(){方法}
public synchronized void run() { //加锁
while (true){
if (Num>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Num--);
}else {
break;
}
}
}
一个线程访问该方法时,阻塞其他想要访问该方法的线程
2.2 Lock 对象
private final ReentrantLock lock = new ReentrantLock(); //先创建锁对象
public void run() {
while (true){
try {
lock.lock(); //加锁
if (Num>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Num--);
}else {
break;
}
}finally { //解锁
lock.unlock();
}
}
}
2.3 synchronized与Lock 对象对比
- Lock需要手动开启与关闭/synchronized在线程结束后自动释放
- Lock只有代码块锁,synchronized有代码块锁和方法锁
- Lock锁性能更好,JVM花费较少的时间来调度线程
- 使用优先级:Lock>同步代码块(已经进入方法体,分配了相应资源)>同步方法(在方法体外)