为什么会有线程安全的问题?
比如两个人同时去做同一件事情,会很快,但是呢,因为要做同一件事,很有可能就会发生争抢冲突,导致这个事情被玩坏
我这里举了一个例子,我和我女朋友(存在的)一起洗碗
我和我女朋友各自是一个线程,对碗进行洗碗操作
代码示例:
首先是创建一个堆没洗的碗,还有洗碗的方法
package com.kaikeba.demo;
import java.util.concurrent.locks.ReentrantLock;
public class Bowl{
//碗数量
private int bowlNumber;
//一参构造,用于创建自定义 碗数量 的一堆碗对象
public Bowl(int bowlNumber) {
this.bowlNumber = bowlNumber;
}
//返回当前碗的数量
public int getBowlNumber() {
return bowlNumber;
}
//洗碗方法
public void wash(){
//循环直到碗洗碗
while (this.getBowlNumber()>0) {
try {
//延迟,放大差错
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
bowlNumber--;
System.out.println(Thread.currentThread().getName() + ",还有" + bowlNumber + "个碗");
}
}
}
这是我洗碗的线程
public class Me implements Runnable{
//传过来这堆还没洗的碗对象
private Bowl b = new Bowl();
public Me( Bowl b) {
this.b = b;
}
@Override
public void run() {
//我把碗洗了
b.wash();
}
}
这是叫女朋友洗碗的线程
package com.kaikeba.demo;
public class She implements Runnable{
private Bowl b = new Bowl();
//传过来这堆还没洗的碗对象
public She(Bowl b) {
this.b = b;
}
//女朋友洗碗
@Override
public void run() {
b.wash();
}
}
然后我们把饭吃掉,生成一堆没洗的碗,我叫上我的女朋友(情愿)一起去洗碗
public class StartWash {
public static void main(String[] args) {
//把饭吃了,留下一堆需要洗的碗数量
Bowl bowl = new Bowl(10);
//我出现
Me me = new Me(bowl);
//叫上女朋友
She she = new She(bowl);
//开始同时洗碗
//修改一下线程的名字
new Thread(me,"我洗了一个碗").start();
new Thread(she,"女朋友洗了一个碗").start();
}
}
结果很可怕
女朋友很想帮我洗碗,我们就争抢起来了,最后洗完还凭空多洗了一个???(邻居正在吃饭,手里的碗突然消失:卧槽我碗呢?)
//运行结果
女朋友洗了一个碗:
,还有8个碗
我洗了一个碗:
,还有9个碗
我洗了一个碗:
,还有6个碗
女朋友洗了一个碗:
,还有6个碗
女朋友洗了一个碗:
,还有5个碗
我洗了一个碗:
,还有5个碗
女朋友洗了一个碗:
,还有4个碗
我洗了一个碗:
,还有3个碗
女朋友洗了一个碗:
,还有2个碗
我洗了一个碗:
,还有1个碗
女朋友洗了一个碗:
,还有-1个碗
我洗了一个碗:
,还有0个碗
Process finished with exit code 0
这时候我们提前就要好好沟通,我们一个一个的洗,洗完了一个才可以洗下一个喔!
方案1:同步代码块
使用 synchronized() 对需要完整执行的语句进行“包裹”,就是洗碗的方法,synchronized(Obj obj) 构造方法里是可以传入任何类的对象,
但是既然是监听器就传一个唯一的对象来保证“锁”的唯一性,因此一般使用共享资源的对象来作为 obj 传入 synchronized(Obj obj) 里:
public class Bowl{
//碗数量
private int bowlNumber;
public Bowl(int bowlNumber) {
this.bowlNumber = bowlNumber;
}
public int getBowlNumber() {
return bowlNumber;
}
public Bowl() {
}
//洗碗方法
public void wash(){
//synchronized 同步代码块
synchronized (this){
//循环直到碗洗碗
while (this.getBowlNumber()>0) {
try {
//延迟s,放大差错
Thread.sleep(250);
} catch (InterruptedException e) {
e.printStackTrace();
}
bowlNumber--;
System.out.println(Thread.currentThread().getName() + ":\n,还有" + bowlNumber + "个碗");
}
}
}
}
方案2:同步方法
在方法的申明里申明 synchronized 即可:
public class Bowl{
//碗数量
private int bowlNumber;
public Bowl(int bowlNumber) {
this.bowlNumber = bowlNumber;
}
public int getBowlNumber() {
return bowlNumber;
}
public Bowl() {
}
//洗碗方法
public synchronized void wash(){
//循环直到碗洗完
while (this.getBowlNumber()>0) {
try {
//延迟,放大差错
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
bowlNumber--;
System.out.println(Thread.currentThread().getName()+":\n,还有"+bowlNumber+"个碗");
}
}
}
方法3: 使用同步锁:
account 类创建私有的 ReetrantLock 对象,调用 lock() 方法,同步执行体执行完毕之后,需要用 unlock() 释放锁。
package com.kaikeba.demo;
import java.util.concurrent.locks.ReentrantLock;
public class Bowl{
//碗数量
private int bowlNumber;
private ReentrantLock lock = new ReentrantLock();
public Bowl(int bowlNumber) {
this.bowlNumber = bowlNumber;
}
public int getBowlNumber() {
return bowlNumber;
}
public Bowl() {
}
//洗碗方法
public void wash(){
//显式锁,上锁
lock.lock();
//循环直到碗洗碗
while (this.getBowlNumber()>0) {
try {
//延迟,放大差错
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
bowlNumber--;
System.out.println(Thread.currentThread().getName()+":\n,还有"+bowlNumber+"个碗");
}
//开锁
lock.unlock();
}
}
我洗了一个碗,还有9个碗
我洗了一个碗,还有8个碗
我洗了一个碗,还有7个碗
我洗了一个碗,还有6个碗
我洗了一个碗,还有5个碗
我洗了一个碗,还有4个碗
我洗了一个碗,还有3个碗
我洗了一个碗,还有2个碗
我洗了一个碗,还有1个碗
我洗了一个碗,还有0个碗
这不就不争不抢,和平的洗完了碗。。。