📋 个人简介
- 💖 作者简介:大家好,我是阿牛,全栈领域优质创作者。😜
- 📝 个人主页:馆主阿牛🔥
- 🎉 支持我:点赞👍+收藏⭐️+留言📝
- 📣 系列专栏:java 小白到高手的蜕变🍁
- 💬格言:要成为光,因为有怕黑的人!🔥
目录
前言
上节我们写了一个比较经典的多窗口卖票案例,但我们最后发现他有重票和错票的情况,这是由于线程的不安全造成的,本节我将总结一下用同步的方式来处理线程安全问题!主要就是synchronized这个关键字。
问题概述
创建三个窗口卖票,总票数为100张。
1.问题:卖票过程中,出现了重票、错票﹣->出现了线程的安全问题
2.问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来。
3.如何解决:当一个线程 a 在操 ticket 的时候,其他线程不能参与进来。直到线程 a 操作完成后,其他线程才可以开始操作 ticket 。这种情況即使线程 a 出现了阻塞,也不能被改变。
同步代码块方式解决线程安全问题
synchronized(同步监视器){
//需要被同步的代码
}
说明:
1.操作共享数据的代码,即为需要被同步的代码
2.共享数据:多个线程共同繰作的变量。比如: ticket 就是共享数据。
3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。要求:多个线程必须要共用同一把锁。
4. 在实现 Runnable 接口创建多线程的方式中,我们可考虑使用 this 充当同步监视器。
注:当然为了方便,这个锁我们一般可以用this(还要看是继承Thread还是实现Runnable方式,继承Thread会有多个对象,此时this作为锁不唯一)、类名.class。
这里在说明一下类名.class。不是说这个锁是任何一个类的对象吗,其实,在java中,类也是一个对象。在 Java 中,每个 class 都有一个相应的 Class 对
象。也就是说,当我们编写一个类,编译完成后,在生成的.class 文件中,就会产生一个 Class 对象,用于表示这个类的类型信息。
其实任何一个类,都会有一个 Class 对象于这个类对像,在这个 Class 对象中,保存着实例化该类时所需要的基本信息, 类名.class 其实返回的是一个类的 Class 对象。
同步代码块解决继承Thread类的线程安全问题
public class SellTickets {
public static void main(String[] args) {
MyTh t1 = new MyTh();
MyTh t2 = new MyTh();
MyTh t3 = new MyTh();
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
class MyTh extends Thread{
private static int tickets = 100; //这里总票数一定要是静态的(static),因为多个线程要共享这一个变量!
//private static Object obj = new Object(); //可以作为锁
@Override
public void run() {
while(true){
// synchronized (obj)
// 或者
synchronized (MyTh.class){ //这里锁不能用this,有三个对象,this作为锁不是共享的
if(tickets>0){
System.out.println(this.getName() + "-票号为:" + tickets);
tickets--;
}else{
break;
}
}
}
}
}
可以看到此时结果无重票。
同步代码块解决实现Runnable接口的线程安全问题
package 多线程;
public class SellTickets {
public static void main(String[] args) {
MyTh m = new MyTh();
Thread t1 = new Thread(m);
Thread t2 = new Thread(m);
Thread t3 = new Thread(m);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
class MyTh implements Runnable{
private int tickets = 100; //这里总票数不必一定要是静态的(static),因为我们是实现的Runnable接口,最后只创建一个对象
//private static Object obj = new Object(); //可以作为锁
@Override
public void run() {
while(true){
// synchronized (obj)
// synchronized (MyTh.class)
synchronized (this){ //此时当前类的对象唯一,可以用this作为锁
if(tickets>0){
System.out.println(Thread.currentThread().getName() + "-票号为:" + tickets);
tickets--;
}else{
break;
}
}
}
}
}
同步方法解决线程安全问题。
将你要进行同步的代码放到同步方法里就行。同步方法要用synchronized修饰!
关于同步方法的总结:
1.同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
2.非静态的同步方法,同步监视器是: this ;静态的同步方法,同步监视器是:当前类本身。
同步方法解决继承Thread类的线程安全问题
public class SellTickets {
public static void main(String[] args) {
MyTh t1 = new MyTh();
MyTh t2 = new MyTh();
MyTh t3 = new MyTh();
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
class MyTh extends Thread{
private static int tickets = 100;
@Override
public void run() {
while(true){
Sell();
if(tickets<=0){
break;
}
}
}
public static synchronized void Sell(){ //此时,同步方法可以必须是静态的,同步监视器(锁)是MyTh.class(static方法里面不能有this)。因为锁不能为this。
if(tickets>0){
System.out.println(Thread.currentThread().getName() + "-票号为:" + tickets);
tickets--;
}
}
}
同步方法解决实现Runnable接口的线程安全问题
public class SellTickets {
public static void main(String[] args) {
MyTh m = new MyTh();
Thread t1 = new Thread(m);
Thread t2 = new Thread(m);
Thread t3 = new Thread(m);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
class MyTh implements Runnable{
private int tickets = 100; //这里总票数不必一定要是静态的(static),因为我们是实现的Runnable接口,最后只创建一个对象
@Override
public void run() {
while(true){
Sell();
if(tickets<=0){
break;
}
}
}
public synchronized void Sell(){ //此时,同步方法可以是非静态的,同步监视器(锁)是this
if(tickets>0){
System.out.println(Thread.currentThread().getName() + "-票号为:" + tickets);
tickets--;
}
}
}
结语
如果你觉得博主写的还不错的话,可以关注一下当前专栏,博主会更完这个系列的哦!也欢迎订阅博主的其他好的专栏。
🏰系列专栏
👉软磨 css
👉硬泡 javascript
👉flask框架快速入门