关键字
this 关键字
this 描述的本类结构调用的关键字
- 当前类中的属性:this.属性
- 当前类中的方法(普通方法、构造方法):this() 、this.方法名称
- 描述当前对象
当前类中的属性:this.属性
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void tell(){
//this 调用本类属性
System.out.println("名字==="+this.name+"年龄==="+this.age);
}
}
class test{
public static void main(String[] args) {
Person person= new Person("吴华亮", 12);
person.tell();
}
}
当前类中的方法(普通方法、构造方法):this() 、this.方法名称
package com.guanjianzi.thi;
public class Person {
private String name;
private int age;
public Person(String name, int age) {
//this 调用本类的setName 方法
this.setName(name);
//不使用this ,也可以调用
setAge(age);
}
public void tell(){
//this 调用本类属性
System.out.println("名字==="+this.name+"年龄==="+this.age);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
class test{
public static void main(String[] args) {
Person person= new Person("吴华亮", 12);
person.tell();
}
}
构造方法
package com.guanjianzi.thi;
public class Person {
private String name;
private int age;
public Person() {
System.out.println("无参构造函数实例化");
}
public Person(String name) {
this(); //调用本类无参构造
this.name = name;
}
public Person(String name, int age) {
//调用本类单参构造
this(name);
this.age=age;
}
public void tell(){
//this 调用本类属性
System.out.println("名字==="+this.name+"年龄==="+this.age);
}
}
class test{
public static void main(String[] args) {
Person person= new Person("吴华亮", 12);
person.tell();
}
}
public class Emp {
private long empp;
private String ename;
private String dept;
private double salary;
public Emp() {
//调用四参构造函数
this(1000,"帅气",null,200);
}
public Emp(long empp) {
//调用四参构造函数
this(empp,"帅气",null,200);
}
public Emp(long empp, String ename, String dept) {
//调用四参构造函数
this(empp,ename,dept,200);
}
public Emp(long empp, String ename, String dept, double salary) {
this.empp = empp;
this.ename = ename;
this.dept = dept;
this.salary = salary;
}
@Override
public String toString() {
return "Emp{" +
"empp=" + empp +
", ename='" + ename + '\'' +
", dept='" + dept + '\'' +
", salary=" + salary +
'}';
}
}
class test1{
public static void main(String[] args) {
Emp emp = new Emp();
System.out.println(emp.toString());
}
}
表示当前对象
public class Message {
public void printhis(){
System.out.println("this========"+this);
}
}
class javademo{
public static void main(String[] args) {
Message message = new Message();
System.out.println(message);
message.printhis();
}
}
//输出是一样的编码
com.guanjianzi.thi.Message@2f4d3709
this========com.guanjianzi.thi.Message@2f4d3709
static 关键字
static是以一个用于声明程序结构的关键字。此关键字
- 可以用于全局属性和全局声明。
- 可以避免对象的实例化限制,
- 在没有实例化对象的时候直接进行此类结构的访问
static 定义属性
在属性上可以添加static 关键字。
就成为了非静态成员。从内存关系分析了两者存储的区别。
代码
public class Chinese {
private String name;
private int age;
//静态成员属性
static String country="中华人民共和国";
public Chinese(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Chinese{" +
"name='" + name + '\'' +
", age=" + age +
"国家:"+country+
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public static String getCountry() {
return country;
}
public static void setCountry(String country) {
Chinese.country = country;
}
}
@SuppressWarnings("ALL")
public class Test {
public static void main(String[] args) {
Chinese chinese= new Chinese("张三", 18);
Chinese chinese1= new Chinese("李四", 18);
Chinese chinese2= new Chinese("王五", 18);
chinese1.country ="中国";
System.out.println(chinese.toString());
System.out.println(chinese1.toString());
System.out.println(chinese2.toString());
}
}
输出:
Chinese{name='张三', age=18国家:中国}
Chinese{name='李四', age=18国家:中国}
Chinese{name='王五', age=18国家:中国}
分析
static定义的属性放到全局数据区。所有对象获取到相同的对象内容,当一个对象修改了这个属性,后面会影响到所有对象。
直接利用类名称并且没有实例化直接调用
public class Test {
public static void main(String[] args) {
System.out.println("直接访问属性==========="+Chinese.country);
Chinese.country="中国";
System.out.println("直接访问属性==========="+Chinese.country);
}
}
输出:
直接访问属性===========中华人民共和国
直接访问属性===========中国
使用static定义的属性是类属性可以直接调用,且不在堆内存和栈内存,而是在全局数据区。
static 定义方法
- 和属性差不多,没有实例化也可以调用。普通方法必须在实例化才可以调用。
- static 定义的方法不能调用非static 的方法和属性
- 非 static 定义的方法能调用static 的方法和属性
- static内部无法使用this 。因为this 为当前对象,需要实例化出来的。static可以在没实例化使用。
代码
public class Chinese {
private String name;
private int age;
static String country="中华人民共和国";
public Chinese(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Chinese{" +
"name='" + name + '\'' +
", age=" + age +
"国家:"+country+
'}';
}
public static void setCountry(String c) {
//无法使用非静态属性
//使用静态属性
country=c;
}
}
package com.guanjianzi.stat;
import com.guanjianzi.thi.Person;
@SuppressWarnings("ALL")
public class Test {
public static void main(String[] args) {
//静态方法调用
Chinese.setCountry("中国1");
Chinese chinese= new Chinese("张三", 18);
System.out.println(chinese.toString());
}
}
普通方法和静态方法的调用
public class Javademo {
public static void main(String[] args) {
//直接
print2();
//实例化
new Javademo().print();
}
public void print(){
System.out.println("1");
}
public static void print2(){
System.out.println("2");
}
}
final关键字
在程序中描述为终接器概念。可以实现一下的功能。
- final 定义类不能被继承
- final 定义方法不能被覆写
- final 定义常量(全局常量)
final 定义类不能被继承
final class Channel {
}
final 定义方法不能被覆写
class Channel {
public final void print(){
System.out.println("打印");
}
}
final 定义常量(全局常量)
final 定义常量
class Channel {
private final int ON=1;
}
final 定义全局常量
class Channel {
private static final int ON=1;
}
throw关键字
异常类的实例化对象都会由JVM默认实例化自动抛出。为了用户方便异常抛出,JVM提供了throw
public class javademo {
public static void main(String[] args) {
try {
throw new Exception("自己玩异常");
}catch (Exception e){
e.printStackTrace();
}
}
}
throw和throws的区别
throw 在代码块中使用
hrows 在方法定义中使用
instanceof 关键字
对象的向下转型存在安全隐患,为了保证转换的安全性,可以在转换前通过instanceof 关键字进行对象所属类型的判断。
语法
对象 instanceof 类
public class Javade {
public static void main(String[] args) {
Person perA = new Person();
System.out.println(perA instanceof Person);
//父类(Person) 没有通过子类实例化
System.out.println(perA instanceof Superman);
//父类(Person) 通过子类实例化
Person perB = new Superman();
System.out.println(perB instanceof Person);
System.out.println(perB instanceof Superman);
}
}
输出:
true
false
true
true
public class Person {
}
public class Superman extends Person {
}
synchinizd 关键字
小概念
线程互斥是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性
互斥即一次只允许一个线程持有某个特定的锁,一次就只有一个线程能够使用该共享数据
synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
- 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
- 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
- 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
- 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
修饰一个代码块
package com.guanjianzi.sy;
/**
* 同步线程
*/
class SyncThread implements Runnable {
private static int count;
public SyncThread() {
count = 0;
}
@Override
public void run() {
//锁住的是对象
synchronized(this) {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public int getCount() {
return count;
}
}
public class Test {
public static void main(String[] args) {
SyncThread syncThread = new SyncThread();
//两个并发线程 (thread1和thread2)访问同一个对象(syncThread)中的synchronized代码块
Thread thread1 = new Thread(syncThread, "SyncThread1");
Thread thread2 = new Thread(syncThread, "SyncThread2");
thread1.start();
thread2.start();
}
}
输出:
SyncThread1:0
SyncThread1:1
SyncThread1:2
SyncThread1:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread2:7
SyncThread2:8
SyncThread2:9
分析
在同一时刻只能有一个线程得到执行,另一个线程受阻塞,必须等待当前线程执行完这个代码块以后才能执行该代码块。Thread1和thread2是互斥的,因为在执行synchronized代码块时会锁定当前的对象,只有执行完该代码块才能释放该对象锁,下一个线程才能执行并锁定该对象。所以先是 thread1 先执行完释放后, 才轮到thread2。
不是互斥出现的问题
public class Test {
public static void main(String[] args) {
SyncThread syncThread = new SyncThread();
Thread thread1 = new Thread(new SyncThread(), "SyncThread1");
Thread thread2 = new Thread(new SyncThread(), "SyncThread2");
thread1.start();
thread2.start();
}
}
输出:
SyncThread1:0
SyncThread2:1
SyncThread1:2
SyncThread2:2
SyncThread2:3
SyncThread1:3
SyncThread2:4
SyncThread1:5
SyncThread1:7
SyncThread2:6
为什么thread1和thread2同时在执行
因为synchronized只锁定一个对象,每个对象只有一个锁(lock)与之相关联,所以上面的代码形成两把锁。分别锁定syncThread1对象和syncThread2对象,这两把锁是互不干扰的,不形成互斥,所以两个线程可以同时执行
解决上面问题(锁字节码)
/**
* 同步线程
*/
class SyncThread implements Runnable {
private static int count;
public SyncThread() {
count = 0;
}
@Override
public void run() {
//锁住的是字节码
synchronized(SyncThread.class) {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public int getCount() {
return count;
}
}
public class Test {
public static void main(String[] args) {
SyncThread syncThread = new SyncThread();
Thread thread1 = new Thread(new SyncThread(), "SyncThread1");
Thread thread2 = new Thread(new SyncThread(), "SyncThread2");
thread1.start();
thread2.start();
}
}
当一个线程访问对象的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该对象中的非synchronized(this)同步代码块。
package com.guanjianzi.sy;
class Counter implements Runnable{
private int count;
public Counter() {
count = 0;
}
public void countAdd() {
synchronized(this) {
for (int i = 0; i < 5; i ++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//非synchronized代码块,未对count进行读写操作,所以可以不用synchronized
public void printCount() {
for (int i = 0; i < 5; i ++) {
try {
System.out.println(Thread.currentThread().getName() + " count:" + count);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
if (threadName.equals("A")) {
//线程1访问 synchronized代码块
countAdd();
} else if (threadName.equals("B")) {
//线程1访问 synchronized代码块
printCount();
}
}
}
package com.guanjianzi.sy;
public class Test {
public static void main(String[] args) {
Counter counter = new Counter();
//线程1访问 synchronized代码块
Thread thread1 = new Thread(counter, "A");
//线程2访问 非synchronized代码块
Thread thread2 = new Thread(counter, "B");
thread1.start();
thread2.start();
}
}
输出:
A:0
B count:1
A:1
B count:2
A:2
B count:2
A:3
B count:4
A:4
B count:5
上面代码中countAdd是一个synchronized的,printCount是非synchronized的。从上面的结果中可以看出一个线程访问一个对象的synchronized代码块时,别的线程可以访问该对象的非synchronized代码块而不受阻塞。
指定要给某个对象加锁
package com.guanjianzi.sy.demo;
/**
* 银行账户类
*/
class Account {
String name;
float amount;
public Account(String name, float amount) {
this.name = name;
this.amount = amount;
}
//存钱
public void deposit(float amt) {
amount += amt;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//取钱
public void withdraw(float amt) {
amount -= amt;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public float getBalance() {
return amount;
}
}
/**
* 账户操作类
*/
class AccountOperator implements Runnable{
private Account account;
public AccountOperator(Account account) {
this.account = account;
}
@Override
public void run() {
synchronized (account) {
account.deposit(500);
account.withdraw(500);
System.out.println(Thread.currentThread().getName() + ":" + account.getBalance());
}
}
}
public class Test {
public static void main(String[] args) {
Account account = new Account("zhang san", 10000.0f);
AccountOperator accountOperator = new AccountOperator(account);
final int THREAD_NUM = 5000;
Thread threads[] = new Thread[THREAD_NUM];
for (int i = 0; i < THREAD_NUM; i ++) {
threads[i] = new Thread(accountOperator, "Thread" + i);
threads[i].start();
}
}
}
分析
这里的 Account 对象相当于一个厕所,每个线程相当于上厕所的人, synchronized (account) 相当于厕所加了个锁。进去之后,等一个线程结束后轮到下一个。
public void method3(SomeObject obj)
{
//obj 锁定的对象
synchronized(obj)
{
// todo
}
}
修饰一个方法
public synchronized void method()
{
// todo
}
等价于
public void method()
{
synchronized(this) {
// todo
}
}
- synchronized关键字不能继承
- 在定义接口方法时不能使用synchronized关键字
- 构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步。
虽然可以使用synchronized来定义方法,但synchronized并不属于方法定义的一部分,因此,synchronized关键字不能被继承。如果在父类中的某个方法使用了synchronized关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized关键字才可以。当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步了。这两种方式的例子代码如下:
在子类方法中加上synchronized关键字
静态方法
//静态方法
private static void test() {
//不能用this ,因为静态方法
//锁定的是字节码
synchronized (Client.class){
count--;
}
}
等价于
//静态方法
private synchronized void test() {
count--;
}
实例
package com.guanjianzi.sy.dem1;
/**
* 同步线程
*/
class SyncThread implements Runnable {
private static int count;
public SyncThread() {
count = 0;
}
public synchronized static void method() {
for (int i = 0; i < 5; i ++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public synchronized void run() {
method();
}
}
public class Teast {
public static void main(String[] args) {
SyncThread syncThread1 = new SyncThread();
SyncThread syncThread2 = new SyncThread();
Thread thread1 = new Thread(syncThread1, "SyncThread1");
Thread thread2 = new Thread(syncThread2, "SyncThread2");
thread1.start();
thread2.start();
}
}
输出:
SyncThread1:0
SyncThread1:1
SyncThread1:2
SyncThread1:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread2:7
SyncThread2:8
SyncThread2:9
分析
syncThread1和syncThread2是SyncThread的两个对象,但在thread1和thread2并发执行时却保持了线程同步。这是因为run中调用了静态方法method,而静态方法是属于类的,所以syncThread1和syncThread2相当于用了同一把锁。这与Demo1是不同的。
修饰一个类
和上面的解决问题是一样的
synchronized作用于一个类T时,是给这个类T加锁,T的所有对象用的是同一把锁。
class ClassName {
public void method() {
synchronized(ClassName.class) {
// todo
}
}
}
此部分借鉴
volatile关键字
概念
volatile是Java提供的一种轻量级的同步机制。Java 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量,相比于synchronized(synchronized通常称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度。但是volatile 变量的同步性较差(有时它更简单并且开销更低),而且其使用也更容易出错。
小知识
并发编程的3个基本概念
1.原子性
定义: 即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
原子性是拒绝多线程操作的,不论是多核还是单核,具有原子性的量,同一时刻只能有一个线程来对它进行操作。简而言之,在整个操作过程中不会被线程调度器中断的操作,都可认为是原子性。例如 a=1是原子性操作,但是a++和a +=1就不是原子性操作。Java中的原子性操作包括:
(1)基本类型的读取和赋值操作,且赋值必须是值赋给变量,变量之间的相互赋值不是原子性操作。
(2)所有引用reference的赋值操作
(3)java.concurrent.Atomic.* 包中所有类的一切操作
2.可见性
定义:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
在多线程环境下,一个线程对共享变量的操作对其他线程是不可见的。Java提供了volatile来保证可见性,当一个变量被volatile修饰后,表示着线程本地内存无效,当一个线程修改共享变量后他会立即被更新到主内存中,其他线程读取共享变量时,会直接从主内存中读取。当然,synchronize和Lock都可以保证可见性。synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。
3.有序性
定义:即程序执行的顺序按照代码的先后顺序执行。
Java内存模型中的有序性可以总结为:如果在本线程内观察,所有操作都是有序的;如果在一个线程中观察另一个线程,所有操作都是无序的。前半句是指“线程内表现为串行语义”,后半句是指“指令重排序”现象和“工作内存主主内存同步延迟”现象。
在Java内存模型中,为了效率是允许编译器和处理器对指令进行重排序,当然重排序不会影响单线程的运行结果,但是对多线程会有影响。Java提供volatile来保证一定的有序性。最著名的例子就是单例模式里面的DCL(双重检查锁)。另外,可以通过synchronized和Lock来保证有序性,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。
保证内存可见性不保证原子性
如上图所示,所有线程的共享变量都存储在主内存中,每一个线程都有一个独有的工作内存,每个线程不直接操作在主内存中的变量,而是将主内存上变量的副本放进自己的工作内存中,只操作工作内存中的数据。当修改完毕后,再把修改后的结果放回到主内存中。每个线程都只操作自己工作内存中的变量,无法直接访问对方工作内存中的变量,线程间变量值的传递需要通过主内存来完成。
上述的Java内存模型在单线程的环境下不会出现问题,但在多线程的环境下可能会出现脏数据,例如:如果有AB两个线程同时拿到变量i,进行递增操作。A线程将变量i放到自己的工作内存中,然后做+1操作,然而此时,线程A还没有将修改后的值刷回到主内存中,而此时线程B也从主内存中拿到修改前的变量i,也进行了一遍+1的操作。最后A和B线程将各自的结果分别刷回到主内存中,看到的结果就是变量i只进行了一遍+1的操作,而实际上A和B进行了两次累加的操作,于是就出现了错误。究其原因,是因为线程B读取到了变量i的脏数据的缘故。
上面的内容通俗来将:通俗来说就是,线程A对一个volatile变量的修改,对于其它线程来说是可见的,即线程每次获取volatile变量的值都是最新的。
案例
public class Test {
private static volatile int count = 0;
public static void main(String[] args){
Thread[] threads = new Thread[5];
for(int i = 0; i<5; i++){
threads[i] = new Thread(()->{
try{
for(int j = 0; j<10; j++){
System.out.println(++count);
Thread.sleep(500);
}
}catch (Exception e){
e.printStackTrace();
}
});
threads[i].start();
}
}
}
输出:
8
7
5
6
9
11
12
10
9
13
17
16
14
15
18
22
21
20
1
输出的结果证明:
仅仅保证数据的可见性并不能保证线程安全。
首先我们需要明确的是,count++操作并不是原子操作,因为自增操作包括三个基本指令:读取数据、计算数据、返回结果,可以看看i++相关的字节码:
Code:
0: getstatic //读取原数据
3: iconst_1 //定义常量1
4: iadd //计算数据
5: putstatic //输出结果 // Field count:I
8: return
现象一:数据没有按顺序输出
假如线程A获取执行权,并在“返回结果”后停止(未打印),而转为线程B执行操作,巧合的是线程B这三步操作在一个时间片中完成:读取数据、计算数据、返回结果、打印数据,然后时间片转回线程A,线程打印刚刚计算的数据,此时就会发生先打印的数据比后打印的数据大的问题。
现象二:数据输出重复
就是不能保证原子性。原子性上面有讲。
解决原子性问题
可以通过synchronized或lock,进行加锁,来保证操作的原子性。也可以通过使用AtomicInteger。
package com.guanjianzi.vola;
import java.util.concurrent.atomic.AtomicInteger;
public class Test2 {
private static volatile AtomicInteger count = new AtomicInteger();
public static void main(String[] args){
Thread[] threads = new Thread[5];
for(int i = 0; i<5; i++){
threads[i] = new Thread(()->{
try{
for(int j = 0; j<5; j++){
System.out.println(count.incrementAndGet());
Thread.sleep(500);
}
}catch (Exception e){
e.printStackTrace();
}
});
threads[i].start();
}
}
}
或者
public class Test2 {
//AtomicInteger 保证了原子性和可见性
private static int count = 0;
public static void main(String[] args){
Thread[] threads = new Thread[5];
for(int i = 0; i<5; i++){
threads[i] = new Thread(()->{
try{
for(int j = 0; j<5; j++){
synchronized (Test2.class){
System.out.println(count++);
}
Thread.sleep(500);
}
}catch (Exception e){
e.printStackTrace();
}
});
threads[i].start();
}
}
}
禁止指令重排序
指令的执行顺序并不一定会像我们编写的顺序那样执行,为了保证执行上的效率,JVM(包括CPU)可能会对指令进行重排序。比方说下面的代码:
int i = 1;
int j = 2;
上述的两条赋值语句在同一个线程之中,根据程序上的次序,“int i = 1;”的操作要先行发生于“int j = 2;”,但是在多线程:“int j = 2;”的代码完全可能会被处理器先执行。JVM会保证在单线程的情况下,重排序后的执行结果会和重排序之前的结果一致。但是在多线程的场景下就不一定了。而volatile确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
案例
单例的演变
非线程的单例
public class LazySingleton {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
System.out.println(LazySingleton.getInstance().hashCode());
}).start();
}
}
private static LazySingleton instance=null;
private LazySingleton() {
}
public static LazySingleton getInstance(){
if (instance==null){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance=new LazySingleton();
}
return instance;
}
}
输出:
1074159941
50896295
978697199
1535968232
452907053
1804498809
1519046171
646019090
357327742
649755825
线程——安全
/**
* Lazy loading
* 懒汉式:什么时候用,什么时候初始化
* 可以通过 synchronized 解决非线程安全问题,但是也让效率降低了很多。
*/
public class LazySingleton {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
System.out.println(LazySingleton.getInstance().hashCode());
}).start();
}
}
private static LazySingleton instance=null;
private LazySingleton() {
}
public synchronized static LazySingleton getInstance(){
if (instance==null){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance=new LazySingleton();
}
return instance;
}
}
输出
1357369814
1357369814
1357369814
1357369814
1357369814
1357369814
1357369814
1357369814
1357369814
1357369814
懒汉式——双重判断可以保证线程安全
public class LazySingleton {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
System.out.println(LazySingleton.getInstance().hashCode());
}).start();
}
}
//在非超高并发情况下,虽然不加 volatile,代码也能运行正确,这里一定要加 volatile,防止指令重排,在超高并发情况下出问题
private static volatile LazySingleton instance=null;
private LazySingleton() {
}
// //双重检查
public synchronized static LazySingleton getInstance(){
if (instance==null){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance=new LazySingleton();
}
return instance;
}
}
volatile原理
volatile可以保证线程可见性且提供了一定的有序性,但是无法保证原子性。在JVM底层volatile是采用“内存屏障”来实现的。观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令,lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:
- 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成
- 它会强制将对缓存的修改操作立即写入主存
- 如果是写操作,它会导致其他CPU中对应的缓存行无效。