多线程(3)- 线程同步


前言

线程问题,线程同步,同步方式
扩展:

概念

  1. 共享资源:多个线程对用一个资源进行访问(读写操作),这个资源就是共享资源。
  2. 数据/资源同步:如何保证多个线程访问的共享资源数据是一致的。

1、数据不一致问题

public class InconsistentDataProblem {

    public static final int MAX = 20;

    static class CallTask implements Runnable {
        private int index;

        @Override
        public void run() {
            while (index < MAX) {
                index++;
                System.out.println(Thread.currentThread().getName() + " 开始叫号 号码为 " + index);
            }
        }
    }

    public static void main(String[] args) {
        CallTask task = new CallTask();
        IntStream.range(0, 4).forEach(
                v -> {
                    new Thread(task, "窗口" + v).start();
                }
        );
    }

}

窗口0 开始叫号 号码为 2
窗口3 开始叫号 号码为 4
窗口2 开始叫号 号码为 3
窗口2 开始叫号 号码为 7
窗口2 开始叫号 号码为 8
窗口2 开始叫号 号码为 9
窗口2 开始叫号 号码为 10
窗口1 开始叫号 号码为 2
窗口2 开始叫号 号码为 11
窗口2 开始叫号 号码为 13
窗口2 开始叫号 号码为 14
窗口2 开始叫号 号码为 15
窗口2 开始叫号 号码为 16
窗口2 开始叫号 号码为 17
窗口2 开始叫号 号码为 18
窗口2 开始叫号 号码为 19
窗口2 开始叫号 号码为 20
窗口3 开始叫号 号码为 6
窗口0 开始叫号 号码为 5
窗口1 开始叫号 号码为 12

2、synchronized

  • synchronized通过一个简单的策略来防止线程干扰和内存一致性错误,如果一个对象对多个线程是可见的,那么该对象的所有读和写的操作都将通过同步的方式来进行,具体表现如下

    • synchronized关键字提供一个锁机制,能确保共享变量的互斥访问,从而保证数据一致性
    • 包含monitor entermonitor exit 两个JVM指令,能保证任何时候任何线程在执行到monitor enter成功之前都从主内存中获取数据,而不是从缓存中,在monitor exit运行成功之后,共享变量被更新后的值必须刷入到主内存
    • synchronized指令严格遵循java happens-before规则,一个monitor exit指令之前必须要有一个monitor enter。

使用Synchronized

public class SynchronizedTest {

    private Object mutex = new Object();

    public static void main(String[] args) {
        SynchronizedTest test = new SynchronizedTest();
        IntStream.range(0, 5).forEach(
                v -> {
                    new Thread(test::accessResource).start();
                }
        );
    }

    public void accessResource() {
        synchronized (mutex) {
            try {
                TimeUnit.MINUTES.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

在这里插入图片描述

monitor

  • 如果monitor的计数为0,意味着这个monitor的lock锁还没有被获得,某个线程获得之后立刻对计数+1 ,从此该线程就是这个monitor的所有者
  • 如果一个已经拥有该monitor的所有权线程重入,会导致monitor的计数再次累加
  • 如果monitor已被其它线程所有,则其它线程尝试获取该monitor的所有权时,会被陷入阻塞状态,直到monitor计数为0,才能再次尝试获取对monitor的所有权
  • this monitor 对象锁和 class monitor 类锁

synchronized缺陷

  • 无法控制阻塞时长
  • 阻塞不可被中断

使用synchronized注意事项

  1. 与monitor关联的对象不能为空
    对象为空,没有monitor一说
  2. synchronized作用域太大
    影响效率,应该只用于对共享资源的读写作用域
  3. 不同monitor企图锁相同的方法
    问题代码:此时每个线程的锁对象引用都不一样,无法达到同步效果
public class InconsistentDataProblem {


    public static final int MAX = 20;
    private static int    index;

    static class CallTask implements Runnable {

        private Object obj = new Object();
        @Override
        public void run() {
            synchronized (obj){
                while (index < MAX) {
                    index++;
                    System.out.println(Thread.currentThread().getName() + " 开始叫号 号码为 " + index);
                }
            }
        }
    }

    public static void main(String[] args) {
        //CallTask task = new CallTask();
        IntStream.range(0, 4).forEach(
                v -> {
                    new Thread(new CallTask(), "窗口" + v).start();
                }
        );
    }
}


窗口0 开始叫号 号码为 2
窗口0 开始叫号 号码为 4
窗口0 开始叫号 号码为 5
窗口2 开始叫号 号码为 3
窗口0 开始叫号 号码为 7
窗口1 开始叫号 号码为 2
窗口0 开始叫号 号码为 9
窗口0 开始叫号 号码为 11
窗口2 开始叫号 号码为 8
窗口3 开始叫号 号码为 6
窗口2 开始叫号 号码为 13
窗口0 开始叫号 号码为 12
窗口0 开始叫号 号码为 16
窗口0 开始叫号 号码为 17
窗口0 开始叫号 号码为 18
窗口0 开始叫号 号码为 19
窗口0 开始叫号 号码为 20
窗口1 开始叫号 号码为 10
窗口2 开始叫号 号码为 15
窗口3 开始叫号 号码为 14

  1. 多个锁交叉导致死锁
public class DeadLock {

    public static final String A = "a";
    public static final String B = "b";

    public static void main(String[] args) {

        Thread t1 = new Thread(() -> {
            synchronized (A) {
                System.out.println(Thread.currentThread().getName() + "获取A锁");
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (B) {
                    System.out.println(Thread.currentThread().getName() + "获取B锁");
                }
            }
        }, "1");

        Thread t2 = new Thread(() -> {
            synchronized (B) {
                System.out.println(Thread.currentThread().getName() + "获取B锁");
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (A) {
                    System.out.println(Thread.currentThread().getName() + "获取A锁");
                }
            }
        }, "2");

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

}

在这里插入图片描述

死锁原因,如何诊断

1、交叉锁导致的死锁
2、内存不足
A 10M,B 20M两个线程都需要30M才能工作,两者都在等待对方释放资源
3、一问一答的数据交换
4、数据库锁
5、文件锁
6、死循环引起的死锁

打开Jstack 或 Jconsole Jstack-l PID 会直接发现死锁信息

jvisualvm
在这里插入图片描述
Jconsole
在这里插入图片描述

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值