1.线程安全问题
在大多数的实际应用中,两个及以上的线程执行同一任务(共享同一数据的存取)。如果多个线程存取同一个对象,并且每个线程都改变了该对象的状态,这时可能会发生线程相互覆盖,导致数据错乱。
//一个卖票的任务
public class Task implements Runnable {
int count = 5; //总共五张票
@Override
public void run() {
while(count > 0){
System.out.println("出票完成!");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("剩余票数:" + count);
}
}
}
public class DemoMultiThread {
public static void main(String[] args) {
Task task = new Task();
new Thread(task).start();
new Thread(task).start();
new Thread(task).start();
}
}
运行结果如下:
可以看到,卖出的票数远不止五张有些票卖出了多次,甚至票数为负的情况还在卖票。
解决线程不安全的方法:
- 同步代码块
- 同步方法
- 显式锁
2.同步代码块
synchronized 关键字,当线程进入如下的代码块时,它会获得obj的锁,此时其他线程不能进入该代码块
synchronized(obj){ //Java任何对象都可作为锁对象传入到obj
//code
}
public class DemoMultiThread {
public static void main(String[] args) {
Task task = new Task();
//三个线程同时卖票
new Thread(task).start();
new Thread(task).start();
new Thread(task).start();
}
}
class Task implements Runnable {
int count = 10; //总票数为10
Object object = new Object(); //新建一个锁对象
@Override
public void run() {
while(count > 0){
synchronized (object) {
if (count > 0) {
System.out.println(Thread.currentThread().getName() + "出票完成!");
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
count--;
System.out.println("剩余票数:" + count);
}
}
}
}
}
从运行结果可知,多线程同时卖票,且票数数据没有发生错乱
3.同步方法
//同步方法格式
public synchronized void methodName(){
// method body
}
//等价于
public void methodName(){
this.intrinsicLock.lock();
try{
//method body;
} finally {
this.intrinsicLock.unlock();
}
}
- 从Java1.0开始,每个对象都有一个内部锁,同步方法的锁对象是
this
(当类有一个静态同步方法时,锁对象为className.class)
public class DemoMultiThread {
public static void main(String[] args) {
Task task = new Task();
new Thread(task).start();
new Thread(task).start();
new Thread(task).start();
}
}
class Ticket implements Runnable {
private int count = 10;
@Override
public void run() {
while(count > 0){
sellTicket();
}
}
public synchronized void sellTicket(){
if (count > 0) {
System.out.println(Thread.currentThread().getName() + "出票完成!");
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
count--;
System.out.println("剩余票数:" + count);
}
}
}
4.显示锁ReentrantLock
-
同步代码块和同步方法都属于隐式锁,
-
显示锁:Lock 子类 ReentrantLock
用ReentrantLock保护代码块的基本结构如下:
myLock.lock(); // a ReentrantLock object
try{
// critical section
} finally {
myLock.unlock(); //make sure the lock is unlocked even if an exception is thrown
}
一旦一个线程锁定了锁对象,其它任何线程都无法通过lock语句
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class DemoMultiThread {
public static void main(String[] args) {
Task task = new Task();
new Thread(task).start();
new Thread(task).start();
new Thread(task).start();
}
}
class Ticket implements Runnable {
private int count = 10;
private Lock l = new ReentrantLock(); //显示锁 l
@Override
public void run() {
while (count > 0) {
l.lock();
if (count > 0) {
System.out.println(Thread.currentThread().getName() + "出票完成!");
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
count--;
System.out.println("剩余票数:" + count);
}
l.unlock();
}
}
}
5.公平锁和非公平锁
- 公平锁:排队,先来先得
- 非公平锁:锁一旦解开,所有线程一起抢
当使用显示锁时,
Lock l = new ReentrantLock(true) //传入参数true, l即为公平锁
6.死锁
public class DemoDeadLock {
public static void main(String[] args) {
Interviewer p1 = new Interviewer();
Interviewee p2 = new Interviewee();
new InterviewThread(p1, p2).start(); // p1.say(p2)
p2.say(p1);
}
}
class InterviewThread extends Thread {
private Interviewer p1;
private Interviewee p2;
InterviewThread() {};
public InterviewThread(Interviewer p1, Interviewee p2) {
this.p1 = p1;
this.p2 = p2;
}
@Override
public void run() {
p1.say(p2);
}
}
// 面试官
class Interviewer {
public synchronized void say(Interviewee p2) {
System.out.println("答出来什么是死锁,就给你发offer");
p2.reply();
}
public synchronized void reply() {
System.out.println("先回答问题");
}
}
// 求职者
class Interviewee {
public synchronized void say(Interviewer p1) {
System.out.println("给我发offer,就回答什么是死锁");
p1.reply();
}
public synchronized void reply() {
System.out.println("先发offer");
}
}
这种情况就有几率出现死锁,运行结果如下:
-
p2.say(p1); 执行完打印输出后,会调用p1的reply方法, (say方法未执行完毕,p2对象(this)此时是锁住的)
-
而此时new InterviewThread(p1, p2).start(); // p1.say(p2) 线程也正在运行,打印完后等待p2.reply,(say方法未执行完毕,p1对象(this)此时是锁住的)
-
p1、p2都在等待双方的回应,但二者此时都是锁着的,所以产生了死锁
也有一定几率是正常的:
当p2.say(p1)执行完毕,而new InterviewThread(p1, p2).start()还未执行时便可以跳出死锁