线程并发和同步锁机制:
并发:
并发指的是当多个线程对同一个对象的数据操作。但是并发会带来数据混乱,数据不安全问题。例如:
/**
1. 2.多个线程访问同一个对象,会引起并发问题,这里通过synchronized同步锁把实现同步化解决问题。synchronized的同步锁有两种实现方式,同步方法或者同步对象代码块。
*/
class ConcurrencyRunnable implements Runnable {
static int i = 1000;
private static boolean flag = true;
@Override
public void run() {
while (flag) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (i <= 0) {
flag = false;
break;
}
System.out.println(Thread.currentThread().getName() + "拿走了" + i--);
}
}
}
public static void main(String[] args) {
ConcurrencyRunnable concurrencyRunnable = new ConcurrencyRunnable();
new Thread(concurrencyRunnable, "小明").start();
new Thread(concurrencyRunnable, "小红").start();
new Thread(concurrencyRunnable, "小刚").start();
}
结果:
会发现小明和小刚拿走的"数字"重复。并发因此的数据不安全的两种情况:
- 多个线程对同一个对象数据进行操作且没有使用同步锁机制。
- 在线程中使用List添加元素。
(记录第一个问题:为什么List是不安全的?,后续深入学习后单独开一篇记录)
同步锁机制:
所谓的同步锁,指的每个对象都有自己的锁,是当多个线程访问同一对象的时候,会拿到对象的排它锁。当一个线程拿到对象的排它锁时候,然后才操作对象的数据,而其他的线程只能等待,直到释放了对象的排它锁后,其他线程才可以对该对象进行操作。本质上来说,就是让没拿到对象的排它锁的线程进入对象的等待池中等待。
同步锁实现的方式:
- 使用synchronized关键字修饰隐式加锁。
- 使用Lock锁进行显式加锁。
(记录第二个问题:synchronized的底层代码原理?,后续深入学习后单独开一篇记录)
synchronized:
synchronized(Object) 代码块:
synchronized 修饰特定方法:
使用Lock锁():
实例化实现Lock接口的子类ReentrantLock类对象,显示定义加锁和解锁。
补充:虽然通过同步锁机制能够很好的解决了并发所带来的问题,但如果使用不当也会造成影响,例如:
- 一个线程拿到对象的排它锁使得其他等待该对象的线程必须挂起。
- 多线程的竞争下,频繁的加锁和解锁会引起Cpu过渡调度等性能问题。
- 当一个优先级高的线程在等待优先级低的线程的对象排它锁时候,会引起优先级倒置所带来的性能问题。
严重的情况下,会导致"死锁"现象。
死锁:
举个例子来说:小明和小刚两个人都有各自的玩具,但彼此都想获得对方的。小明不让自己玩具给小刚,小刚也不让自己玩具给小明,结果两个人都僵持在一起,一直都没有结果。多线程的死锁现象就和这例子一样,双方线程彼此都想访问对方的资源,但同时也没有对使用完的资源进行释放。
例如:
public class Demo10 {
public static void main(String[] args) {
new Thread(new SwapToy("小明")).start();
new Thread(new SwapToy("小刚")).start();
}
}
class xiaoMing {
protected String toyCar = "玩具车";
}
class xiaoGang {
protected String toyHouse = "玩具房子";
}
class SwapToy implements Runnable {
private String name;
private static xiaoMing xiaoMing;
private static xiaoGang xiaoGang;
public SwapToy(String name) {
this.name = name;
xiaoGang = new xiaoGang();
xiaoMing = new xiaoMing();
}
@Override
public void run() {
if (name.equals("小明")) {
synchronized (xiaoMing) {
System.out.println(name + "拿到了" + xiaoMing.toyCar);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (xiaoGang) {
System.out.println(name + "拿到了" + xiaoGang.toyHouse);
}
}
} else {
synchronized (xiaoGang) {
System.out.println(name + "拿到了" + xiaoGang.toyHouse);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (xiaoMing) {
System.out.println(name + "拿到了" + xiaoMing.toyCar);
}
}
}
}
}
结果死锁了(小明和小刚为了玩具打起来了…):
死锁的必要条件:
- 互斥条件:即线程访问的资源只允许线程一个一个访问。
- 请求和保持条件:一个线程因请求资源而进入阻塞状态;线程对以获得的资源不释放。
- 不剥夺条件:不强制释放线程获得的资源。
- 循环条件:线程之间对资源的获取形成了头尾相连的循环。
因此根据必要这四个必要条件任意一条进行处理便可以避免死锁的发生。
因此对代码修改一下:
public class Demo10 {
public static void main(String[] args) {
new Thread(new SwapToy("小明")).start();
new Thread(new SwapToy("小刚")).start();
}
}
class xiaoMing {
protected String toyCar = "玩具车";
}
class xiaoGang {
protected String toyHouse = "玩具房子";
}
class SwapToy implements Runnable {
private String name;
private static xiaoMing xiaoMing;
private static xiaoGang xiaoGang;
public SwapToy(String name) {
this.name = name;
xiaoGang = new xiaoGang();
xiaoMing = new xiaoMing();
}
@Override
public void run() {
if (name.equals("小明")) {
synchronized (xiaoMing) {
System.out.println(name + "拿到了" + xiaoMing.toyCar);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (xiaoGang) {
System.out.println(name + "拿到了" + xiaoGang.toyHouse);
}
} else {
synchronized (xiaoGang) {
System.out.println(name + "拿到了" + xiaoGang.toyHouse);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (xiaoMing) {
System.out.println(name + "拿到了" + xiaoMing.toyCar);
}
}
}
}
结果(小刚和小明和好了…):