Java多线程--同步机制解决线程安全问题方式二:同步方法

文章目录

  • 一、同步方法
    • (1)同步方法--案例1
      • 1、案例1
      • 2、案例1之同步监视器
    • (2)同步方法--案例2
      • 1、案例2之同步监视器的问题
      • 2、案例2的补充说明
  • 二、代码及重要说明
    • (1)代码
    • (2)重要说明

一、同步方法

同步方法:synchronized 关键字直接修饰方法,表示同一时刻只有一个线程能进入这个方法,其他线程在外面等着

️格式:

public synchronized void method(){
    可能会产生线程安全问题的代码
}

(1)同步方法–案例1

1、案例1

还是拿这个例子来说,方式一实现Runnable接口,如下:

代码

package yuyi02;

/**
 * ClassName: WindowTset2
 * Package: yuyi02
 * Description:
 *		使用同步方法解决实现Runnable接口的线程安全问题
 * @Author 雨翼轻尘
 * @Create 2024/1/30 0030 9:52
 */
public class WindowTest2 {
    public static void main(String[] args) {
        //3.创建当前实现类的对象
        SaleTicket2 s=new SaleTicket2();

        //4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的实例
        Thread t1 = new Thread(s);
        Thread t2 = new Thread(s);
        Thread t3 = new Thread(s);

        //给三个线程起名字
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        //5.通过Thread类的实例调用start():1.启动线程 2.调用当前线程的run()。
        t1.start();
        t2.start();
        t3.start();
    }
}

class SaleTicket2 implements Runnable{   //卖票    1.创建一个实现Runnable接口的类(实现类)
    int ticket=100;
    @Override
    public void run() { //2.实现接口中的抽象方法run()方法
        while (true){
            if(ticket>0){   //如果票数大于0就可以售票
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //哪个窗口卖票了,票卖了多少
                System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);  //最开始票号为100
                ticket--;
            }else{
                break;
            }
        }
    }
}

输出结果(部分)

Java多线程--同步机制解决线程安全问题方式二:同步方法_第1张图片

可以看到,出现了重票和错票


现在来解决这个安全问题。

操作ticket的代码:

Java多线程--同步机制解决线程安全问题方式二:同步方法_第2张图片

现在将他们完全声明在一个方法show()当中,然后在while里面调用show()方法。比如:

Java多线程--同步机制解决线程安全问题方式二:同步方法_第3张图片

我们可以将while里面的show()synchronized包裹,就是同步代码块的方式,如下:

public void run() { //2.实现接口中的抽象方法run()方法
    while (true){
        synchronized (this) {
            show();
        }
    }
}

当然也可以将show方法声明为同步方法

现在这里有点错误,就是break的问题。之前是在while里面写的,现在将if-else从while里面抽出来了,所以break就不行了。
将break直接删掉吗?不行,这样的话程序就不能自己结束了。如下:

Java多线程--同步机制解决线程安全问题方式二:同步方法_第4张图片

我们可以声明一个变量isFlag,初始化为true。如下:

Java多线程--同步机制解决线程安全问题方式二:同步方法_第5张图片

代码

public class WindowTest2 {
    public static void main(String[] args) {
        //3.创建当前实现类的对象
        SaleTicket2 s=new SaleTicket2();

        //4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的实例
        Thread t1 = new Thread(s);
        Thread t2 = new Thread(s);
        Thread t3 = new Thread(s);

        //给三个线程起名字
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        //5.通过Thread类的实例调用start():1.启动线程 2.调用当前线程的run()。
        t1.start();
        t2.start();
        t3.start();
    }
}

class SaleTicket2 implements Runnable{   //卖票    1.创建一个实现Runnable接口的类(实现类)
    int ticket=100;
    boolean isFlag=true;
    public void show(){
        if(ticket>0){   //如果票数大于0就可以售票
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //哪个窗口卖票了,票卖了多少
            System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);  //最开始票号为100
            ticket--;
        }else{
            isFlag=false;
        }
    }
    @Override
    public void run() { //2.实现接口中的抽象方法run()方法
        while (isFlag){
            synchronized (this) {
                show();
            }
        }
    }
}

输出结果(部分)

Java多线程--同步机制解决线程安全问题方式二:同步方法_第6张图片

但是现在还是用的“同步代码块”来解决问题。


