多线程中对变量的互斥实:
1. 对多线程模式中共享变量的上锁
public class DemoSynchronized {
public static void main(String[] args) {
/*
TODO :
多线程模式中出现的问题:
1.对于总的销售票数 会大于100张
2.对于票数可能会出现负数 不符合代码的执行逻辑
总结:对于多个线程使用同一个变量,那么会造成线程不安全的问题
如何解决? => 线程同步 => 当使用一个变量时,会对当前变量进行加锁,不让其他线程获取到变量
但是其他线程可以获取到CPU的执行权
解决思路:
对于共享变量的使用,需要对其进行加锁操作
格式:
synchronized(对象){需要同步的代码;}
注意:
① 如果当 synchronized 将 sell 方法进行对当前对象加锁,那么可能会出现售票为0的现象
原因:while循环中的ticksNum可能会被其他的线程所抢占CPU执行导致当售票为0时能进入循环
② 如果当 synchronized 将while循环进行对当前对象枷锁,那么对导致只有一个线程运行
原因: 当一个线程获取到 sellRunnable对象之后对其进行加锁
加锁以后没有释放,所以会导致一个线程会一直拥有对象,其他线程没有办法获取到
对象,就没有办法执行while循环
如何释放锁?
对于synchronized修饰的代码块,当执行完成后,那么会对其进行释放锁
*/
//TODO synchronized对对象进行枷锁
// 情况一: sellRunnable对象是 全局唯一的 ,并且其中的变量ticksNum也是唯一的;对当前对象进行上锁
SellRunnable sellRunnable = new SellRunnable();
new Thread(sellRunnable, "售票员1").start();
new Thread(sellRunnable, "售票员2").start();
// 情况二: 当每个线程拥有自己独有的对象,每个线程只对当前自己的对象加锁而不影响其它线程
SellRunnable sellRunnable1 = new SellRunnable();
SellRunnable sellRunnable2 = new SellRunnable();
new Thread(sellRunnable1, "售票员1").start();
new Thread(sellRunnable2, "售票员2").start();
//情况三: 使用一个类来作为唯一、共享的资源;可以实现对资源的互斥访问
TicksNum ticksNum = new TicksNum();
new SynchronizedThread("售票员1",ticksNum).start();
new SynchronizedThread("售票员2",ticksNum).start();
//情况四: 不可以同步:传入的共享资源必须唯一
TicksNum ticksNum1 = new TicksNum();
TicksNum ticksNum2 = new TicksNum();
new SynchronizedThread("售票员1",ticksNum1).start();
new SynchronizedThread("售票员2",ticksNum2).start();
}
static class SellRunnable implements Runnable {
int ticksNum = 100;
@Override
public void run() {
// TODO 一旦当线程获取到唯一的一个this对象,其他线程如果想要执行同步代码块,必须要等当前线程释放
// 同步代码块
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//synchronized对当前SellRunnable的对象进行上锁
synchronized (this) {
// TODO this 代表当前 SellRunnable 的对象 => 在main方法中只有一个sellRunnable对象
if (ticksNum > 0) {
sell();
} else {
break;
}
}
}
}
// 销售的方法
public void sell() {
System.out.println("当前售票员:" + Thread.currentThread().getName() + "正在销售第" + ticksNum + "票");
ticksNum -= 1;
}
}
static class SynchronizedThread extends Thread {
TicksNum ticksNum; // TODO 对于ticksNum可以是唯一的一个对象
public SynchronizedThread(String name, TicksNum ticksNum) {
//设置进程名
super(name);
//设定共享资源
this.ticksNum = ticksNum;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// TODO 如果要加锁,那么需要寻找唯一存在的东西 可以使用传入的 ticksNum => 但是要求只能传入一个
// 对唯一的资源进行加锁
synchronized (ticksNum) {
// TODO this 代表的是 SynchronizedThread 的对象
if (ticksNum.getNum() > 0) {
sell();
} else {
break;
}
}
}
}
public void sell() {
System.out.println("当前售票员:" + Thread.currentThread().getName() + "正在销售第" + ticksNum.getNum() + "票");
ticksNum.sellTick();
}
}
static class TicksNum {
int num = 100;
public void sellTick() {
num -= 1;
}
public int getNum() {
return num;
}
}
}
2. 锁定类来实现同步1
public class DemoSynchronized1Clazz {
public static void main(String[] args) {
/*
TODO
由于对于同步代码块来说,如果给定的锁是一个对象,那么该对象可能在调用方调用时
给定多个对象,那么就失去了同步的意义 于是寻找唯一的一个类别 => 类
总结:
可以在同步代码块中使用特定的类来锁定代码,但是当前的写法,使用的是静态变量
在有些场合下,不是很方便
*/
new SynchronizedClazz("销售员1").start();
new SynchronizedClazz("销售员2").start();
}
static class SynchronizedClazz extends Thread{
public SynchronizedClazz(String name) {
super(name);
}
@Override
public void run() {
while (true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// TODO 针对TicksNumClazz使用加锁操作 表示在同一时刻 有且只能使用一次该
// 对一个类进行上锁,对于静态内部类有如下写法
synchronized (TicksNumClazz.class) {
if (TicksNumClazz.getNum() > 0) {
sell();
} else {
break;
}
}
}
}
public void sell() {
System.out.println("当前售票员:" + Thread.currentThread().getName() + "正在销售第" + TicksNumClazz.getNum() + "票");
//调用静态内部类中的方法实现票数的减一操作
TicksNumClazz.sellTick();
}
}
static class TicksNumClazz {
static int num = 100;
public static void sellTick() {
num -= 1;
}
public static int getNum() {
return num;
}
}
}
3. 锁定类来实现同步2
public class DemoSynchronized2Clazz {
public static void main(String[] args) {
/*
TODO
由于对于同步代码块来说,如果给定的锁是一个对象,那么该对象可能在调用方调用时
给定多个对象,那么就失去了同步的意义 于是寻找唯一的一个类别 => 类
总结:
可以在同步代码块中使用特定的类来锁定代码,但是当前的写法,使用的是静态变量
在有些场合下,不是很方便
解决方案如下:
*/
//传入的 ticksNum 变量唯一,故此可以实现同步操作
TicksNum ticksNum = new TicksNum();
TicksNumObject ticksNumObject1 = new TicksNumObject(ticksNum);
TicksNumObject ticksNumObject2 = new TicksNumObject(ticksNum);
new SynchronizedClazz("销售员1",ticksNumObject1).start();
new SynchronizedClazz("销售员2",ticksNumObject2).start();
}
static class SynchronizedClazz extends Thread{
TicksNumObject ticksNumObject;
public SynchronizedClazz(String name,TicksNumObject ticksNumObject) {
super(name);
this.ticksNumObject = ticksNumObject;
}
@Override
public void run() {
while (true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// TODO 针对TicksNumObject使用加锁操作 表示在同一时刻 有且只能使用一次该类
synchronized (TicksNumObject.class) {
if (ticksNumObject.ticksNum.getNum() > 0) {
sell();
} else {
break;
}
}
}
}
public void sell() {
System.out.println("当前售票员:" + Thread.currentThread().getName() + "正在销售第" + ticksNumObject.ticksNum.getNum() + "票");
ticksNumObject.ticksNum.sellTick();
}
}
static class TicksNumObject {
//TODO 更改其类内部的变量、方法,使其不是静态的;并将原类中的变量num通过一个类来进行实现
TicksNum ticksNum;
public TicksNumObject(TicksNum ticksNum) {
this.ticksNum = ticksNum;
}
}
static class TicksNum{
int num = 100;
public void sellTick() {
num -= 1;
}
public int getNum() {
return num;
}
}
}
4. notify()、wait()的实现
public class DemoThreadConnection {
public static void main(String[] args) {
/*
TODO:
由于同步代码块中,是对当前代码进行加锁操作,线程如果要运行,必须要获取全局唯一的一把锁
那么从运行结果看,线程之间执行是没有顺序的,同一个线程可能被执行多次
那么如果想要实现多个线程之间相互执行,如何操作,并且同步代码块是以代码块加锁,能否使用线程阻塞方式
如果一个线程执行到某个条件时,可以通过调用方法对当前线程进行阻塞,同时唤醒其他被阻塞的线程
TODO 注意:
① synchronized 也可以对当前 成员方法 进行加锁,加锁的是当前对象
② synchronized 也可以对当前 静态方法 进行加锁,加锁的是当前方法所属的类 => 实现 ???
③ notify 作用是从因为当前对象被锁定的线程中唤醒一个线程
④ wait 作用是从当前对象中的当前线程进行等待阻塞,如果要被唤醒,那么必须有一个线程使用 notify 来进行
⑤ notify和wait必须在synchronized修饰的代码块或者方法中才能存在
⑥ notify和wait是属于顶级父类Object中的方法
⑦ notify和wait 调用的对象,必须是被加锁的
⑧ 当代码中先wait之后再notify代码无法完整执行,暂停了..
原因:
*/
TickRunnable tickRunnable = new TickRunnable(100);
new Thread(tickRunnable,"销售员1").start();
new Thread(tickRunnable,"销售员2").start();
TickRunError tickRunError = new TickRunError(new TicksNum());
new Thread(tickRunError, "销售员1").start();
new Thread(tickRunError, "销售员2").start();
}
static class TickRunError implements Runnable {
TicksNum tickNum;
public TickRunError(TicksNum tickNum) {
this.tickNum = tickNum;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// TODO 对tickNum对象进行上锁
synchronized (tickNum) {
if (tickNum.getNum() > 0) {
sell();
} else {
break;
}
}
}
}
//TODO 通过notify、wait方法实现了两个售票员的以此售票
public void sell() {
System.out.println("当前售票员:" + Thread.currentThread().getName() + "正在销售第" + tickNum.getNum()+ "票");
tickNum.sellTick();
try {
tickNum.notify(); // 唤醒其他线程
// tickNum.notifyAll(); // 唤醒所有线程
tickNum.wait(); // 对当前的对象进行阻塞
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
static class TicksNum {
static int num = 100;
public void sellTick() {
num -= 1;
}
public int getNum() {
return num;
}
}
static class TickRunnable implements Runnable {
int tickNum;
public TickRunnable(int tickNum) {
this.tickNum = tickNum;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// TODO 针对TicksNumObject使用加锁操作 表示在同一时刻 有且只能使用一次该类
if (tickNum > 0) {
sell();
} else {
break;
}
}
}
//TODO synchronized 对当前 成员方法 进行加锁,
// 加锁的是当前对象;tickRunnable对象是全局唯一的,故此可以实现同步操作
public synchronized void sell() {
System.out.println("当前售票员:" + Thread.currentThread().getName() + "正在销售第" + tickNum + "票");
tickNum -= 1;
try {
// this.notify(); // 唤醒其他线程
this.notifyAll(); // 唤醒所有线程
this.wait(); // 对当前的对象进行阻塞
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
5. lock()的实现
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class DemoLockAndUnlock {
public static void main(String[] args) {
TickLock tickLock = new TickLock(100, new ReentrantLock());
new Thread(tickLock,"销售员1").start();
new Thread(tickLock,"销售员2").start();
}
static class TickLock implements Runnable {
int tickNum;
Lock lock;
public TickLock(int tickNum, Lock lock) {
this.tickNum = tickNum;
this.lock = lock;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// TODO 相当于在当前位置添加了一个 synchronized 同步锁
lock.lock();
if (tickNum > 0) {
sell();
} else {
break;
}
lock.unlock();
}
}
public void sell() {
System.out.println("当前售票员:" + Thread.currentThread().getName() + "正在销售第" + tickNum + "票");
tickNum -= 1;
}
}
}