欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Java 多线程(二)—— 线程的同步

程序员文章站 2023-08-26 08:21:26
上文创建多线程买票的例子中注释会出现错票、重票的问题,本文来讲讲如何解决此问题。本文例子:利用多线程模拟 3 个窗口卖票 上文创建多线程买票的例子中注释会出现错票、重票的问题,本文来讲讲如何解决此问题。本文例子:利用多线程模拟 3 个窗口卖票 实现Runnable接口 public class Te ......

实现runnable接口

public class testthread2 {
    public static void main(string [] args){
        window window=new window();
        thread thread1=new thread(window,"窗口一");
        thread thread2=new thread(window,"窗口二");
        thread thread3=new thread(window,"窗口三");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

class window implements  runnable{
    int ticket=50;
    @override
    public void run(){
        while (true){
            if(ticket > 0){
                try {
                    thread.currentthread().sleep(100);//模拟卖票需要一定的时间
                } catch (interruptedexception e) {
                    e.printstacktrace();
                }
                system.out.println(thread.currentthread().getname()+"售票,票号为:"+ticket--);
            }else {
                break;
            }
        }
    }
}

运行结果:

窗口二售票,票号为:13
窗口三售票,票号为:12
窗口一售票,票号为:11
窗口二售票,票号为:10
窗口一售票,票号为:10
窗口三售票,票号为:10
窗口三售票,票号为:9
窗口一售票,票号为:8
窗口二售票,票号为:7
窗口三售票,票号为:6
窗口一售票,票号为:5
窗口二售票,票号为:4
窗口三售票,票号为:3
窗口一售票,票号为:2
窗口二售票,票号为:1
窗口三售票,票号为:0
窗口一售票,票号为:-1

 

结果分析:这里出现了票数为0和负数还有重票的情况,这在现实生活中肯定是不存在的,那么为什么会出现这样的情况呢?

  当票号为10时:a线程、b线程、c线程同时进入到if(ticket > 0)的代码块中,a线程已经执行了打印输出语句,但是还没有做ticket--操作;
  这时b线程就开始执行了打印操作,那么就会出现两个线程打印票数一样,即卖的是同一张票
  当票号为1时:a线程、b线程,c线程同时进入到if(ticket > 0)的代码块中,a线程执行了打印语句,并且已经做完了ticket--操作,则此时ticket=0;
  b线程再打印时就出现了0的情况,同理c线程打印就会出现-1的情况。

解决办法:即我们不能同时让超过两个以上的线程进入到 if(ticket > 0)的代码块中,不然就会出现上述的错误。必须让一个线程操作共享数据完毕以后,其他线程才有机会参与共享数据的操作。我们可以通过以下两个办法来解决:

  1、使用 同步代码块

  2、使用 同步方法

使用 同步代码块

synchronized(同步监视器){
      //需要被同步的代码块(即为操作共享数据的代码)
}

  同步监视器:由任意一个类的对象来充当,哪个线程获取此监视器,谁就执行大括号里被同步的代码。俗称:锁

  要求:1、所有的线程必须公用同一把锁!不能相对于线程是变化的对象;

        2、并且只需锁住操作共享数据的代码,锁多了或少了都不行;

 

实例:

1、实现的方式

public class testwindow {
    public static void main(string [] args){
        window1 window=new window1();
        thread thread1=new thread(window,"窗口一");
        thread thread2=new thread(window,"窗口二");
        thread thread3=new thread(window,"窗口三");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

class window1 implements  runnable{
    int ticket=100;//共享数据
    @override
    public void  run(){
        while (true){
            synchronized (this){//this表示当前对象,此时表示创建的 window
                if(ticket > 0){
                    try {
                        //模拟卖票需要一定的时间
                        thread.sleep(100);
                    } catch (interruptedexception e) {
                        e.printstacktrace();
                    }
                    system.out.println(thread.currentthread().getname()+"售票,票号为:"+ticket--);
                }
            }
        }
    }
}

注意:在实现的方式中,考虑同步的话,可以使用this充当锁,但在继承的方式中,会创建多个对象,慎用this

2、继承的方式

public class testwindow1 {
    public static void main(string [] args){
        window2 window1=new window2();
        window2 window2=new window2();
        window1.start();
        window2.start();
    }
}


class window2 extends thread{
    static int ticket=100;//共享数据;注意声明为 static,表示几个窗口共享
    static object object=new object();//用static 可以表示唯一
    @override
    public void  run(){
        while (true){
            //synchronized (this){//this表示当前对象,此时表示创建的 window1和window2
            synchronized (object){//锁必须是唯一,不能每个线程都使用自己的一把锁
                if(ticket > 0){
                    try {
                        thread.sleep(200);
                    } catch (interruptedexception e) {
                        e.printstacktrace();
                    }
                    system.out.println(thread.currentthread().getname()+"售票,票号为:"+ticket--);
                }
            }
        }
    }
}

注意:1、继承的方式会创建多个实例,所以共享资源需要用static来修饰,表示共享

      2、继承的方式会创建多个实例,所以 this 表示不同的实例对象,这里表示widow1和window2,所以不能使用this当锁,此时可以定义一个 static 修饰的对象当锁

 

使用 同步方法

语法:即用  synchronized  关键字修饰方法

将操作共享数据的方法声明为synchronized。即此方法为同步方法,能够保证当其中一个线程执行此方法时,其他线程再外等待直至此线程执行完此方法。
注意:同步方法的锁:this

实例:

1、实现的方式

public class testwindow2 {
    public static void main(string [] args){
        window3 window=new window3();
        thread thread1=new thread(window,"窗口一");
        thread thread2=new thread(window,"窗口二");
        thread thread3=new thread(window,"窗口三");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

class window3 implements  runnable{
    int ticket=100;//共享数据
    @override
    public void  run(){
        while (true){
            show();
        }
    }

    public synchronized void show(){//this充当锁,此时表示创建的 window;
        // 如果用继承的方式,使用同步方法,这里表示创建的 window1和window2,继承的方式不要使用同步方法
        if(ticket > 0){
            try {
                thread.currentthread().sleep(100);
            } catch (interruptedexception e) {
                e.printstacktrace();
            }
            system.out.println(thread.currentthread().getname()+"售票,票号为:"+ticket--);
        }
    }
}

 

注意:1、synchronized 的锁为this,这里表示创建的对象实例window;

     2、继承的时候t this 表示创建的window1和window2,继承的方式不要使用同步方法。