老生常谈…对于大佬们根本都不用看的博客!
对于像我这种菜鸡还是得一步一步的来!如果有错误请大佬们指点指点!
线程安全
原因
多线程环境下操作同一个资源(成员变量),就会引发安全问题。
简单的解决思路
对于多条操作共享数据的语句,只能让一个线程都执行完,在执行的过程中,其他线程不参与
JDK如何解决线程安全?
JDK5之前
JAVA提供了专业的解决方法:同步机制
- 同步代码块
synchronized (对象){
// 需要被同步的代码
}
- 同步方法
权限修饰符 synchronized 返回值类型 方法名(形参列表){
// 需要被同步的代码
}
同步机制:在并发工作时,通过加上一个锁防止两个线程同时争夺相同的资源。这个资源被A线程访问,则加上一把锁,让B线程处于等待状态,当A线程出来的后,B才能进行处理。
synchronized 的锁是什么?
- 任意对象都可以作为同步锁。所有对象都自动含有单一的锁
- 同步方法和同步代码的锁:静态方法( 类名.class )、非静态方法( this )
注意:确保使用同一个资源的多个线程共用一把锁,否则会出现安全问题。
JDK5之后
显式定义同步锁对象来实现同步,通过Lock接口控制多个线程对共享资源进行访问。锁对共享资源进行了独占访问,每次只能有一个线程对Lock对象加锁。所以每当线程开始访问共享资源时。应先获得Lock对象。
ReentrantLock 类实现Lock接口,它可以显示的加锁和释放锁。
synchronized解决火车票问题
public class TicketSynchronized implements Runnable {
private int tickets = 100;
@Override
public void run() {
while (true) {
//this.consume();
//同步代码块
synchronized (this) {
if (this.tickets > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "出售第" + this.tickets-- + "张");
} else {
break;
}
}
}
}
//同步方法
private synchronized void consume() {
if (this.tickets > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "出售第" + this.tickets-- + "张");
}
}
public static void main(String[] args) {
TicketSynchronized ticket = new TicketSynchronized();
Thread thread1 = new Thread(ticket,"A");
Thread thread2 = new Thread(ticket,"B");
Thread thread3 = new Thread(ticket,"C");
thread1.start();
thread2.start();
thread3.start();
}
}
Lock解决火车票问题
public class TicketLock implements Runnable{
private final Lock lock = new ReentrantLock();
private int tickets = 100;
@Override
public void run() {
while (true) {
//加锁
this.lock.lock();
try {
if (this.tickets > 0) {
System.out.println(Thread.currentThread().getName() + "卖第" + this.tickets-- + "张票");
}
} finally {
// 解锁
this.lock.unlock();
}
}
}
public static void main(String[] args) {
TicketLock ticket = new TicketLock();
Thread thread1 = new Thread(ticket,"A");
Thread thread2 = new Thread(ticket,"B");
Thread thread3 = new Thread(ticket,"C");
thread1.start();
thread2.start();
thread3.start();
}
}
死锁问题与代码实现
死锁:
- 一个共享资源需要两个不同的锁,但是这个两个锁分别被两个线程锁拥有,但是双发都不肯放弃这把锁,然后形成了僵持状态就是死锁
- 出现死锁后,程序不会抛异常,不会有提示,所有的线程都处于阻塞状态,无法正常运行。
标准答案如何避免死锁:
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
- 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
注意:破坏其中的一条就可以!
解决方案:
- 减少同步资源的定义
- 避免嵌套同步
代码实现如下所示:
public class DeadLock implements Runnable{
private static final String A = "A";
private static final String B = "B";
private final boolean flag;
public DeadLock(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
while (true) {
if (this.flag) {
synchronized (A) {
System.out.println("true线程获取A锁");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (B) {
System.out.println("true线程获取B锁");
}
}
} else {
synchronized (B) {
System.out.println("false线程获取B锁");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (A) {
System.out.println("false线程获取A锁");
}
}
}
}
}
public static void main(String[] args) {
DeadLock lock1 = new DeadLock(true);
DeadLock lock2 = new DeadLock(false);
Thread thread1 = new Thread(lock1);
Thread thread2 = new Thread(lock2);
thread1.start();
thread2.start();
}
}
Lock和Synchronized 总结
- Lock 是显式锁(手动开启和关闭锁,别忘记关闭锁),Synchronized 是隐式锁,出了作用域自动释放。
- Lock 只有代码块锁,Synchronized有代码块锁和方法锁。
- 使用 Lock 锁,JVM 将花费较少的时间来调度线程,性能更好,有更好的扩展性(因为是接口可以实现很多类)。