java的线程安全、单例模式、JVM内存结构等知识学习和整理

知其然,不知其所以然 !在技术的海洋里,前路漫漫,我一直在迷失着自我。


本文始发地址:http://blog.csdn.net/u010648555/article/details/78195674
欢迎访问我的csdn博客,我们一同成长!

不管做什么,只要坚持下去就会看到不一样!在路上,不卑不亢!

博客首页 :http://blog.csdn.net/u010648555 欢迎访问!


在下面的题目来自于我要加的一个QQ群,然后要加这个QQ群,首先要通过进阶考核,也就是下面这些题,当我看到这些题目的时候。发现这些题目很常见,但是细细去研究,发现每一个问题的知识点都是特别的多也比较深奥!

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.什么是线程安全 (参考书: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();
    }
}

运行结果如下,多次运行后,发现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

线程安全说简单了就上面这些内容,如何深入需要知道线程的工作原理,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也不一致,
        //说明设置的值在内存中存储在不同的位置
    }
}

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

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

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

这里写图片描述

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

**相关参考文章 **

1:【知乎-胖胖 回答】[如何理解 String 类型值的不可变?】(https://www.zhihu.com/question/20618891)

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是基于数组实现的,是一个数组队列。可以动态的增加容量!

  1. 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());
    }
}

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

当前线程 : main
当前线程 : main
当前线程 : main
当前线程 : main
当前线程 : main

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

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

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

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

 public synchronized void start() {
       //代码省略....
        try {
            start0();//注意这个地方,调用了native本地方法
            started = true;
        } finally {
          //代码省略....
        }
    }
    //本地方法
    private native void start0();

总结: 调用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的实现原理

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 -XX:PermSize=256m -XX:MaxPermSize=512m -XX:MaxTenuringThreshold=20 XX:CMSInitiatingOccupancyFraction=80 -XX:+UseCMSInitiatingOccupancyOnly。

-server -Xms512m -Xmx512m -Xss1024K  -Xmn256m
-XX:PermSize=256m -XX:MaxPermSize=512m 
-XX:MaxTenuringThreshold=20 
-XX:CMSInitiatingOccupancyFraction=80 
-XX:+UseCMSInitiatingOccupancyOnly

这里写图片描述

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

-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收集</fon

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

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

jvm参数设置参考博文

JVM系列三:JVM参数设置、分析

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

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


本次总结结束...........


如果帅气(美丽)、睿智(聪颖),和我一样简单善良的你看到本篇博文中存在问题,请指出,我虚心接受你让我成长的批评,谢谢阅读!
祝你今天开心愉快!


欢迎访问我的csdn博客,我们一同成长!

不管做什么,只要坚持下去就会看到不一样!在路上,不卑不亢!

博客首页 :http://blog.csdn.net/u010648555 欢迎访问!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容

  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,174评论 11 349
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,567评论 18 399
  • Java8张图 11、字符串不变性 12、equals()方法、hashCode()方法的区别 13、...
    Miley_MOJIE阅读 3,690评论 0 11
  • 一个简单的单例示例 单例模式可能是大家经常接触和使用的一个设计模式,你可能会这么写 publicclassUnsa...
    Martin说阅读 2,207评论 0 6
  • 今天,见了久别五年的高中老同学。过往无数的回忆和旧事都被一一触发,萦绕在心间百转千结。原来,自己最不愿意回忆起的那...
    安若玲阅读 543评论 0 3