zoukankan      html  css  js  c++  java
  • java的线程安全、单例模式、JVM内存结构等知识学习和整理

    1,什么是线程安全 (参考书:https://book.douban.com/subject/10484692/)
    2,都说String是不可变的,为什么我可以这样做呢
       String a = "1";
       a = "2";
    3,HashMap的实现原理
    4,写出三种单例模式,如果能考虑线程安全最好
    5,ArrayList和LinkedList有什么区别
    6,实现线程的2种方式
    7,JVM的内存结构
    8,Lock与Synchronized的区别
    9,数据库隔离级别有哪些,各自的含义是什么,MYSQL默认的隔离级别是是什么。
    10,请解释如下jvm参数的含义:
    -server -Xms512m -Xmx512m -Xss1024K 
    -XX:PermSize=256m -XX:MaxPermSize=512m -XX:MaxTenuringThreshold=20 
    -XX:CMSInitiatingOccupancyFraction=80 -XX:+UseCMSInitiatingOccupancyOnly。
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    1.什么是线程安全 (参考书:https://book.douban.com/subject/10484692/)

    线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。[百度百科:线程安全]

    思考1:为什么会出现线程安全问题?

    从百度百科的概念可以知道,发送线程安全问题的两个条件:

    • 多线程访问

    • 访问某个数据,(这里强调一下,某个数据是实例变量即对线程是共享的

      这两个条件都必须满足,缺一不可,否则不会出现线程安全问题。

    思考2:怎么解决线程安全问题?

    通过加锁机制,可以使用关键字synchronized,或者java并发包中的Lock。还有在使用集合中的类如ArrayList或者HashMap时要考虑是否存在线程安全问题,如果存在最好使用ConcurrentHashMap替代hashMap,或者使用Collections.synchronizedXXX进行封装!

    实例:通过一段代码演示线程安全和非线程安全

    
    /**
     * 线程安全和线程不安全---简单实例
     *
     * @author:dufy
     * @version:1.0.0
     * @date 2017/10/13
     */
    public class ThreadSafey {
    
    
        private int countUnSafe = 0;//实例变量
        private int countSafe = 0;
    
        //线程不安全的方法
        public void addUnSafe(){
            try {
                Thread.sleep(100);//为了更好的测试。休眠100ms
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            countUnSafe ++;
            System.out.println("countUnSafe = " + countUnSafe);
        }
        //线程安全的方法
        //这里也可以使用使用同步代码块的方式,建议在实际开发使用同步代码块,相对比同步方法好很多, 也可以使用Lock进行加锁!
        public synchronized void addSafe(){
            try {
                Thread.sleep(100);//为了更好的测试。休眠100ms
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            countSafe ++;
            System.out.println("countSafe = " + countSafe);
        }
    
        public static void main(String[] args) {
    
            ThreadSafey ts = new ThreadSafey();
            UnSafeT unSafeT = new UnSafeT(ts);
            SafeT safeT = new SafeT(ts);
            //启动10个线程
            for (int i = 0; i < 10; i++) {
               Thread thread = new Thread(unSafeT);
                thread.start();
            }
            //启动10个线程
            for (int i = 0; i < 10; i++) {
                Thread thread = new Thread(safeT);
                thread.start();
            }
        }
    
    }
    //不安全线程测试类
    class UnSafeT implements Runnable{
    
        private  ThreadSafey threadSafey;
    
        public UnSafeT(ThreadSafey threadSafey){
            this.threadSafey = threadSafey;
        }
        @Override
        public void run() {
            threadSafey.addUnSafe();
        }
    }
    //安全线程测试类
    class SafeT implements Runnable{
    
        private  ThreadSafey threadSafey;
    
        public SafeT(ThreadSafey threadSafey){
            this.threadSafey = threadSafey;
        }
        @Override
        public void run() {
            threadSafey.addSafe();
        }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80

    运行结果如下,多次运行后,发现countUnSafe总是有重复的值,并且不按照顺序输出,最后的结果也不是10; 
    countSafe 按照顺序打印,最后的结果也是10。如果你运行了上面的代码,可能和我执行下面打印的不一样,但是结论是一样的。

    countUnSafe = 3
    countUnSafe = 3
    countUnSafe = 3
    countUnSafe = 3
    countUnSafe = 3
    countUnSafe = 5
    countUnSafe = 8
    countUnSafe = 8
    countUnSafe = 6
    countUnSafe = 5
    countSafe = 1
    countSafe = 2
    countSafe = 3
    countSafe = 4
    countSafe = 5
    countSafe = 6
    countSafe = 7
    countSafe = 8
    countSafe = 9
    countSafe = 10
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    线程安全说简单了就上面这些内容,如何深入需要知道线程的工作原理,JVM下线程是如何进行工作,为什么实例变量会存在线程安全问题,而私有变量不会出现,这就和变量在内存中创建和存储的位置有关。下面进行简单的说明,不会一一展开了。

    在程序运行后JVM中有一个主内存,线程在创建后也会有一个自己的内存(工作内存),会拷贝主内存的一些数据,每个线程之间能够共享主内存,而不能访问其他线程的工作内存,那么一个变量是实例变量的时候,如果没有加锁机制,就会出现线程安全问题。

    比如:系统有线程A和线程B,这两个线程同时访问了addUnSafe方法,并将countUnsafe变量拷贝在自己的内存中(countUnsafe = 0),然后进行操作,那么这两个线程 都执行countUnsafe++,这两个线程的工作内存中countUnsafe = 1;然后写回主内存,此时主内存countUnsafe = 1,当另一个线程C访问时候,C工作内存操作的countUnsafe的值就是1,此时发生了线程安全问题。

    【图片来自–java并发编程艺术-第二章 java内存模型抽象结构】 
    这里写图片描述

    暂时就讲这么多了!

    可以参考:Java 线程通信内存模型—主内存与工作内存 了解更多详细内容!


    2.都说String是不可变的,为什么我可以这样做呢,String a = “1”;a = “2”;

    先看一段代码,然后通过代码和一幅图进行讲解!

    public class StringTest {
    
        public static void main(String[] args) {
            String s = "ABCabc";
            System.out.println("s1.hashCode() = " + s.hashCode() + "--" + s);
            s = "123456";
            System.out.println("s2.hashCode() = " + s.hashCode() + "--" + s);
            //运行后输出的结果不同,两个值的hascode也不一致,
            //说明设置的值在内存中存储在不同的位置
        }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    【首先创建一个String对象s,然后让s的值为“ABCabc”, 然后又让s的值为“123456”。 从打印结果可以看出,s的值确实改变了。那么怎么还说String对象是不可变的呢?

    其实这里存在一个误区: s只是一个String对象的引用,并不是对象本身。对象在内存中是一块内存区,成员变量越多,这块内存区占的空间越大。引用只是一个4字节的数据,里面存放了它所指向的对象的地址,通过这个地址可以访问对象。

    也就是说,s只是一个引用,它指向了一个具体的对象,当s=“123456”; 这句代码执行过之后,又创建了一个新的对象“123456”, 而引用s重新指向了这个心的对象,原来的对象“ABCabc”还在内存中存在,并没有改变。内存结构如下图所示:

    这里写图片描述

    】—摘自【Java中的String为什么是不可变的? – String源码分析

    相关参考文章

    1:【知乎-胖胖 回答】如何理解 String 类型值的不可变?

    2:一个图参考8 张图理解 Java

    这里写图片描述


    3.HashMap的实现原理

    HashMap 我之前的专栏中也有写过,不过分析HashMap要注意JDK版本,jdk1.7和jdk1.8中底层的实现就有不同。

    说简单点HashMap是一个集合,通过put(key,value)存储数据,然后使用get(key)获取数据。

    实现原理是基于hashing原理,使用hash算法实现。 
    jdk1.7 数组+链表 
    jdk1.8 数组+链表+红黑树

    详情可参考下面博文:

    java集合系列——Map之HashMap介绍(八) 
    HashMap的工作原理 
    Java8系列之重新认识HashMap


    4.写出三种单例模式,如果能考虑线程安全最好

    首先总结目前实现单例模式的方式有以下五种:

    • 饿汉式,线程安全
    • 懒汉式,线程不安全(注意加synchronized,变线程安全)
    • 双重检验锁(注意将instance 变量声明成 volatile,并注意jdk版本大于等于1.5)
    • 静态内部类 ,线程安全
    • 枚举,线程安全

    注:推荐使用后面三种

    具体代码就不一一写了:如果想了解具体的代码如何写,点击下面:

    你真的会写单例模式吗——Java实现

    单例模式-专栏


    5.ArrayList和LinkedList有什么区别

    这个我之前也在我的博客中有介绍在java集合系列——List集合总结(六) 
    这里在说明一下: 
    简单介绍 
    1 ArrayList是基于数组实现的,是一个数组队列。可以动态的增加容量! 
    2. LinkedList是基于链表实现的,是一个双向循环列表。可以被当做堆栈使用!

    使用场景 
    1. 当集合中对插入元素数据的速度要求不高,但是要求快速访问元素数据,则使用ArrayList! 
    2. 当集合中对访问元素数据速度不做要求不高,但是对插入和删除元素数据速度要求高的情况,则使用LinkedList!

    具体分析 
    1.ArrayList随机读取的时候采用的是get(index),根据指定位置读取元素,而LinkedList则采用size/2 ,二分法去加速一次读取元素,效率低于ArrayList! 
    2.ArrayList插入时候要判断容量,删除时候要将数组移位,有一个复制操作,效率低于LinkList!而LinkedList直接插入,不用判断容量,删除的时候也是直接删除跳转指针节点,没有复制的操作!


    6.实现线程的2种方式

    Java中有两种方式实现多线程,一种是继承Thread类,一种是实现Runnable接口。具

    • 实现Runnable接口
    • 继承Thread类

    注意:线程的启动是调用start()方法,而不是run()方法!

    举例并进行解释

    1.直接调用run方法实例:

    public class TestTheadDemo {
    
    
        public static void main(String[] args) {
            for (int i = 0; i < 10; i++) {
                ThreadTest thread = new ThreadTest();
                thread.run();
    //            thread.start();
            }
        }
    }
    
    class ThreadTest extends Thread{
    
        @Override
        public void run() {
            System.out.println("当前线程 : " + Thread.currentThread().getName());
        }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    运行结果 :当前线程全部是main线程,相当于ThreadTest类的thread对象直接调用了run()方法。(直接调用run方法只是一个普通的单线程程式)

    当前线程 : main
    当前线程 : main
    当前线程 : main
    当前线程 : main
    当前线程 : main
    • 1
    • 2
    • 3
    • 4
    • 5

    2.调用start()方法 
    将上面的代码 注释的thread.start();打开, thread.run();注释!

    运行结果 :发现启动了不同的线程进行执行。

    当前线程 : Thread-0
    当前线程 : Thread-5
    当前线程 : Thread-3
    当前线程 : Thread-4
    当前线程 : Thread-2
    当前线程 : Thread-1
    当前线程 : Thread-7
    当前线程 : Thread-6
    当前线程 : Thread-9
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    查看start()方法的源码中,发现有个地方

     public synchronized void start() {
           //代码省略....
            try {
                start0();//注意这个地方,调用了native本地方法
                started = true;
            } finally {
              //代码省略....
            }
        }
        //本地方法
        private native void start0();
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    总结: 调用start()方法,虚拟机JVM通过执行本地native方法start0和操作系统cup进行交互,此时线程并没有正在立即执行,而是等待cup的调度,当cpu分配给线程时间,线程才执行run()方法!

    相关内容可以参考 【java多线程编程核心技术: 高洪岩 

    7.JVM的内存结构

    上图:【图片版本-深入理解Java虚拟机:JVM高级特性与最佳实践:周志明】

    这里写图片描述

    点击查看详情: 运行时数据区域

    这里写图片描述

    还有这个博文: JVM内存结构图解

    这里写图片描述


    8.Lock与Synchronized的区别

    在Java中Lock与Synchronized都可以进行同步操作,保证线程的安全,就如上面第一问提到的,线程安全性问题。下面进行简单的额介绍!

    1.Synchronized的简单介绍

    synchronized同步的原理

    关键字synchronized可以修饰方法或者以同步块的形式来使用,它主要确保多个线程在同一时刻,只能有一个线程处于方法或者同步块 中,保证了线程对变量访问的可见性和排他性。

    synchronized同步实现的基础 
    1. 普通同步方法,锁是当前实例对象 
    2. 静态同步方法,锁是当前类的class对象 
    3. 同步方法块,锁是括号里面的对象

    一张图讲解对象锁和关键字synchronized修饰方法(代码块)

    【死磕Java并发】—–深入分析synchronized的实现原理

    注:有兴趣也可以看看 volatile关键字!

    关键字volatile可以用来修饰字段(成员变量),就是告知程序任何对该变量的访问均需要从共享内存中获取,而对它的改变必须同步刷新回共享内存中,保证所有线程对变量访问的可见性。

    关键字volatile和关键字synchronized均可以实现线程间通信!

    2.Lock的简单介绍

    首先明确Lock是Java 5之后,在java.util.concurrent.locks包下提供了另外一种方式来实现同步访问。 
    Lock是一个接口,其由三个具体的实现:ReentrantLock、ReetrantReadWriteLock.ReadLock 和 ReetrantReadWriteLock.WriteLock,即重入锁、读锁和写锁。增加Lock机制主要是因为内置锁存在一些功能上局限性。

    Java并发编程系列之十六:Lock锁

    区别总结: 
    1.synchronized是Java语言的关键字,Lock是一个类,通过这个类可以实现同步访问;

    2.synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,Lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock(),最好放到finally{}中。 
    3.Lock有ReetrantLock(可重入锁)实现类,可选的方法比synchronized多,使用更灵活。 
    4..并不是Lock就比synchronized一定好,因为synchronized在jdk后面的版本也在不断优化。在资源竞争不是很激烈的情况下,synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,synchronized的性能会下降很多,性能不如Lock。

    Lock和synchronized的区别和使用

    9.数据库隔离级别有哪些,各自的含义是什么,MYSQL默认的隔离级别是是什么。

    这个问题正好在上一家公司整理过,开心ing!

    事务隔离级别

    事务指定了4种隔离级别(从弱到强分别是):

    1. Read Uncommitted:读未提交
    2. Read Committed:读提交
    3. Repeatable Read:重复读
    4. Serializable:序列化

    1:Read Uncommitted(读未提交):一个事务可以读取另一个未提交事务的数据。

    2:Read Committed(读提交):一个事务要等另一个事务提交后才能读取数据。

    3:Repeatable Read(重复读):在开始读取数据(事务开启)时,不再允许修改操作。

    4:Serializable(序列化):Serializable 是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。

    大多数数据库默认的事务隔离级别是Read committed,比如Sql Server , Oracle。MySQL的默认隔离级别是Repeatable read。

    在事务的并发操作中可能会出现脏读(dirty read),不可重复读(repeatable read),幻读(phantom read)。可参考:理解事务的4种隔离级别

    注:Mysql查询事务隔离级别: 

    查看当前会话隔离级别:select @@tx_isolation; 
    查看系统当前隔离级别:select @@global.tx_isolation; 
    设置当前会话隔离级别:set session transaction isolation level repeatable read; 
    设置当前会话隔离级别:set global transaction isolation level repeatable read; 

    10.请解释如下jvm参数的含义

    -server -Xms512m -Xmx512m -Xss1024K  -Xmn256m
    -XX:PermSize=256m -XX:MaxPermSize=512m 
    -XX:MaxTenuringThreshold=20 
    -XX:CMSInitiatingOccupancyFraction=80 
    -XX:+UseCMSInitiatingOccupancyOnly
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这里写图片描述 
    【图片来自网络,如有版权问题,请反馈,及时删除!】

    -server :服务器模式 
    注:JVM client模式和server模式,生产环境请使用-server,性能更好! 
    JVM client模式和Server模式的区别

    -Xms512m :JVM初始分配的堆内存,一般和Xmx配置成一样以避免每次gc后JVM重新分配内存。

    -Xmx512m :JVM最大允许分配的堆内存,按需分配

    -Xss1024K :设置每个线程的堆栈大小

    -Xmn256m :年轻代内存大小,整个JVM内存=年轻代 + 年老代 + 持久代

    -XX:PermSize=256m :设置持久代(perm gen)初始值,默认物理内存的1/64

    -XX:MaxPermSize=512m : 设置持久代最大值

    -XX:MaxTenuringThreshold=20 : 垃圾最大年龄

    -XX:CMSInitiatingOccupancyFraction=80 : 使用cms作为垃圾回收 
    使用80%后开始CMS收集

    -XX:+UseCMSInitiatingOccupancyOnly : 使用手动定义初始化定义开始CMS收集

    还有很多很多参数。。。。

    jvm参数设置参考博文 
    JVM系列三:JVM参数设置、分析

    一个性能较好的JVM参数配置

    JVM调优总结 -Xms -Xmx -Xmn -Xss

    转自http://blog.csdn.net/u010648555/article/details/78195674

  • 相关阅读:
    父子进程 signal 出现 Interrupted system call 问题
    一个测试文章
    《淘宝客户端 for Android》项目实战 html webkit android css3
    Django 中的 ForeignKey ContentType GenericForeignKey 对应的数据库结构
    coreseek 出现段错误和Unigram dictionary load Error 新情况(Gentoo)
    一个 PAM dbus 例子
    漫画统计学 T分数
    解决 paramiko 安装问题 Unable to find vcvarsall.bat
    20141202
    js
  • 原文地址:https://www.cnblogs.com/shizhijie/p/8257920.html
Copyright © 2011-2022 走看看