解决线程安全-------Synchronized 同步代码块和同步方法

1.背景
例子:创建个窗口卖票,总票数为100张.使用实现Runnable接口的方式
*
* 1.问题:卖票过程中,出现了重票、错票 -->出现了线程的安全问题
* 2.问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。
* 3.如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,其他线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。

2.Java解决方案:同步机制
在Java中,我们通过同步机制,来解决线程的安全问题。

方式一:同步代码块
*
* synchronized(同步监视器){
* //需要被同步的代码/操作共享数据的代码
*
* }
* 说明:1.操作共享数据的代码,即为需要被同步的代码。 -->不能包含代码多了,也不能包含代码少了。
* 2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
* 3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。

  锁:是控制多个线程访问共享资源的一种方式,一般来说,一个锁能够防止多个线程同时访问共享资源。

* 要求:多个线程必须要共用同一把锁。
*
* 补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器。

 实现runnable接口的代码中使用synchronized同步代码块
package main.java.ThreadImplements;

/**
 * @Author lx
 * @Description 实现runnable接口的代码中使用synchronized同步代码块
 * @Date 9:11 2020/8/5
 * @Version
 */
class windowsSyn implements Runnable{

    private int ticket = 100;
    @Override
    public void run() {

            while (true) {
                //注意synchronized包含的代码块的范围不能少也不能多
                synchronized (this) {  //同步监视器(锁)可以使用this指代当前对象 WindowsIplementsSyn在此例中只创建了一个
                if (ticket > 0) {

                    try {
                        Thread.sleep(50); //等待时间
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("当前窗口号" + Thread.currentThread().getName() + ":" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}

public class WindowsIplementsSyn {
    public static void main(String[] args) {
        windowsSyn W1 = new windowsSyn(); //只创建了一个对象

        //多个线程共用一个对象
        Thread t1 = new Thread(W1);
        Thread t2 = new Thread(W1);
        Thread t3 = new Thread(W1);
        t1.start();
        t2.start();
        t3.start();
    }
}
继承Thread方式的代码中使用Synchronized同步代码块
package main.java.ThreadExtends;

/**
 * @Author lx
 * @Description 继承Thread方式的代码中使用Synchronized同步代码块
 * @Date 10:29 2020/8/5
 * @Version
 */
class windows extends Thread{

    private static int ticket = 100;        //将ticket设为静态全局变量 只加载一次
   @Override
    public void run() {
        while (true) {
            //  synchronized (this) {  //在这里不能用this 因为此例中创建了多个对象 每进循环一次都是不同的对象,线程安全问题依旧存在
            synchronized (window.class) { //多个线程必须使用同一把锁 window.class 只会加载一次

                if (ticket > 0) {
                    System.out.println("当前窗口号:" + Thread.currentThread().getName() + "还剩票数:" + ticket);
                    ticket--;
                } else
                    break;
            }
        }
    }
}
public class WindowsExtendsSyn {

    public static void main(String[] args) {

        //创建了多个对象
        windows w1 = new windows();
        windows w2 = new windows();
        windows w3 = new windows();
        w1.start();
        w2.start();
        w3.start();
    }
}


 

方式二:同步方法
* 如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。


* 关于同步方法的总结:
* 1. 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
* 2. 非静态的同步方法,同步监视器是:this
*  静态的同步方法,同步监视器是:当前类本身

1.实现Runnable接口方式的代码中使用Synchronized同步方法
package main.java.ThreadImplements;

/**
 * @Author lx
 * @Description  实现Runnable接口方式的代码中使用Synchronized同步方法
 * @Date 17:27 2020/8/5
 * @Version
 */
class windows2 implements Runnable{

    private int ticket = 100;
    @Override
    public void run() {

        while (true) {

            show();
            }
    }

    private synchronized void show(){
        if (ticket > 0) {

            try {
                Thread.sleep(50); //等待时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("当前窗口号" + Thread.currentThread().getName() + ":" + ticket);
            ticket--;
        }
    }
}

public class WindowImpMethod {
    public static void main(String[] args) {
        windows2 W1 = new windows2(); //只创建了一个对象

        //多个线程共用一个对象
        Thread t1 = new Thread(W1);
        Thread t2 = new Thread(W1);
        Thread t3 = new Thread(W1);
        t1.start();
        t2.start();
        t3.start();
    }
}

2.继承Thread方式的代码中使用Synchronized同步方法

package main.java.ThreadExtends;

/**
 * @Author  lx
 * @Description 继承Thread方式的代码中使用Synchronized同步方法
 * @Date 15:36 2020/8/5
 * @Version
 */

class windows1 extends Thread{

    private static int ticket = 100;        //将ticket设为静态全局变量 只加载一次
    @Override
    public void run() {
        while (true) {
            show(); //调用当前类的静态show方法
        }
    }

    private synchronized static void show(){    //静态的同步方法 同步监视器是:当前类本身
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("当前窗口号:" + Thread.currentThread().getName() + "还剩票数:" + ticket);
            ticket--;
        }
    }
}
public class WinExtendsMethod {
    public static void main(String[] args) {

        //创建了多个对象
        windows1 w1 = new windows1();
        windows1 w2 = new windows1();
        windows1 w3 = new windows1();
        w1.start();
        w2.start();
        w3.start();
    }
}


 
原文地址:https://www.cnblogs.com/lixia0604/p/13440021.html