现在操作ticket的代码完全写在了show()方法里面,那么将这个show方法加一个同步即可,就是直接加一个synchronized,如下:

Java多线程--同步机制解决线程安全问题方式二:同步方法_第7张图片

代码

public class WindowTest2 {
    public static void main(String[] args) {
        //3.创建当前实现类的对象
        SaleTicket2 s=new SaleTicket2();

        //4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的实例
        Thread t1 = new Thread(s);
        Thread t2 = new Thread(s);
        Thread t3 = new Thread(s);

        //给三个线程起名字
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        //5.通过Thread类的实例调用start():1.启动线程 2.调用当前线程的run()。
        t1.start();
        t2.start();
        t3.start();
    }
}

class SaleTicket2 implements Runnable{   //卖票    1.创建一个实现Runnable接口的类(实现类)
    int ticket=100;
    boolean isFlag=true;
    public synchronized void show(){ //此时的同步监视器就是:this 。此题目中是s,是唯一的,线程安全
        if(ticket>0){   //如果票数大于0就可以售票
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //哪个窗口卖票了,票卖了多少
            System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);  //最开始票号为100
            ticket--;
        }else{
            isFlag=false;
        }
    }
    @Override
    public void run() { //2.实现接口中的抽象方法run()方法
        while (isFlag){
            show();
        }
    }
}

输出结果(部分)

Java多线程--同步机制解决线程安全问题方式二:同步方法_第8张图片

可以。


2、案例1之同步监视器

上面的show()方法中,我们没有显示得去写同步监视器,其实它是默认的。

针对于这个同步方法,若这个方法是非静态的,那么这个同步监视器默认的就是this

Java多线程--同步机制解决线程安全问题方式二:同步方法_第9张图片

这个this是改不了的,我们只能考虑这个this是不是唯一的。

此时this是唯一的吗?

是唯一的。因为现在实在当前实现方式里面写的,类SaleTicket2的对象只造了一个,并且被多个线程所共用。所以调用方法的时候,只有唯一的对象s

如下:

Java多线程--同步机制解决线程安全问题方式二:同步方法_第10张图片

所以线程是安全的,没有问题。

(2)同步方法–案例2

1、案例2之同步监视器的问题

还是拿这个例子来说,方式二继承Thread类,如下:

代码

package yuyi02;

/**
 * ClassName: WindowTest3
 * Package: yuyi02
 * Description:
 *      使用同步方法解决继承Thread类的线程安全问题
 * @Author 雨翼轻尘
 * @Create 2024/1/30 0030 11:03
 */
public class WindowTest3 {
    public static void main(String[] args) {
        //3.创建3个窗口  创建当前Thread的子类的对象
        Window w1=new Window();
        Window w2=new Window();
        Window w3=new Window();

        //命名
        w1.setName("窗口1");
        w2.setName("窗口2");
        w3.setName("窗口3");

        //4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()方法
        w1.start();
        w2.start();
        w3.start();
    }
}

class Window extends Thread{    //卖票  1.创建一个继承于Thread类的子类
    //票
    static int ticket=100;

    //2.重写Thread类的run() —>将此线程要执行的操作,声明在此方法体中
    @Override
    public void run() {
        while (true){
            if(ticket>0){   //如果票数大于0就可以售票
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //哪个窗口卖票了,票卖了多少
                System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);  //最开始票号为100
                ticket--;
            }else{
                break;
            }
        }
    }
}

输出结果(部分)

Java多线程--同步机制解决线程安全问题方式二:同步方法_第11张图片

出现了重票的问题。


现在来解决这个安全问题。

操作ticket的代码:

Java多线程--同步机制解决线程安全问题方式二:同步方法_第12张图片

将上述操作ticket的代码放在方法show1()中,如下:

Java多线程--同步机制解决线程安全问题方式二:同步方法_第13张图片

跟上一个案例类似,将break去掉,加一个isFlag1,如下:

Java多线程--同步机制解决线程安全问题方式二:同步方法_第14张图片

当然,isFlag都要共用一个,所以需要加上static,如下:

Java多线程--同步机制解决线程安全问题方式二:同步方法_第15张图片


代码

public class WindowTest3 {
    public static void main(String[] args) {
        //3.创建3个窗口  创建当前Thread的子类的对象
        Window w1=new Window();
        Window w2=new Window();
        Window w3=new Window();

        //命名
        w1.setName("窗口1");
        w2.setName("窗口2");
        w3.setName("窗口3");

        //4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()方法
        w1.start();
        w2.start();
        w3.start();
    }
}

