线程同步

Posted 小布丁value

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线程同步相关的知识,希望对你有一定的参考价值。

  • 问题的提出


例 题
模拟火车站售票程序,开启三个窗口售票。

public class Rwindow implements  Runnable {
    private int ticket =3;
    @Override
    public void run() {
        while(ticket>0){
            System.out.println("买票号"+ ticket);
            ticket--;
        }
    }
}
class test1{
    public static void main(String[] args) {
        Rwindow t = new Rwindow();
        Thread thread = new Thread(t);
        Thread thread1 = new Thread(t);
        Thread thread2= new Thread(t);

        thread.setName("窗口1");
        thread.setName("窗2");
        thread.setName("窗3");
        thread.start();
        thread1.start();
        thread2.start();

    }
}

理想状态

极端状态 1 2 3 都sleep了进入阻塞状态

安全问题

由此就导致多线程出现了安全问题
.问题的原因:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有
执行完,另一个线程参与进来执行。导致共享数据的错误。

解决办法:

对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以 参与执行。

1.synchronized同步代码块:

synchronized(对象){
            //需要被同步的代码块
        }`

说明:
1.操作共享数据的代码,即为需要被同步的代码
2.共享数据:多个线程共同操作的变量。比如: ticket就是共享数据。
3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
要求:多个线程必须要共用同一把锁。这一点非常重要

Runnable()



Runnable方式可以把synchronized里的关键字换成this
因为只有一个对象,
但是继承不可以,因为有三个对象





synchronized里的关键字也可以换成 当前类.class
反射,类只会被加载一次,所有产生的Class对象唯一

继承

一定要加static 哦

2.synchronized同步方法

如果操作共享数据的代码完整的声明在一个方法中,我们可以将此方法声明为同步(synchronized)方法
注意:
1.同步方法仍然涉及到同步监视器,只是不需要显示的声明
2.非静态的同步方法,同步监视器 this
静态同步监视器,当前类本身

public synchronized void show(String name){
}

Runnable()

继承

懒汉单例模式变成线程安全的

懒汉单例模式:

class Bank{
    private Bank(){}
    private static Bank instance =null;
    public static Bank getInstance() {
        //方式一:效率较差
       
            if (instance == null) {
                instance = new Bank();
            }
            return instance;
            }
   }

方式一:

class Bank{
    private Bank(){}
    private static Bank instance =null;
    public static Bank getInstance() {
        //方式一:效率较差
  synchronized (Bank.class) {
            if (instance == null) {
                instance = new Bank();
            }
            return instance;
        }

因为后面等待的数据就不用再判断等不等于null
直接返回instance 就好
方式二:

    if (instance == null) {
            synchronized (Bank.class) {
                if (instance == null) {
                    instance = new Bank();
                }
            }
        }
        return instance;

死锁

来看个例子:
在计算机系统中也存在类似的情况。例如,某计算机系统中只有一台打印机和一台输入 设备,进程P1正占用输入设备,同时又提出使用打印机的请求,但此时打印机正被进程P2 所占用,而P2在未释放打印机之前,又提出请求使用正被P1占用着的输入设备。这样两个进程相互无休止地等待下去,均无法继续执行,此时两个进程陷入死锁状态。

  • 死锁
  • 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃 自己需要的同步资源,就形成了线程的死锁
  • 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于 阻塞状态,无法继续
public class ThreadTest {
    public static void main(String[] args) {
        StringBuffer s1= new StringBuffer();
        StringBuffer s2= new StringBuffer();
        new Thread(){
            @Override
            public void run() {
                synchronized(s1){
                    s1.append("a");
                    s2.append("1");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized(s2){
                        s1.append("b");
                        s2.append("2");
                        System.out.println(s1);
                        System.out.println(s2);

                    }
                }
            }
        }.start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized(s2){
                    s1.append("c");
                    s2.append("3");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized(s1){
                        s1.append("d");
                        s2.append("4");
                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }).start();
    }
}

Lock(锁)

  • 从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
  • ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和
    内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以 显式加锁、释放锁。

构造函数:

public ReentrantLock() {
        sync = new NonfairSync();
    }
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }


注意:如果同步代码有异常,要将unlock()写入finally语句块

synchronized 与 Lock 的对比

  1. Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是
    隐式锁,出了作用域自动释放
    Lock手动挡,快 , synchronized 自动挡
  2. Lock只有代码块锁,synchronized有代码块锁和方法锁
  3. 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有
    更好的扩展性(提供更多的子类)
    优先使用顺序:
    Lock -> 同步代码块(已经进入了方法体,分配了相应资源) ->同步方法
    (在方法体之外)

实战

class Account{
    private double balance;
    private ReentrantLock lock = new ReentrantLock();

    public  double depisit(double money){
            if (money > 0) {
                balance += money;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + "余额" + balance);
            return balance;
        }

}
class Customer extends  Thread{
    private Account acct;
    public Customer(Account acct){
        this.acct=acct;
    }

    @Override
    public void run() {
        for(int i=0;i<3;i++){
            acct.depisit(1000);
        }
    }
}
class Test{
    public static void main(String[] args) {
        Account acct = new Account();
        Customer t1= new Customer(acct);
        Customer t2= new Customer(acct);
        t1.setName("甲");
        t2.setName("乙");
        t1.start();
        t2.start();
    }
}

同步代码块一睡准出错:线程不安全

三种方式都可以,博主这里用了最简单的

以上是关于线程同步的主要内容,如果未能解决你的问题,请参考以下文章

起底多线程同步锁(iOS)

多线程编程

第十次总结 线程的异步和同步

详解C++多线程

进程线程同步异步

配置 kafka 同步刷盘