前面已经介绍了“线程安全”与”非线程安全“的相关概念。“非线程安全”其实会在多个线程对同一个对象中的实例变量进行并发访问时发生,产生的后果就是“脏读”,也就是读取到的数据其实是被更改的。而“线程安全”就是以获得的实例变量的值是进过同步处理的,不会出现脏读的现象。
方法内的变量为线程安全的
“非线程安全”问题存在与“实例变量”中,如果是方法内的私有变量,则不存在“线程安全”问题,所以结果也就是“线程安全”的。创建如下示例代码:
public class HasSelfPrivateNum {
public void addI(String username){
try{
int num = 0;
if("a".equals(username)){
num = 100;
System.out.println("a set over");
Thread.sleep(2000);
}else{
num = 200;
System.out.println("b set over");
}
System.out.println(username+" num="+num);
}catch(Exception e){
e.printStackTrace();
}
}
}
public class MyThreadA extends Thread{
private HasSelfPrivateNum num;
public MyThreadA(HasSelfPrivateNum num){
this.num = num;
}
@Override
public void run() {
num.addI("a");
}
}
public class MyThreadB extends Thread{
private HasSelfPrivateNum num;
public MyThreadB(HasSelfPrivateNum num){
this.num = num;
}
@Override
public void run() {
num.addI("b");
}
}
public class Run {
public static void main(String[] args) {
HasSelfPrivateNum num = new HasSelfPrivateNum();
MyThreadA a = new MyThreadA(num);
a.start();
MyThreadB b = new MyThreadB(num);
b.start();
}
}
执行结果如下:
可见,方法中的变量不存在非线程安全问题,永远是线程安全的。这是方法内部变量是私有特性造成的。
实例变量非线程安全
如果多个线程共同访问1个对象中的实例变量,则有可能出现“非线程安全”问题。创建如下代码:
public class HasSelfPrivateNum {
private int num = 0;
public void addI(String username){
if("a".equals(username)){
num = 100;
System.out.println("a set over!");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
num = 200;
System.out.println("b set over!");
}
System.out.println(username + " num="+num);
}
}
public class ThreadA extends Thread{
private HasSelfPrivateNum num;
public ThreadA(HasSelfPrivateNum num){
this.num = num;
}
@Override
public void run() {
num.addI("a");
}
}
public class ThreadB extends Thread{
private HasSelfPrivateNum num;
public ThreadB(HasSelfPrivateNum num) {
this.num = num;
}
@Override
public void run() {
num.addI("b");
}
}
public class Run {
public static void main(String[] args) {
HasSelfPrivateNum num = new HasSelfPrivateNum();
ThreadA ta = new ThreadA(num);
ta.start();
ThreadB tb = new ThreadB(num);
tb.start();
}
}
执行结果如下:
本程序是两个线程同时访问一个没有同步的方法,如果两个线程同时操作业务对象中的实例变量,则有可能会出现“非线程安全”问题。只需在方法前面加上synchronized即可,代码改为如下:
public class HasSelfPrivateNum {
private int num = 0;
synchronized public void addI(String username){
if("a".equals(username)){
num = 100;
System.out.println("a set over!");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
num = 200;
System.out.println("b set over!");
}
System.out.println(username + " num="+num);
}
}
执行结果如下:
多个对象多个锁:
创建如下代码:
public class HasSelfPrivateNum {
private int num = 0;
synchronized public void addI(String username){
if("a".equals(username)){
num = 100;
System.out.println("a set over");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
num = 200;
System.out.println("b set over");
}
System.out.println(username+" num="+num);
}
}
public class ThreadA extends Thread{
private HasSelfPrivateNum num;
public ThreadA(HasSelfPrivateNum num){
this.num = num;
}
@Override
public void run() {
num.addI("a");
}
}
public class ThreadB extends Thread{
private HasSelfPrivateNum num;
public ThreadB(HasSelfPrivateNum num){
this.num = num;
}
@Override
public void run() {
num.addI("b");
}
}
public class Run {
public static void main(String[] args) {
HasSelfPrivateNum num1 = new HasSelfPrivateNum();
HasSelfPrivateNum num2 = new HasSelfPrivateNum();
ThreadA t1 = new ThreadA(num1);
t1.start();
ThreadB t2 = new ThreadB(num2);
t2.start();
}
}
执行结果如下:
上面示例是两个线程分别访问同一个类的两个不同实例的相同名称的同步方法,效果确是以异步的方式运行的。本示例由于创建了2个业务对象,在系统中产生出2个锁,所以运行结果是异步的,打印结果就是先打印b,再打印a。
关键字synchronized取得的锁都是对象锁,而不是把一段代码或方法当作锁。所以在上面的示例中,哪个线程先执行带synchronized关键字的方法,哪个线程就持有该方法所属对象的锁Lock,那么其他线程只能呈等待状态,前提是多个线程访问的是一个对象。
但是如果多个线程访问多个对象,则JVM会创建多个锁。上面的示例就是创建了2个HasSelfPrivateNum类的对象,所以就会产生2个锁。
synchronized方法与锁对象:
为了证明线程锁的是对象,创建如下示例代码:
public class MyObject {
public void methodA(){
System.out.println("begin methodA threadName="+Thread.currentThread().getName());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end");
}
}
public class ThreadA extends Thread{
private MyObject object;
public ThreadA(MyObject object){
this.object = object;
}
@Override
public void run() {
object.methodA();
}
}
public class ThreadB extends Thread{
private MyObject object;
public ThreadB(MyObject object){
this.object = object;
}
@Override
public void run() {
object.methodA();
}
}
public class Run {
public static void main(String[] args) {
MyObject object = new MyObject();
ThreadA a = new ThreadA(object);
a.setName("A");
ThreadB b= new ThreadB(object);
b.setName("B");
a.start();
b.start();
}
}
执行结果如下:
修改MyObject类的代码如下:
public class MyObject {
synchronized public void methodA(){
System.out.println("begin methodA threadName="+Thread.currentThread().getName());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end");
}
}
加上synchronized关键字后再次运行Run,结果如下:
通过上面运行的示例结果如下,调用关键字synchronized申明的方法一定是排队运行的。另外还需要资源共享,只有共享资源的读写访问才需要同步,如果不是共享资源,没有必要同步。
那么其他的方法被调用时会是什么效果。创建如下代码:
public class MyObject {
synchronized public void methodA(){
System.out.println("begin methodA threadName="+Thread.currentThread().getName());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end");
}
public void methodB(){
System.out.println("begin methodB threadName="+Thread.currentThread().getName());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end");
}
}
public class ThreadA extends Thread{
private MyObject object;
public ThreadA(MyObject object){
this.object = object;
}
@Override
public void run() {
object.methodA();
}
}
public class ThreadB extends Thread{
private MyObject object;
public ThreadB(MyObject object){
this.object = object;
}
@Override
public void run() {
object.methodB();
}
}
public class Run {
public static void main(String[] args) {
MyObject object = new MyObject();
ThreadA a = new ThreadA(object);
a.setName("A");
ThreadB b= new ThreadB(object);
b.setName("B");
a.start();
b.start();
}
}
执行结果如下:
通过上面运行结果可以得知,虽然线程A先持有了MyObject对象的锁,但是线程B完全可以异步调用非synchronized类型的方法。
继续修改代码,将MyObject文件中的methodB方法加上synchronized关键字,代码如下:
public class MyObject {
synchronized public void methodA(){
System.out.println("begin methodA threadName="+Thread.currentThread().getName());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end");
}
synchronized public void methodB(){
System.out.println("begin methodB threadName="+Thread.currentThread().getName());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end");
}
}
在执行Run类的main方法,执行结果如下:
此实验的结论如下:
- A线程先持有MyObject对象的Lock锁,B线程可以以异步的方式调用MyObject对象的非synchronized类型的方法。
- 2、A线程先持有MyObject对象的Lock锁,B线程如果这个时候调用MyObject对象中的synchronized类型的方法则需要等待,也就是同步。
脏读
前面已经实现了多线程调用一个方法时,为了避免数据出现交叉的情况,使用synchronized关键字进行同步。
虽然进行了同步,但在取值时可能出现一些意想不到的意外,这种情况就是脏读。发生脏读的情况是在读取实例变量时,此值已经被其他线程更改过了。创建如下代码:
public class PublicVar {
private String username = "A";
private String password = "AA";
synchronized public void setValue(String username, String password) {
this.username = username;
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.password = password;
System.out.println("setValue method thread name=" + Thread.currentThread().getName() + " username=" + username
+ " password=" + password);
}
public void getValue(){
System.out.println("getValue method thread name=" + Thread.currentThread().getName() + " username=" + username
+ " password=" + password);
}
}
public class ThreadA extends Thread{
private PublicVar publicVar;
public ThreadA(PublicVar publicVar){
this.publicVar = publicVar;
}
@Override
public void run() {
publicVar.setValue("B", "BB");
}
}
public class Run {
public static void main(String[] args) throws InterruptedException {
PublicVar publicVar = new PublicVar();
ThreadA threadA = new ThreadA(publicVar);
threadA.start();
Thread.sleep(200);
publicVar.getValue();
}
}
运行结果如下:
出现脏读情况是因为getValue()方法不是同步的,可以在任意时候调用。解决方法给getValue()方法加上synchronized关键字。代码如下:
synchronized public void getValue(){
System.out.println("getValue method thread name=" + Thread.currentThread().getName() + " username=" + username
+ " password=" + password);
}
程序执行结果如下:
可见,setValue()和getValue()被依次执行。通过这个案例可以知道脏读可以通过synchronized关键字解决。应该还清楚如下内如:
当线程A调用anyObject对象加入synchronized关键字的A方法时,A线程就获得了X方法锁,更确切的讲是获得了对象锁,所有其他线程必须要等A线程执行完毕才可以调用X方法,但其他线程可以调用非synchronized同步方法。
当A线程调用anyObject对象加了synchronized关键字的X方法时,A线程就获得了X方法所在对象的锁,所以其他线程必须等A线程执行完毕才可以执行X方法,而B线程如果调用了声明了synchronized关键字的非X方法,必须等A线程将X方法执行完,也就是必须等释放对象锁后才可以调用。
脏读一定是在出现操作实例变量的情况下,这就是不同线程争抢实例变量的结果。
synchronized锁重入:
关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到了对象锁后,再次请求此对象锁时是可以再次得到该对象的锁的。这也证明在一个synchronized方法/块的内部调用本类的其他synchronized方法/块时,是永远可以得到锁的。创建如下代码:
public class Service {
synchronized public void service1(){
System.out.println("service1");
service2();
}
synchronized public void service2(){
System.out.println("service2");
service3();
}
synchronized public void service3(){
System.out.println("service3");
}
}
public class MyThread extends Thread{
@Override
public void run() {
Service service = new Service();
service.service1();
}
}
public class Run {
public static void main(String[] args) {
MyThread m = new MyThread();
m.start();
}
}
执行的结果如下:
可重入锁的概念是:自己可以再次获取自己的内部的锁。比如有线程A获得了某对象的锁,此时这个时候锁还没有释放,当其再次想获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。
可重入锁也支持在父子类继承的环境中。创建如下代码:
public class Main {
protected int i=10;
synchronized public void operateMainMethod(){
i--;
System.out.println("main print i="+i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Sub extends Main{
synchronized public void operateISubMethod(){
while(i>0){
i--;
System.out.println("sub print i="+i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.operateMainMethod();
}
}
}
public class MyThread2 extends Thread{
public void run() {
Sub sub = new Sub();
sub.operateISubMethod();
};
}
public class Run2 {
public static void main(String[] args) {
MyThread2 m2 = new MyThread2();
m2.start();
}
}
执行结果如下:
这也说明,子类继承父类时,子类完全可以通过可重入锁调用父类的同步方法。
出现异常,锁自动释放
当一个线程执行的代码出现异常时,其所持有的锁会自动释放。创建如下代码:
public class Service {
synchronized public void testMethod() {
if (Thread.currentThread().getName().equals("a")) {
System.out.println(
"ThreadName=" + Thread.currentThread().getName() + " run beginTime=" + System.currentTimeMillis());
int i = 1;
while (i == 1) {
if (("" + Math.random()).substring(0, 8).equals("0.123456")) {
System.out.println("ThreadName=" + Thread.currentThread().getName() + " run exceptionTime="
+ System.currentTimeMillis());
Integer.parseInt("a");
}
}
} else {
System.out.println("Thread B run time="+System.currentTimeMillis());
}
}
}
public class ThreadA extends Thread{
private Service service;
public ThreadA(Service service){
this.service = service;
}
@Override
public void run() {
service.testMethod();
}
}
public class ThreadB extends Thread{
private Service service;
public ThreadB(Service service){
this.service = service;
}
public void run() {
service.testMethod();
}
}
public class Run {
public static void main(String[] args) throws InterruptedException {
Service service = new Service();
ThreadA threadA = new ThreadA(service);
threadA.setName("a");
threadA.start();
Thread.sleep(500);
ThreadB threadB = new ThreadB(service);
threadB.setName("b");
threadB.start();
}
}
执行结果如下:
线程a出现异常并释放锁,线程b进入方法正常打印。
同步不具有继承性:
同步不可以继承。创建如下代码:
public class Main {
synchronized public void serviceMethod() {
System.out.println("int main 下一步 sleep begin threaName=" + Thread.currentThread().getName() + " time="
+ System.currentTimeMillis());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("int main 下一步 sleep end threadName=" + Thread.currentThread().getName() + " time"
+ System.currentTimeMillis());
}
}
public class Sub extends Main {
@Override
public void serviceMethod() {
System.out.println("int sub 下一步 sleep begin ThreadName=" + Thread.currentThread().getName() + " time="
+ System.currentTimeMillis());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("int sub 下一步 sleep end ThreadName=" + Thread.currentThread().getName() + " time"
+ System.currentTimeMillis());
super.serviceMethod();
}
}
public class ThreadA extends Thread{
private Sub sub;
public ThreadA(Sub sub){
this.sub = sub;
}
@Override
public void run() {
sub.serviceMethod();
}
}
public class ThreadB extends Thread{
private Sub sub;
public ThreadB(Sub sub){
this.sub = sub;
}
@Override
public void run() {
sub.serviceMethod();
}
}
public class Run {
public static void main(String[] args) {
Sub sub = new Sub();
ThreadA threadA = new ThreadA(sub);
threadA.setName("a");
threadA.start();
ThreadB threadB = new ThreadB(sub);
threadB.setName("b");
threadB.start();
}
}
执行结果如下:
修改子类的代码如下:
public class Sub extends Main {
@Override
synchronized public void serviceMethod() {
System.out.println("int sub 下一步 sleep begin ThreadName=" + Thread.currentThread().getName() + " time="
+ System.currentTimeMillis());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("int sub 下一步 sleep end ThreadName=" + Thread.currentThread().getName() + " time"
+ System.currentTimeMillis());
super.serviceMethod();
}
}
执行结果如下:
从上面可以看出,同步是不能继承的,所有还得在子类的方法中添加synchronized关键字。