线程的同步

举报
yd_45102651 发表于 2020/07/22 21:08:38 2020/07/22
【摘要】 线程的同步线程的安全问题多个线程执行的不确定性引起执行的结果的不稳定性多个线程对数据的共享,会造成操作的不完整性、会破坏数据(例如窗口买票问题,多个窗口对票数进行共享,会出现两个窗口卖号码相同的票给不同的人)通过同步机制解决线程安全问题方法一:同步代码块格式synchronized(同步监视器){需要被同步的代码}举例说明classThreadimplementsRunnabl...

线程的同步

线程的安全问题

  • 多个线程执行的不确定性引起执行的结果的不稳定性
  • 多个线程对数据的共享,会造成操作的不完整性、会破坏数据(例如窗口买票问题,多个窗口对票数进行共享,会出现两个窗口卖号码相同的票给不同的人)

通过同步机制解决线程安全问题

方法一:同步代码块

格式

synchronized(同步监视器){

    需要被同步的代码

    }

举例说明

class Thread implements Runnable{

    private Object obj = new Object();

    public void run() {
        //使用类对象充当锁
        synchronized(obj){
        .......
        }
    }
}

说明

  • 操作共享数据的代码即为需要被同步的代码
    • 不能多包含代码,也不能少包含代码
  • 共享数据:多个线程共同操作的变量
  • 同步监视器:俗称锁
    • 任何一个类的对象都可以来充当锁
    • 要求多个线程必须共用同一把锁
    • 在实现Runnable接口创建多线程的方式中,考虑使用this充当同步监视器
    • 在继承Thread类创建多线程的方式中,慎用this来充当同步监视器,考虑使用当前类来充当同步监视器

特点

  • 好处:解决线程的安全问题
  • 局限性:操作同步代码时,只能有一个线程参与,其他线程等待。相当于一个单线程的过程,效率低

代码实现

实现Runnable接口创建多线程的方式
/**
 * 创建三个窗口买票,票数100张:使用实现Runnable接口的方式实现的
 */
class WindowThread implements Runnable{

    private int ticket = 100;
    // private Object obj = new Object();

    public void run() {
        while (true) {
            //此时this:唯一的WindowThread对象
            synchronized(this){// 方式二:synchronized(obj){
                if (ticket > 0) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread(). getName() + ":" + "买票,票号为" + ticket);
                    ticket--;
                }else{
                    break;
                }
            }
        }
    }
}

public class Test1 {

    public static void main(String[] args) {
        WindowThread window = new WindowThread();

        Thread w1 = new Thread(window);
        Thread w2 = new Thread(window);
        Thread w3 = new Thread(window);

        w1.setName("窗口1");
        w1.start();
        w2.setName("窗口2");
        w2.start();
        w3.setName("窗口3");
        w3.start();
    }
}
继承Thread类创建多线程的方式
class Window extends Thread {
    // 大家公用数据,只有100张票
    private static int ticket = 100;
    private static Object obj = new Object();
    public void run() {
        while (true) {
            //方式二
            synchronized(Window.class){
                // 方式一:synchronized(obj){
                //synchronized(this)错误的,此时this代表着三个对象
                if(ticket > 0){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(getName() + ":" + "买票,票号为" + ticket);
                    ticket--;
                }else{
                    break;
                }
            }

        }
    }
}

public class Test2 {
    public static void main(String[] args) {
        Window w1 = new Window();
        Window w2 = new Window();
        Window w3 = new Window();

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

        w1.start();
        w2.start();
        w3.start();
    }
}

同步方法

如果操作共享数据的代码完整的声明在一个方法中,就可以将此方法声明同步的

格式

利用synchronized 修饰方法

public synchronized void XXX(){

}

public static synchronized void XXX(){

}

说明

  • synchronized修饰方法时锁定的是调用该方法的对象
  • 同步方法仍然涉及到同步监视器,只是不需要我们显示的声明
  • 非静态的同步方法,同步监视器是this
  • 静态的同步方法,同步监视器是当前类本身(Window.class)

代码实现

实现Runnable接口创建多线程的方式

非静态同步方法,调用this