class Window extends Thread{    //卖票  1.创建一个继承于Thread类的子类
    //票
    static int ticket=100;
    static boolean isFlag1=true;

    //2.重写Thread类的run() —>将此线程要执行的操作,声明在此方法体中
    @Override
    public void run() {
        while (isFlag1){
            show1();
        }
    }

    public void show1(){
        if(ticket>0){   //如果票数大于0就可以售票
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //哪个窗口卖票了,票卖了多少
            System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);  //最开始票号为100
            ticket--;
        }else{
            isFlag1=false;
        }
    }
}

输出结果(部分)

现在还没有解决线程安全问题,所以输出结果还是有重票的,如下:

Java多线程--同步机制解决线程安全问题方式二:同步方法_第16张图片


现在我们直接给show1()方法加上synchronized,可以吗?

如下:

Java多线程--同步机制解决线程安全问题方式二:同步方法_第17张图片

代码

public class WindowTest3 {
    public static void main(String[] args) {
        //3.创建3个窗口  创建当前Thread的子类的对象
        Window w1=new Window();
        Window w2=new Window();
        Window w3=new Window();

        //命名
        w1.setName("窗口1");
        w2.setName("窗口2");
        w3.setName("窗口3");

        //4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()方法
        w1.start();
        w2.start();
        w3.start();
    }
}

class Window extends Thread{    //卖票  1.创建一个继承于Thread类的子类
    //票
    static int ticket=100;
    static boolean isFlag1=true;

    //2.重写Thread类的run() —>将此线程要执行的操作,声明在此方法体中
    @Override
    public void run() {
        while (isFlag1){
            show1();
        }
    }

    public synchronized void show1(){   //非静态同步方法,此时同步监视器就是this,此问题中的this有:w1,w2,w3
        if(ticket>0){   //如果票数大于0就可以售票
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //哪个窗口卖票了,票卖了多少
            System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);  //最开始票号为100
            ticket--;
        }else{
            isFlag1=false;
        }
    }
}

输出结果(部分)

Java多线程--同步机制解决线程安全问题方式二:同步方法_第18张图片

此时是非静态同步方法,同步监视器就是this,此问题中的this有:w1,w2,w3(当前类的对象造了三个)。所以肯定不行


2、案例2的补充说明

上面那个既然不行,那该怎么办呢?

show()方法改成静态的吗?如下:

Java多线程--同步机制解决线程安全问题方式二:同步方法_第19张图片

代码

public class WindowTest3 {
    public static void main(String[] args) {
        //3.创建3个窗口  创建当前Thread的子类的对象
        Window w1 = new Window();
        Window w2 = new Window();
        Window w3 = new Window();

        //命名
        w1.setName("窗口1");
        w2.setName("窗口2");
        w3.setName("窗口3");

        //4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()方法
        w1.start();
        w2.start();
        w3.start();
    }
}

class Window extends Thread {    //卖票  1.创建一个继承于Thread类的子类
    //票
    static int ticket = 100;
    static boolean isFlag1 = true;

    //2.重写Thread类的run() —>将此线程要执行的操作,声明在此方法体中
    @Override
    public void run() {
        while (isFlag1) {
            show1();
        }
    }
    
    public static synchronized void show1() {   //静态方法的同步监视器是:当前类,就是Window.class
        if (ticket > 0) {   //如果票数大于0就可以售票
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //哪个窗口卖票了,票卖了多少
            System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);  //最开始票号为100
            ticket--;
        } else {
            isFlag1 = false;
        }
    }

}

输出结果(部分)

Java多线程--同步机制解决线程安全问题方式二:同步方法_第20张图片

现在这个案例加上static是可行的。

静态方法的同步监视器是当前类本身,就是Window.class(这里是一个对象,一个值,不是类),是唯一的。所以现在是安全的。

说明

这个方法能不能改成静态的,需要看具体的问题。适合就可以改,不适合就不要改了。

若有的方法就是一个实例方法,里面要用实例变量,那就不适合改,同步方法就不靠谱了。

所以这里不要刻意去满足同步方法让它去达到我们的要求(不要为了线程安全,去特意将方法改为静态的)。

有的时候这个方法就不适合加上静态,同步方法就不适合去做了,就不要使用同步方法了。

