关键字学习——this、static、final、throw、synchinizd、volatile

关键字

this 关键字

this 描述的本类结构调用的关键字

  1. 当前类中的属性:this.属性
  2. 当前类中的方法(普通方法、构造方法):this() 、this.方法名称
  3. 描述当前对象

当前类中的属性: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是以一个用于声明程序结构的关键字。此关键字

  1. 可以用于全局属性和全局声明。
  2. 可以避免对象的实例化限制,
  3. 在没有实例化对象的时候直接进行此类结构的访问

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 定义方法

  1. 和属性差不多,没有实例化也可以调用。普通方法必须在实例化才可以调用。
  2. static 定义的方法不能调用非static 的方法和属性
  3. 非 static 定义的方法能调用static 的方法和属性
  4. 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关键字

在程序中描述为终接器概念。可以实现一下的功能。

  1. final 定义类不能被继承
  2. final 定义方法不能被覆写
  3. 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中的关键字,是一种同步锁。它修饰的对象有以下几种:

  1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
  2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
  3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
  4. 修改一个类,其作用的范围是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
   }
}
  1. synchronized关键字不能继承
  2. 在定义接口方法时不能使用synchronized关键字
  3. 构造方法不能使用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个功能:

  1. 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成
  2. 它会强制将对缓存的修改操作立即写入主存
  3. 如果是写操作,它会导致其他CPU中对应的缓存行无效。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值