class WindowThread3 implements Runnable{

    private int ticket = 100;
    private static boolean isFlag = true;
    // private Object obj = new Object();

    public void run() {
        while (isFlag) {
            show();
        }
    }

    public synchronized void show(){//同步监视器:this
        if (ticket > 0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread(). getName() + ":" + "买票,票号为" + ticket);
            ticket--;
        }else{
            isFlag = false;
        }
    }
}

public class Test3 {
    public static void main(String[] args) {
        WindowThread3 window = new WindowThread3();

        Thread w1 = new Thread(window);
        Thread w2 = new Thread(window);
        Thread w3 = new Thread(window);

        w1.setName("窗口1");
        w1.start();
        w2.setName("窗口2");
        w2.start();
        w3.setName("窗口3");
        w3.start();
    }
}
继承Thread类创建多线程的方式

静态同步方法,调用当前类本身

class Window4 extends Thread{
    private static int ticket = 100;
    private static boolean isFlag = true;

    @Override
    public void run() {
        while(isFlag){
            show();
        }
    }

    public static synchronized void show(){
        //同步监视器:Window.class
        if(ticket > 0){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread(). getName() + ":" + "买票,票号为" + ticket);
            ticket--;
        }else{
            isFlag = false;
        }
    }
}

public class Test4 {
    public static void main(String[] args) {
        Window4 w1 = new Window4();
        Window4 w2 = new Window4();
        Window4 w3 = new Window4();

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

        w1.start();
        w2.start();
        w3.start();
    }
}

通过Lock(锁)解决线程安全问题

步骤

  1. 实例化ReentrantLock

    private ReentrantLock lock = new ReentrantLock(true);

    • true代表公平
    • 不填默认为false
  2. 调用锁的方法

    在可能会出现安全问题代码前调用Lock接口中的方法Lock获取锁
    lock.lock();

  3. 调用解锁的方法

    lock.unlock();

注意:其中调用lock()方法和unlock()方法时要用try()finally()包住

代码实现

class Window5 implements Runnable {

    private int ticket = 100;

    //1.实例化ReentrantLock
    private ReentrantLock lock = new ReentrantLock(true);

    public void run() {
        while (true) {
            try{
                //2.调用锁定的方法:lock()
                lock.lock();

                if (ticket > 0) {

                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() +  "卖票" + ":" + "票号为" + ticket);
                    ticket--;
                }else{
                    break;
                }
            }finally{
                //3.调用解锁的方法:unlock()
                lock.unlock();
            }
        }

    }

}

public class Test5 {
    public static void main(String[] args) {
        Window5 window = new Window5();

        Thread t1 = new Thread(window);
        Thread t2 = new Thread(window);
        Thread t3 = new Thread(window);

        t1.setName("窗口1:");
        t2.setName("窗口2:");
        t3.setName("窗口3:");

        t1.start();
        t2.start();
        t3.start();
    }
}

synchronized和Lock的异同

  • synchronized机制在执行完相应的代码逻辑后自动释放同步监视器
  • Lock需要手动的启动同步(lock),同时结束同步也需要手动的实现(unlock)

  • 都可以解决线程安全问题

释放锁与不释放锁的操作

释放锁的操作

  • 当前线程的同步方法、同步代码块执行结束
  • 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
  • 当前线程在同步代码块、同步方法中遇到了break、return终止了该代码块、方法的继续执行
  • 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁

不释放锁的操作

  • 线程在执行同步代码块或同步方法时,程序调用了Thread.sleep()或Thread.yield()方法暂停当前线程的执行
  • 线程在执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)
    • 尽量避免使用suspend()(挂起)和resume()(继续执行)来控制线程

使用顺序

Lock--->同步代码块--->同步方法

死锁

  • 不同线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
  • 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
  • 使用同步时,避免出现死锁
  • 避免
    • 专门的算法
    • 尽量减少同步资源的定义
    • 尽量避免嵌套同步

文章来源: www.cnblogs.com,作者:南煎丸子,版权归原作者所有,如需转载,请联系作者。

原文链接:https://www.cnblogs.com/CrabDumplings/p/13357235.html

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。