【Java多线程】对象及变量的并发访问——synchronized关键字(下)

    上一篇文章主要讲解了synchronized关键字的作用和在各种情况下的各种用法,本篇将对于并发访问对象的相关知识进行一些补充。

一、String的常量池特性

    在JVM中String具有常量池的缓存功能,比如

    String a = "abc";

    String b = "abc";

    System.out.println(a==b);

    此程序将输出true,这是因为用这种方式初始化a和b,结果就是ab所指向的是同一个对象,a、b只是这个“abc”字符串的两个不同的引用,而要使他们只想不同的对象,则需要分别调用String的构造函数new String("abc");

    如下定义一个类,其中print方法内将传入的String参数当作对象监视器,每隔两秒输出当前线程的线程名。

public class Service {
	public void print(String str) {
		try {
                    synchronized(str) {
            	        while(true) {
    				System.out.println("ThreadName="+Thread.currentThread().getName());
    				Thread.sleep(1000);
    			}
                    }
	  	}catch(InterruptedException e) {
			e.printStackTrace();
		}
	}
}

定义两个线程ThreadA和ThreadB

public class ThreadA extends Thread{
	private Service service;
	public ThreadA(Service service) {
		super();
		this.service = service;
	}
	@Override
	public void run() {
		service.print("aa");;
	}
}
public class ThreadB extends Thread{
	private Service service;
	public ThreadB(Service service) {
		super();
		this.service = service;
	}
	@Override
	public void run() {
		service.print("aa");
	}
}

main函数如下:

	public static void main(String args[]) {
		Service service = new Service();
		ThreadA t1 = new ThreadA(service);
		ThreadB t2 = new ThreadB(service);
		t1.setName("A");
		t2.setName("B");
		t1.start();
		t2.start();
	}

运行结果为:

ThreadName=A

ThreadName=A

ThreadName=A

ThreadName=A

ThreadName=A

可以看到线程A执行而线程B没有得到执行,因为两个线程在调用print方法的时候输入的参数是同一个String。

将上述的service.print(new String("aa"));再次运行程序可以看到结果为:

ThreadName=B

ThreadName=B

ThreadName=A

ThreadName=B

ThreadName=A

ThreadName=A

ThreadName=B


二、用同步块解决无限等待

    设想一下有两个线程同时调用一个对象的两个不同的synchronized方法,但是方法A内部存在一个无限循环,则线程1调用A后,进入无限循环,同时无限持有对象object的锁,导致线程2不能获得锁,从而陷入无限等待。

    这时,我们可以将两个方法改为非同步方法,然后分别在两个方法内部把需要同步执行的代码放入同步块内,创建两个不同的object对象用来设置同步块,这样两个方法执行的时候便持有不同的对象锁,也就可以异步执行了。

三、内置类的同步访问

    首先我们要分清楚内置类和静态内置类这两个概念。内置类是存在于一个类内部的另一个不同的类。若有一个类PublicClass的实例publicClass,此类内部有一个内部类PrivateClass,则在本类内部实例化内置类的时候,可以使用PrivateClass privateClass = publicClass.new PrivateClass(); 而对于静态内置类,实例化对象时则直接使用PrivateClass privateClass = new PrivateClass();即可。

    而在访问内置类的时候,有关锁的原则其实与实例变量同步访问是相同的,关键就是看“谁持有谁的锁”,只要两个不同的线程持有的是同一个内部类的锁,则两个线程的任务同步执行;如果一个任务持有了一个内部类的锁,另一个任务要访问此内部类的同步方法时需要等待,但是要访问非同步方法时则可异步执行。

四、对象发生改变时锁改变

    假设有两个线程任务,要同步访问一个String类型的实例变量str="aaa",我们将str设为对象监视器即synchronized(str),当第一个线程访问结束时,执行str="bbb";并让线程1 sleep(1000);如果在启动线程1一小段时间后启动线程2,我们会发现,虽然看似线程1在休眠期间仍旧持有str的锁,但是线程2可以异步执行,这是为什么呢?

    原因就是当我们将str="bbb"以后,线程2执行到竞争str锁的代码时,会自动去竞争"bbb"的锁,而线程1持有的仍然是"aaa"的锁,这两个线程竞争的锁变得不再一样,自然可以异步执行。所以我们还是要强调,锁是用来锁住对象的,是对象的锁,不是方法的锁也不是引用名的锁。

    但是如果一开始就连续启动两个线程,则两个线程都要去竞争"aaa"的锁,就算线程1将str改成"bbb",线程2依旧会继续等待"aaa"的锁释放;如果线程1和线程2竞争的是一个object对象,线程1将object内的属性做了修改,仍然不会影响线程2对于这个对象锁的竞争,因为对象本身还是那个对象,所以线程依旧竞争这个对象的锁。



猜你喜欢

转载自blog.csdn.net/u012198209/article/details/80265057
今日推荐