zoukankan      html  css  js  c++  java
  • java高并发编程--02--线程安全与数据同步

    1.synchronized关键字
    synchronized关键字提供了一种排他机制,也就是同一时间只能有一个线程执行某些操作,从而防止线程干扰和内存一致性错误,如果一个对象对多个线程是可见的,那么对该对象的索引读或者写都将通过同步的方式来进行,表现如下:
    1)synchronized关键字提供一种锁机制,能够确保变量的互斥访问,从而防止数据不一致问题出现。
    2)synchronized关键字包含monitor enter 和 monitor exit两个jvm指令,它能够保证任何线程执行到monitor enter成功之前必须从主内存获取数据,而不是从缓存中,在执行monitor exit成功之后,共享变量被更新后的值必须刷入主内存。
    3)synchronized的指令严格遵循java happens-before规则,一个monitor exit指令前必定有一个monitor enter

    1.1synchronized关键字用法
    1)同步方法
    [default|public|private|protected] synchronized [static] type method(){}
    2)同步代码块

    private final Object MUTEX = new Object();
    public void sync(){
        synchronized(MUTEX){
            ...
            ...
        }
    }

    注意:synchronized关键字不可以用到变量上,对公共变量的同步可以使用上面两种同步方法,示例代码:

    public class TicketWindowRunable1 implements Runnable{
        private int index = 1;
        private int max = 100;
        private static final Object MUTEX = new Object();
        @Override
        public void run() {
            synchronized (MUTEX) {
                //注意,一定要将对公共数据的所有操作放到同步代码中,包括下面的while判断
                while(index <= max) {
                    try {
                        TimeUnit.MILLISECONDS.sleep(1);//模拟数据处理耗时
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("请编号为"+index ++ + "的客户到"+Thread.currentThread().getName()+"办理业务!");
                }
            }
        }
        public static void main(String[] args) {
            TicketWindowRunable1 tr = new TicketWindowRunable1();
            Thread thread1 = new Thread(tr, "1号窗口");
            Thread thread2 = new Thread(tr, "2号窗口");
            Thread thread3 = new Thread(tr, "3号窗口");
            thread1.start();
            thread2.start();
            thread3.start();
        }
    }

    1.2synchronized关键字注意问题
    1)monitor关联的对象不能为空
    2)synchronized作用域不宜太大
    由于synchronized的排他性,synchronized作用域太长会影响效率
    3)不同monitor企图锁相同的代码
    如下代码:

    public class ErrorMonitor implements Runnable{
        private final Object MUTEX = new Object();
        public void run() {
            synchronized (MUTEX) {
                System.out.println(MUTEX);
            }
        }
        public static void main(String[] args) throws InterruptedException {
            for(int i = 0;i < 5;i ++) {
                new Thread(new ErrorMonitor()).start();
            }
            TimeUnit.SECONDS.sleep(10);
        }
    }

    输出结果:
    java.lang.Object@f5cbdd
    java.lang.Object@4b20c054
    java.lang.Object@7ff41d1f
    java.lang.Object@3dcbd823
    java.lang.Object@163bd47a
    上面代码中,MUTEX不是静态的,每创建一个ErrorMonitor对象都会创建一个MUTEX,main方法中5个线程各自创建了一个,因此起不到锁的作用,其特点是MUTEX属于对象,不同线程使用的是不同的对象。
    如果改成下面写法,则就没有问题:

    public class ErrorMonitor implements Runnable{
        private final Object MUTEX = new Object();
        public void run() {
            synchronized (MUTEX) {
                System.out.println(MUTEX);
            }
        }
        public static void main(String[] args) throws InterruptedException {
            ErrorMonitor target = new ErrorMonitor();
            for(int i = 0;i < 5;i ++) {
                new Thread(target).start();
            }
            TimeUnit.SECONDS.sleep(10);
        }
    }

    输出结果:
    java.lang.Object@5405ed04
    java.lang.Object@5405ed04
    java.lang.Object@5405ed04
    java.lang.Object@5405ed04
    java.lang.Object@5405ed04
    4)多个锁交叉导致死锁

        private final Object MUTEX_read = new Object();
        private final Object MUTEX_write = new Object();
        public void read() {
            synchronized (MUTEX_read) {
                synchronized (MUTEX_write) {
                    //do
                }
            }
        }
        public void write() {
            synchronized (MUTEX_write) {
                synchronized (MUTEX_read) {
                    //do
                }
            }
        }

    1.3 this monitor 与 class monitor
    synchronized(this)的monitor对象是当前类的实例对象,synchronized修饰的成员方法也是当前实例对象。
    静态方法的monitor对象是类的Class对象

    1.4死锁
    死锁可能的原因:
    1)交叉锁导致死锁:线程A持有R1的锁等待获取R2的锁,线程B持有R2的锁等待R1的锁
    2)内存不足:当内存不足时,两个线程都在等待对方释放锁
    3)一问一答的数据交换:
    服务端开启某个端口等待客户端访问,客户端发起请求立即等待接收,由于某种原因服务端错过客户端的请求导致两端都陷入等待
    4)数据库锁
    某个线程执行for update语句意为退出事务,其他线程访问被锁资源时都将陷入死锁
    5)文件锁
    某个线程获得了文件锁意外退出,其他读取该文件的线程会进入死锁直到系统释放文件句柄资源
    6)死循环引起死锁
    由于代码原因或某些处理不得当,进入死循环,虽然查看线程栈信息不回发现任何死锁迹象,但是程序不工作,CPU占有率居高不下,这种死锁一般称之为系统假死,为一种最为致命也最难排查的死锁现象

    死锁举例:
    HashMap不是线程安全的类,如果多个线程对其进行写操作,很有可能出现死循环引起的死锁。程序运行一段时间后CPU等资源居高不下,各种诊断工具很难派上用场,因为死锁引起的进程往往会榨干CPU等几乎所有资源,诊断工具由于缺少资源一时难以启动。如下代码:

    public class DeadBlockTest {
        private static final HashMap<String, String> map = new HashMap<>();
    
        public void add(String key, String value) {
            map.put(key, value);
        }
    
        public static void main(String[] args) {
            final DeadBlockTest dt = new DeadBlockTest();
            for (int m = 0; m < 2; m++) {
                new Thread(() -> {
                    for (int x = 0; x < Integer.MAX_VALUE; x++) {
                        //System.out.println(Thread.currentThread().getName() + ":" + x);
                        dt.add(x + "", x + "");
                    }
                }, "T-" + m).start();
            }
        }
    }

    HashMap不具备线程安全的能力,如果想要使用线程安全的map结构请使用ConcurrentHashMap或者使用Collection.synchronizedMap

  • 相关阅读:
    CentOS 7 设置iptables防火墙开放proftpd端口
    Android手机修改Hosts的方法
    Wireshark提示没有一个可以抓包的接口
    while read line无法循环read文件
    Nagios新添加的hosts和services有时显示,有时不显示问题解决
    Nginx配置http强制跳转到https
    xargs rm -rf 与 -exec rm
    解决vim不能使用方向键和退格键问题
    NRPE: Unable to read output 问题处理总结
    MySQL 5.6 解决InnoDB: Error: Table "mysql"."innodb_table_stats" not found.问题
  • 原文地址:https://www.cnblogs.com/ShouWangYiXin/p/11420213.html
Copyright © 2011-2022 走看看