Java访问修饰符和非访问修饰符(static、final、abstract、synchronized、transient、volatile)
原文:https://www.runoob.com/java/java-modifier-types.html
一、Java 修饰符介绍
Java语言提供了很多修饰符,主要分为以下两类:
- 访问修饰符
- 非访问修饰符
修饰符用来定义类、方法或者变量,通常放在语句的最前端。我们通过下面的例子来说明:
public class ClassName { // ... } private boolean myFlag; static final double weeks = 9.5; protected static final int BOXWIDTH = 42; public static void main(String[] arguments) { // 方法体 }
二、访问修饰符
1、四种访问修饰符的控制权限
Java中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。Java 支持 4 种不同的访问权限。
-
default (即默认,什么也不写): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
-
private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类),但是可以修饰内部类。
-
public : 对所有类可见。使用对象:类、接口、变量、方法
-
protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类),但是可以修饰内部类。
我们可以通过以下表来说明访问权限:
注意:private 和 protected 不能修饰类(外部类),但是可以修饰内部类。
为什么不能修饰外部类?
因为如果外部类可以使用private来修饰,表示该包下的这个类不能被其它类访问,那么该类也失去了存在的意义,所以不能使用private来修饰类。
如果外部类可以使用protected来修饰,表示该类所在的包的其它类可以访问该类;该类所在的包的子包的类可以访问该类,但是包没有继承的概念,所以后一句是不对。所以用protected来修饰类也是没有意义的。
综上,外部类只有public和default修饰。
2、默认访问修饰符-不使用任何关键字
使用默认访问修饰符声明的变量和方法,对同一个包内的类是可见的。接口里的变量都隐式声明为 public static final,而接口里的方法默认情况下访问权限为 public。
如下例所示,变量和方法的声明可以不使用任何修饰符。
String version = "1.5.1"; boolean processOrder() { return true; }
3、私有访问修饰符-private
私有访问修饰符是最严格的访问级别,所以被声明为 private 的方法、变量和构造方法只能被所属类访问,并且类和接口不能声明为 private。
声明为私有访问类型的变量只能通过类中公共的 getter 方法被外部类访问。
Private 访问修饰符的使用主要用来隐藏类的实现细节和保护类的数据。
下面的类使用了私有访问修饰符:
public class Logger { private String format; public String getFormat() { return this.format; } public void setFormat(String format) { this.format = format; } }
实例中,Logger 类中的 format 变量为私有变量,所以其他类不能直接得到和设置该变量的值。为了使其他类能够操作该变量,定义了两个 public 方法:getFormat() (返回 format的值)和 setFormat(String)(设置 format 的值)
4、公有访问修饰符-public
被声明为 public 的类、方法、构造方法和接口能够被任何其他类访问。
如果几个相互访问的 public 类分布在不同的包中,则需要导入相应 public 类所在的包。由于类的继承性,类所有的公有方法和变量都能被其子类继承。
以下函数使用了公有访问控制:
public static void main(String[] arguments) { // ... }
Java 程序的 main() 方法必须设置成公有的,否则,Java 解释器将不能运行该类。
5、受保护的访问修饰符-protected
protected 需要从以下两个点来分析说明:
-
子类与基类在同一包中:被声明为 protected 的变量、方法和构造器能被同一个包中的任何其他类访问;
-
子类与基类不在同一包中:那么在子类中,子类实例可以访问其从基类继承而来的 protected 方法,而不能访问基类实例的protected方法。
protected 可以修饰数据成员,构造方法,方法成员,不能修饰类(内部类除外)。
接口及接口的成员变量和成员方法不能声明为 protected。
子类能访问 protected 修饰符声明的方法和变量,这样就能保护不相关的类使用这些方法和变量。
下面的父类使用了 protected 访问修饰符,子类重写了父类的 openSpeaker() 方法。
class AudioPlayer { protected boolean openSpeaker(Speaker sp) { // 实现细节 } } class StreamingAudioPlayer extends AudioPlayer { protected boolean openSpeaker(Speaker sp) { // 实现细节 } }
如果把 openSpeaker() 方法声明为 private,那么除了 AudioPlayer 外,其他类将不能访问该方法。
如果把 openSpeaker() 声明为 public,那么所有的类都能够访问该方法。
如果我们只想让该方法对其所在类的子类可见,则将该方法声明为 protected。
protected 是最难理解的一种 Java 类成员访问权限修饰词,更多详细内容请查看 Java protected 关键字详解。
6、访问控制和继承
请注意以下方法继承的规则:
-
父类中声明为 public 的方法在子类中也必须为 public。
-
父类中声明为 protected 的方法在子类中要么声明为 protected,要么声明为 public,不能声明为 private。
-
父类中声明为 private 的方法,不能够被子类继承。
三、非访问修饰符
为了实现一些其他的功能,Java 也提供了许多非访问修饰符。
- static 修饰符,用来修饰类方法和类变量。
- final 修饰符,用来修饰类、方法和变量,final 修饰的类不能够被继承,修饰的方法不能被继承类重新定义,修饰的变量为常量,是不可修改的。
- abstract 修饰符,用来创建抽象类和抽象方法。
- synchronized 和 volatile 修饰符,主要用于线程的编程。
1、static 修饰符
-
静态变量:
static 关键字用来声明独立于对象的静态变量,无论一个类实例化多少对象,它的静态变量只有一份拷贝。 静态变量也被称为类变量。局部变量不能被声明为 static 变量。
-
静态方法:
static 关键字用来声明独立于对象的静态方法。静态方法不能使用类的非静态变量。静态方法从参数列表得到数据,然后计算这些数据。
对类变量和方法的访问可以直接使用 classname.variablename 和 classname.methodname 的方式访问。
如下例所示,static 修饰符用来创建类方法和类变量。
public class InstanceCounter { private static int numInstances = 0; protected static int getCount() { return numInstances; } private static void addInstance() { numInstances++; } InstanceCounter() { InstanceCounter.addInstance(); } public static void main(String[] arguments) { System.out.println("Starting with " + InstanceCounter.getCount() + " instances"); for (int i = 0; i < 500; ++i){ new InstanceCounter(); } System.out.println("Created " + InstanceCounter.getCount() + " instances"); } }
以上实例运行编辑结果如下:
Starting with 0 instances
Created 500 instances
2、final 修饰符
1、final 变量
final 表示"最后的、最终的"含义,变量一旦赋值后,不能被重新赋值。被 final 修饰的实例变量必须显式指定初始值。
final 修饰符通常和 static 修饰符一起使用来创建类常量。
public class Test{ final int value = 10; // 下面是声明常量的实例 public static final int BOXWIDTH = 6; static final String TITLE = "Manager"; public void changeValue(){ value = 12; //将输出一个错误 } }
2、final 方法
父类中的 final 方法可以被子类继承,但是不能被子类重写。
声明 final 方法的主要目的是防止该方法的内容被修改。
如下所示,使用 final 修饰符声明方法。
public class Test{ public final void changeName(){ // 方法体 } }
3、final 类
final 类不能被继承,没有类能够继承 final 类的任何特性。
public final class Test { // 类体 }
4、final变量初始化
final 成员变量初始化方式:
- 在定义变量时直接赋值
- 声明完变量后在构造方法中为其赋值
- 声明完变量后在构造代码块中为其赋值
- 在定义类变量时直接赋值
- 在静态代码块中赋值
public class FinalTest { //-----------------成员变量------------------// //初始化方式一,在定义变量时直接赋值 private final int i = 3; //初始化方式二,声明完变量后在构造方法中为其赋值 //如果采用用这种方式,那么每个构造方法中都要有j赋值的语句 private final int j; public FinalTest() { j = 3; } //如果取消该构造方法的注释,程序就会报错,因此它没有为j赋值 /* public FinalTest(String str) { }*/ //为了方便我们可以这样写 public FinalTest(String str) { this(); //调用无参构造器 } //下面的代码同样会报错,因为对j重复赋值 /*public FinalTest(String str1, String str2) { this(); j = 3; }*/ //初始化方式三,声明完变量后在构造代码块中为其赋值 //如果采用此方式,就不能在构造方法中再次为其赋值 //构造代码块中的代码会在构造函数之前执行,如果在构造函数中再次赋值, //就会造成final变量的重复赋值 private final int k; { k = 4; } //-----------------类变量(静态变量)------------------// //初始化方式一,在定义类变量时直接赋值 public final static int p = 3; //初始化方式二,在静态代码块中赋值 //成员变量可以在构造函数中赋值,但是类变量却不可以。 //因此成员变量属于对象独有,每个对象创建时只会调用一次构造函数, //因此可以保证该成员变量只被初始化一次; //而类变量是该类的所有对象共有,每个对象创建时都会对该变量赋值 //这样就会造成变量的重复赋值。 public final static int q; static { q = 3; } public static void main(String[] args) { } }
3、abstract 修饰符
1、抽象类
抽象类不能用来实例化对象,声明抽象类的唯一目的是为了将来对该类进行扩充。
一个类不能同时被 abstract 和 final 修饰。如果一个类包含抽象方法,那么该类一定要声明为抽象类,否则将出现编译错误。
抽象类可以包含抽象方法和非抽象方法。
abstract class Caravan { private double price; private String model; private String year; public abstract void goFast(); //抽象方法 public abstract void changeColor(); }
2、抽象方法
抽象方法是一种没有任何实现的方法,该方法的具体实现由子类提供。
抽象方法不能被声明成 final 和 static。
任何继承抽象类的子类必须实现父类的所有抽象方法,除非该子类也是抽象类。
如果一个类包含若干个抽象方法,那么该类必须声明为抽象类。抽象类可以不包含抽象方法。
抽象方法的声明以分号结尾,例如:public abstract sample();
public abstract class SuperClass{ abstract void m(); //抽象方法 } class SubClass extends SuperClass{ //实现抽象方法 void m(){ ......... } }
4、synchronized 修饰符
原文:http://blog.csdn.net/luoweifu/article/details/46613015
作者:luoweifu
转载请标名出处
synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
- 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
- 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
- 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
- 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
1.修饰一个代码块
一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞。我们看下面一个例子:
【Demo1】:synchronized的用法
/** * 同步线程 */ class SyncThread implements Runnable { private static int count; public SyncThread() { count = 0; } 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 class TestThread { public static void main(String[] args) { // SyncThread的调用: SyncThread syncThread = new SyncThread(); 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)访问同一个对象(syncThread)中的synchronized代码块时,在同一时刻只能有一个线程得到执行,另一个线程受阻塞,必须等待当前线程执行完这个代码块以后才能执行该代码块。Thread1和thread2是互斥的,因为在执行synchronized代码块时会锁定当前的对象,只有执行完该代码块才能释放该对象锁,下一个线程才能执行并锁定该对象。
我们再把SyncThread的调用稍微改一下:
public class TestThread { public static void main(String[] args) { // 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:3 SyncThread1:4 SyncThread2:5 SyncThread2:6 SyncThread1:7 SyncThread1:8 SyncThread2:9
不是说一个线程执行synchronized代码块时其它的线程受阻塞吗?为什么上面的例子中thread1和thread2同时在执行。这是因为synchronized只锁定对象,每个对象只有一个锁(lock)与之相关联,而上面的代码等同于下面这段代码:
public class TestThread { public static void main(String[] args) { // SyncThread的调用: SyncThread syncThread1 = new SyncThread(); SyncThread syncThread2 = new SyncThread(); Thread thread1 = new Thread(syncThread1, "SyncThread1"); Thread thread2 = new Thread(syncThread2, "SyncThread2"); thread1.start(); thread2.start(); } }
这时创建了两个SyncThread的对象syncThread1和syncThread2,线程thread1执行的是syncThread1对象中的synchronized代码(run),而线程thread2执行的是syncThread2对象中的synchronized代码(run);我们知道synchronized锁定的是对象,这时会有两把锁分别锁定syncThread1对象和syncThread2对象,而这两把锁是互不干扰的,不形成互斥,所以两个线程可以同时执行。
2.当一个线程访问对象的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该对象中的非synchronized(this)同步代码块。
【Demo2】:多个线程访问synchronized和非synchronized代码块
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(); } } } public void run() { String threadName = Thread.currentThread().getName(); if (threadName.equals("A")) { countAdd(); } else if (threadName.equals("B")) { printCount(); } } } public class TestThread { public static void main(String[] args) { Counter counter = new Counter(); Thread thread1 = new Thread(counter, "A"); Thread thread2 = new Thread(counter, "B"); thread1.start(); thread2.start(); } }
结果如下:
A:0 B count:1 A:1 B count:2 A:2 B count:3 A:3 B count:4 A:4 B count:5
上面代码中countAdd是一个synchronized的,printCount是非synchronized的。从上面的结果中可以看出一个线程访问一个对象的synchronized代码块时,别的线程可以访问该对象的非synchronized代码块而不受阻塞。
指定要给某个对象加锁
【Demo3】:指定要给某个对象加锁
/** * 银行账户类 */ 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; } public void run() { synchronized (account) { account.deposit(500); account.withdraw(500); System.out.println(Thread.currentThread().getName() + ":" + account.getBalance()); } } } public class TestThread { public static void main(String[] args) { Account account = new Account("zhang san", 10000.0f); AccountOperator accountOperator = new AccountOperator(account); final int THREAD_NUM = 5; Thread threads[] = new Thread[THREAD_NUM]; for (int i = 0; i < THREAD_NUM; i++) { threads[i] = new Thread(accountOperator, "Thread" + i); threads[i].start(); } } }
结果如下:
Thread3:10000.0
Thread2:10000.0
Thread1:10000.0
Thread4:10000.0
Thread0:10000.0
在AccountOperator 类中的run方法里,我们用synchronized 给account对象加了锁。这时,当一个线程访问account对象时,其他试图访问account对象的线程将会阻塞,直到该线程访问account对象结束。也就是说谁拿到那个锁谁就可以运行它所控制的那段代码。
当有一个明确的对象作为锁时,就可以用类似下面这样的方式写程序。
public void method3(SomeObject obj) { //obj 锁定的对象 synchronized(obj) { // todo } }
当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的对象来充当锁:
class Test implements Runnable { private byte[] lock = new byte[0]; // 特殊的instance变量 public void method() { synchronized(lock) { // todo 同步代码块 } } public void run() { } }
说明:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。
2.修饰一个方法
Synchronized修饰一个方法很简单,就是在方法的前面加synchronized,public synchronized void method(){//todo}; synchronized修饰方法和修饰一个代码块类似,只是作用范围不一样,修饰代码块是大括号括起来的范围,而修饰方法范围是整个函数。如将【Demo1】中的run方法改成如下的方式,实现的效果一样。
【Demo4】:synchronized修饰一个方法
/** * 同步线程 */ class SyncThread implements Runnable { private static int count; public SyncThread() { count = 0; } public synchronized void run() { for (int i = 0; i < 5; i++) { try { System.out.println(Thread.currentThread().getName() + ":" + (count++)); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class TestThread { public static void main(String[] args) { SyncThread syncThread = new SyncThread(); Thread thread1 = new Thread(syncThread, "SyncThread1"); Thread thread2 = new Thread(syncThread, "SyncThread2"); thread1.start(); thread2.start(); } }
Synchronized作用于整个方法的写法。
写法一:
public synchronized void method() { // todo }
写法二:
public void method() { synchronized(this) { // todo } }
写法一修饰的是一个方法,写法二修饰的是一个代码块,但写法一与写法二是等价的,都是锁定了整个方法时的内容。
在用synchronized修饰方法时要注意以下几点:
1. synchronized关键字不能继承。
虽然可以使用synchronized来定义方法,但synchronized并不属于方法定义的一部分,因此,synchronized关键字不能被继承。如果在父类中的某个方法使用了synchronized关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized关键字才可以。当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步了。这两种方式的例子代码如下:
在子类方法中加上synchronized关键字
class Parent { public synchronized void method() { } } class Child extends Parent { public synchronized void method() { } }
在子类方法中调用父类的同步方法
class Parent { public synchronized void method() { } } class Child extends Parent { public void method() { super.method(); } }
- 在定义接口方法时不能使用synchronized关键字。
- 构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步。
3.修饰一个静态的方法
Synchronized也可修饰一个静态方法,用法如下:
public synchronized static void method() { // todo }
我们知道静态方法是属于类的而不属于对象的。同样的,synchronized修饰的静态方法锁定的是这个类的所有对象。我们对Demo1进行一些修改如下:
【Demo5】:synchronized修饰静态方法
/** * 同步线程 */ 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(); } } } public void run() { method(); } } public class TestThread { 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是不同的。
4.修饰一个类
Synchronized还可作用于一个类,用法如下:
class ClassName { public void method() { synchronized(ClassName.class) { // todo } } }
我们把Demo5再作一些修改。
【Demo6】:修饰一个类
/** * 同步线程 */ class SyncThread implements Runnable { private static int count; public SyncThread() { count = 0; } public void method() { 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 void run() { method(); } } public class TestThread { 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(); } }
其效果和【Demo5】是一样的,synchronized作用于一个类T时,是给这个类T加锁,T的所有对象用的是同一把锁。
6.总结
A. 无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。
B. 每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。
C. 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
5、transient 修饰符
参考文章: https://blog.csdn.net/qq_44515207/article/details/120275750
1. 作用
transient关键字的主要作用就是让某些被transient关键字修饰的成员属性变量不被序列化。
2. 什么是序列化?
序列化简单来说就是将java对象转化为字节流的形式。我们在程序中创建的JAVA对象只存在于JVM中, 当程序退出时, 这些对象也就消失了, 而序列化正是为了将这些对象保存起来便于将来使用,也可以将已经序列化的对象传送给其他JVM来使用,这些序列化的字节流是于JVM无关的, 也就是说一个JVM序列化的对象可以在另一个JVM中反序列化。
反序列化就是将字节流转化为Java对象的过程。
字节流就是以字节(byte)为单位的数据流。在Java中的两个主要的接口是InputStream(输入)和OutputStream(输出)。
3. transient的用处
序列化会让可以让Java对象进行传输保存,但是也有一些数据不希望被序列化,比如密码等信息,以防这些信息序列化之后在网络中传输,然后被反序列化造成安全问题,这些信息的变量就需要加上transient关键字。
4.使用transient注意事项
- transient只能修饰变量,不能修饰方法和类
- transient不能是局部变量和静态变量(static)
- 如果对transient修饰的变量进行序列化,会对变量重新进行默认初始化(反序列之后的对象会变为null)。
public class Test { transient String password = "123abc"; Test() { System.out.println(password); } public static void main(String[] args) { new Test(); } }
5.写入文件实例
当对象被序列化时(写入字节序列到目标文件)时,transient阻止实例中那些用此关键字声明的变量持久化;当对象被反序列化时(从源文件读取字节序列进行重构),这样的实例变量值不会被持久化和恢复。
import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; //定义一个需要序列化的类 class People implements Serializable{ String name; //姓名 transient Integer age; //年龄 public People(String name,int age){ this.name = name; this.age = age; } public String toString(){ return "姓名 = "+name+" ,年龄 = "+age; } } public class TransientPeople { public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException { People a = new People("李雷",30); System.out.println(a); //打印对象的值 ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("d://people.txt")); os.writeObject(a);//写入文件(序列化) os.close(); ObjectInputStream is = new ObjectInputStream(new FileInputStream("d://people.txt")); a = (People)is.readObject();//将文件数据转换为对象(反序列化) System.out.println(a); // 年龄 数据未定义 is.close(); } }
运行结果如下:
姓名 = 李雷 ,年龄 = 30
姓名 = 李雷 ,年龄 = null
6、volatile 修饰符
volatile可以用在任何变量前面,但不能用于final变量前面,因为final型的变量是禁止修改的。
volatile 修饰的成员变量在每次被线程访问时,都强制从共享内存中重新读取该成员变量的值。而且,当成员变量发生变化时,会强制线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。
一个 volatile 对象引用可能是 null。
public class MyRunnable implements Runnable { private volatile boolean active; public void run() { active = true; while (active) // 第一行 { // 代码 } } public void stop() { active = false; // 第二行 } }
通常情况下,在一个线程调用 run() 方法(在 Runnable 开启的线程),在另一个线程调用 stop() 方法。 如果 第一行 中缓冲区的 active 值被使用,那么在 第二行 的 active 值为 false 时循环不会停止。
但是以上代码中我们使用了 volatile 修饰 active,所以该循环会停止。
应用场景1:
单例模式中采用DCL双锁检测(double checked locking)机制,在多线程访问的情况下,可使用volatitle修改,保证多线程下的可见性。缺点是性能有损失,因此单线程情况下不必用此修饰符。
class Singleton{ private volatile static Singleton instance = null; private Singleton() { } public static Singleton getInstance() { if(instance==null) { synchronized (Singleton.class) { if(instance==null) instance = new Singleton(); } } return instance; } }