那我们就主动将操作ticket的代码用synchronized包裹一下,然后指定一个同步监视器即可。

二、代码及重要说明

(1)代码

①【使用同步方法解决实现Runnable接口的线程安全问题】

代码

package yuyi02;

/**
 * ClassName: WindowTset2
 * Package: yuyi02
 * Description:
 *      使用同步方法解决实现Runnable接口的线程安全问题
 * @Author 雨翼轻尘
 * @Create 2024/1/30 0030 9:52
 */
public class WindowTest2 {
    public static void main(String[] args) {
        //3.创建当前实现类的对象
        SaleTicket2 s=new SaleTicket2();

        //4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的实例
        Thread t1 = new Thread(s);
        Thread t2 = new Thread(s);
        Thread t3 = new Thread(s);

        //给三个线程起名字
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        //5.通过Thread类的实例调用start():1.启动线程 2.调用当前线程的run()。
        t1.start();
        t2.start();
        t3.start();
    }
}

class SaleTicket2 implements Runnable{   //卖票    1.创建一个实现Runnable接口的类(实现类)
    int ticket=100;
    boolean isFlag=true;

    public synchronized void show(){    //此时的同步监视器就是:this 。此题目中是s,是唯一的,线程安全
        if(ticket>0){   //如果票数大于0就可以售票
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //哪个窗口卖票了,票卖了多少
            System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);  //最开始票号为100
            ticket--;
        }else{
            isFlag=false;
        }
    }
    @Override
    public void run() { //2.实现接口中的抽象方法run()方法
        while (isFlag){
            show();
        }
    }
}

②【使用同步方法解决继承Thread类的线程安全问题】

代码

package yuyi02;

/**
 * ClassName: WindowTest3
 * Package: yuyi02
 * Description:
 *      使用同步方法解决继承Thread类的线程安全问题
 * @Author 雨翼轻尘
 * @Create 2024/1/30 0030 11:03
 */
public class WindowTest3 {
    public static void main(String[] args) {
        //3.创建3个窗口  创建当前Thread的子类的对象
        Window w1 = new Window();
        Window w2 = new Window();
        Window w3 = new Window();

        //命名
        w1.setName("窗口1");
        w2.setName("窗口2");
        w3.setName("窗口3");

        //4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()方法
        w1.start();
        w2.start();
        w3.start();
    }
}

class Window extends Thread {    //卖票  1.创建一个继承于Thread类的子类
    //票
    static int ticket = 100;
    static boolean isFlag1 = true;

    //2.重写Thread类的run() —>将此线程要执行的操作,声明在此方法体中
    @Override
    public void run() {
        while (isFlag1) {
            show1();
        }
    }

    public static synchronized void show1() {   //非静态同步方法,此时同步监视器就是this,此问题中的this有:w1,w2,w3,线程仍然不安全,加一个static
        if (ticket > 0) {   //如果票数大于0就可以售票
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //哪个窗口卖票了,票卖了多少
            System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);  //最开始票号为100
            ticket--;
        } else {
            isFlag1 = false;
        }
    }

}

(2)重要说明

【同步方法】

️格式:

public synchronized void method(){
    可能会产生线程安全问题的代码
}

说明

  • 如果操作共享数据的代码(需要被同步的代码)完整的声明在了一个方法中,那么我们就可以将此方法声明为同步方法即可。
  • 非静态的同步方法,默认同步监视器是this静态的同步方法,默认同步监视器是当前类本身

☕注意

现在咱们线程一共说了这么几件事情,如下:

Java多线程--同步机制解决线程安全问题方式二:同步方法_第21张图片

下面来看一下这个关键字:synchronized(同步的)

  • 好处:解决了线程的安全问题
  • 弊端:在操作共享数据时,多线程其实是串行执行的,意味着性能低

卖票:三个线程来做,交互去执行。

当一个线程还没有操作完,其他线程也过来了,就会出现安全问题。

执行前面代码的时候,三个线程没有共享数据,同时执行也没有问题。

但是在执行共享数据的代码的时候,只能让一个线程进去,其他线程在外面等着。

也就是说,执行前面代码的时候,三个线程可以并发执行,但是在操作共享数据的时候,一定是串行的去执行,也就是只有一个线程可以进去执行。所以性能会差一点

你可能感兴趣的:(Java基础,java,多线程,同步机制解决线程安全问题方式二,同步方法)