线程同步解决线程安全问题

郎家岭伯爵 2022年05月10日 553次浏览

背景

多个线程操作同一资源时,会产生线程安全问题。

实现

synchronizedlock锁均可解决线程安全问题。

问题复现

代码

package com.langjialing;


public class ThreadSynchronized {
    public static void main(String[] args) {
        BuyTicket buy = new BuyTicket();

        new Thread(buy,"小明").start();
        new Thread(buy,"小红").start();
    }
}

class  BuyTicket implements Runnable{

    private int ticketsNum = 100;
    boolean flag = true;

    @Override
    public void run() {
        //买票
        while(flag){
            try{
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private synchronized void buy() throws InterruptedException {
        if (ticketsNum <= 0){
            flag = false;
            return;
        }

        //模拟延迟
        Thread.sleep(100);

        System.out.println(Thread.currentThread().getName() + "拿到"+ ticketsNum--);

    }
}

效果

synchronized解决

synchronized同步方法

synchronized同步方法仅需要在buy()方法上添加synchronized关键字,将buy()方法设置为同步方法。

package com.langjialing;


public class ThreadSynchronized {
    public static void main(String[] args) {
        BuyTicket buy = new BuyTicket();

        new Thread(buy,"小明").start();
        new Thread(buy,"小红").start();
    }
}

class  BuyTicket implements Runnable{

    private int ticketsNum = 100;
    boolean flag = true;

    @Override
    public void run() {
        //买票
        while(flag){
            try{
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    //synchronized关键字,将方法设置为同步方法,锁的是this
    private synchronized void buy() throws InterruptedException {
        if (ticketsNum <= 0){
            flag = false;
            return;
        }

        //模拟延迟
        Thread.sleep(100);

        System.out.println(Thread.currentThread().getName() + "拿到"+ ticketsNum--);

    }
}

synchronized同步代码块

使用格式如下:

// Obj即为需要同步的对象
synchronized(Obj){
    功能代码块;
}

例如对 List 添加同步锁:

public void test13() throws InterruptedException{
    List<Integer> list = new ArrayList<>();

    final ExecutorService executorService = Executors.newFixedThreadPool(32);

    for (int i = 0; i < 32; i++) {
        executorService.execute(() -> {
            try {
                Thread.sleep(RandomUtil.randomInt(100,1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for (int j = 0; j < 10; j++) {
                synchronized (list){
                    list.add(j);
                }

            }
            // 这里需要添加一个等待,否则会由于 List 引起并发问题
            try {
                Thread.sleep(RandomUtil.randomInt(300,500));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("size:" + list.size() + "; element:" + list);

        });
    }
    executorService.shutdown();
}

Lock锁解决

package com.langjialing;


import java.util.concurrent.locks.ReentrantLock;

public class ThreadSynchronized {
    public static void main(String[] args) {
        BuyTicket buy = new BuyTicket();

        new Thread(buy,"小明").start();
        new Thread(buy,"小红").start();
        new Thread(buy,"小兰").start();
    }
}

class  BuyTicket implements Runnable{

    private int ticketsNum = 100;
    boolean flag = true;

    //定义lock锁
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        //买票
        while(flag){
            
            try{
                lock.lock();    //加锁
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }


    private void buy() throws InterruptedException {
        if (ticketsNum <= 0){
            flag = false;
            return;
        }
        //模拟延迟
        Thread.sleep(100);

        System.out.println(Thread.currentThread().getName() + "拿到"+ ticketsNum--);

    }
}

synchronized与lock锁的区别

  • synchronized可以作用在方法,也可以作用在指定的代码块,在括号中指定要锁的对象,同时不会出现死锁;而lock锁通过lock和unlock实现加锁、解锁,并需要把unlock写在finally代码块中防止死锁。
  • synchronized是Java的关键字,是JVM内置的语言实现,因此性能比较低效;而lock锁是控制锁的代码块。
  • synchronized只能等待锁的释放,不可以中断;而lock锁可以通过interrupt来中断。
  • 在线程数量较大时,lock锁的效率要远高于synchronized。
  • synchronized使用Object对象本身的wait、notify、notifyAll调度机制,而lock锁可以使用Condition进行线程之间的调度。

总结

线程安全问题可以通过线程同步来解决。