JAVA 多线程(4)
接着3说:
一、String常量池
先回顾 java 的基本数据类型:
变量就是申请内存来存储值。也就是说,当创建变量的时候,需要在内存中申请空间。
内存管理系统根据变量的类型为变量分配存储空间,分配的空间只能用来储存该类型数据。
因此,通过定义不同类型的变量,可以在内存中储存整数、小数或者字符。
Java语言提供了八种基本类型。六种数字类型(四个整数型(默认是int 型),两个浮点型(默认是double 型)),一种字符类型,还有一种布尔型
说byte之前说一下别的。计算机码或者语言是二进制也就是 0 和1,为了让计算机能识别人类语言,所以用二进制来定义规则,也就是编码,把人类语言翻译为二进制的数据。
所以这里多记一下:
计算机的计算单位-》
0/1 的单位我们称为bit ,也就是虽小单位,1byte = 8bit ,byte 我们称它为字节,比如 10000001 的大小是1byte ,也就是8bit。byte简称为B。
再往上,就是K,1K = 1024B,我们常说的1KB 意思就是说有1K的B,但是K是1024 不是1000 ,因为计算机是通过二进制来识别计算的,这样计算性能更高。
以此类推 1M = 1025K,1G = 1024 M,1T = 1024G 等等等。
多说依据硬盘空间与网络传输单位,为了方便,一般服务商则采用的是十进制来计算,所以有时候我们买的是10G的硬盘,但是计算机上显示的却比它小。第三方网络服务商也是通过十进制而不是二进制。这点需要注意。
数字型(整数)
byte:
- byte数据类型是8位、有符号的,以二进制补码表示的整数;(256个数字),占1字节
- 最小值是-128(-2^7);
- 最大值是127(2^7-1);
- 默认值是0;
- byte类型用在大型数组中节约空间,主要代替整数,因为byte变量占用的空间只有int类型的四分之一;
- 例子:byte a = 100,byte b = -50。
short:
- short数据类型是16位、有符号的以二进制补码表示的整数,占2字节
- 最小值是-32768(-2^15);
- 最大值是32767(2^15 - 1);
- Short数据类型也可以像byte那样节省空间。一个short变量是int型变量所占空间的二分之一;
- 默认值是0;
- 例子:short s = 1000,short r = -20000。
int(一般我们使用int较多):
- int数据类型是32位、有符号的以二进制补码表示的整数;占3字节
- 最小值是-2,147,483,648(-2^31);
- 最大值是2,147,485,647(2^31 - 1);
- 一般地整型变量默认为int类型;
- 默认值是0;
- 例子:int a = 100000, int b = -200000。
long:
- long数据类型是64位、有符号的以二进制补码表示的整数;占4字节
- 最小值是-9,223,372,036,854,775,808(-2^63);
- 最大值是9,223,372,036,854,775,807(2^63 -1);
- 这种类型主要使用在需要比较大整数的系统上;
- 默认值是0L;
- 例子: long a = 100000L,int b = -200000L。
long a=111111111111111111111111(错误,整数型变量默认是int型)
long a=111111111111111111111111L(正确,强制转换)
如果强制为long 请在变量值后加上L
数字型(浮点)
float:
- float数据类型是单精度、32位、符合IEEE 754标准的浮点数;占4字节 -3.4*E38- 3.4*E38。。。浮点数是有舍入误差的
- float在储存大型浮点数组的时候可节省内存空间;
- 默认值是0.0f;
- 浮点数不能用来表示精确的值,如货币;
- 例子:float f1 = 234.5f。
- float f=6.26(错误 浮点数默认类型是double类型)
- float f=6.26F(转换正确,强制)
- double d=4.55(正确)
double:
- double数据类型是双精度、64位、符合IEEE 754标准的浮点数;
- 浮点数的默认类型为double类型;
- double类型同样不能表示精确的值,如货币;
- 默认值是0.0d;
- 例子:double d1 = 123.4。
布尔型
boolean:
- boolean数据类型表示一位的信息;
- 只有两个取值:true和false;
- 这种类型只作为一种标志来记录true/false情况;
- 默认值是false;
- 例子:boolean one = true。
关于布尔型占用几个字节请参考
https://www.jianshu.com/p/2f663dc820d0
引用上面这位博主的话:
boolean: The boolean data type has only two possible values: true and false. Use this data type for simple flags that track true/false conditions. This data type represents one bit of information, but its "size" isn't something that's precisely defined.
布尔类型:布尔数据类型只有两个可能的值:真和假。使用此数据类型为跟踪真/假条件的简单标记。这种数据类型就表示这一点信息,但是它的“大小”并不是精确定义的。
可以看出,boolean类型没有给出精确的定义,《Java虚拟机规范》给出了4个字节,和boolean数组1个字节的定义,具体还要看虚拟机实现是否按照规范来,所以1个字节、4个字节都是有可能的。这其实是运算效率和存储空间之间的博弈,两者都非常的重要。
字符型
char:
- char类型是一个单一的16位Unicode字符;用 ‘’表示一个字符。。java 内部使用Unicode字符集。。他有一些转义字符 ,2字节
- 最小值是’\u0000’(即为0);
- char数据类型可以储存任何字符;
- 最大值是’\uffff’(即为65,535);可以当整数来用,它的每一个字符都对应一个数字
- 可以存放汉字,字母和数字占一个字节,一个字节8位,中文占2个字节,16位
- char 的值使用单引号 ‘s’
参考地址https://www.cnblogs.com/1130136248wlxk/articles/5105524.html
说完了基本类型,String是什么String是一个类,是引用类型。
重点:
Java为String类型提供了缓冲池机制,当使用双引号定义对象时,Java环境首先去字符串缓冲池寻找内容相同的字符串,如果存在就拿出来使用,否则就创建一个新的字符串放在缓冲池中。
例如: String S=new String("abc''), 产生(或者创建)几个对象?
答案是:产生一个或者两个对象。如果常量池中原来没有“abc",就产两个对象,如果字符串常量池中"abc",就产生一个对象。
因此,这个问题如果换成 String str = new String("abc")涉及到几个String对象?合理的解释是2个。
在Java中,被final修饰的类是不允许被继承的,并且该类中的成员方法都默认为final方法。
在这里要永远记住一点:“String对象一旦被创建就是固定不变的了,对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象”。
字符串常量池:
VM为了提高性能和减少内存的开销,在实例化字符串的时候进行了一些优化:使用字符串常量池。
每当我们创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。由于String字符串的不可变性我们可以十分肯定常量池中一定不存在两个相同的字符串。
其他String的讲解请参考
https://blog.csdn.net/qauchangqingwei/article/details/80831797
所以~ 字符串是具有缓存机制的,多线程中如果使用string最晚lock锁,可能会出现以下问题:
使用不同的锁建立异步机制,但是结果是使用同一个锁:
public static void main(String[] args) { Test2 test2 = new Test2(); String a; Thread t = new Thread(new Runnable() { @Override public void run() { test2.judge("A","AA"); } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { test2.judge("A","BB"); } }); t.start(); t2.start(); } public void judge(String what,String param) { synchronized (what) { while (true){ System.out.println(param); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
输出结果:
线程t2 的没有抢到资源,因为同步锁(对象监视器是“A”),线程A和线程B的锁实际上是同一个锁,因为String的缓存机制,“A”在编译器就被放在字符串常量池中,导致实际指向的是同一个对象。
如果使用new String 的方式就没问题了,问new String 实际上会在堆中再创建一个对象。
二、死锁
当程序中出现某一个线程等待一个永远都不会释放的锁时,就会出现死锁。
例如2个线程A和B,调用同一个实例对象的方法C和方法D,方法C和D中使用同步块绑定了对象锁E和F。
或者说有一个锁A,线程A和线程B要用用,但是锁A是永远不会释放~咋整- 死锁。
public static void main(String[] args) { TestThread testThread = new TestThread(); Thread t = new Thread(testThread,"线程A"); Thread t2 = new Thread(testThread,"线程B"); testThread.setUsername("A"); try { t.start(); Thread.sleep(100); testThread.setUsername("B"); t2.start(); } catch (InterruptedException e) { e.printStackTrace(); } } static class TestThread implements Runnable { private String username; Object a = new Object(); Object b = new Object(); public void setUsername(String username) { this.username = username; } @Override public void run() { if (username.equals("A")) { synchronized (a) { try { System.out.println(Thread.currentThread().getName()); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (b) { System.out.println("A OVER"); } } } else { synchronized (b) { try { System.out.println(Thread.currentThread().getName()); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (a) { System.out.println("B OVER"); } } } } }
输出:
没有OVER ,线程A在等线程B的对象锁b,线程B在等线程A的对象锁a。
查询:
通过到jdk的bin目录查询状态。
由此监测到了死锁状态。