java线程(三)——线程中的数据共享

一.并发和串行

在java线程中,默认抢占式执行,所以多个线程在同时开启时会出现交叉执行的情况,以如下这段代码为例,开启两个TimeThread线程,用for循环分别打印100语句,执行结果如下:

package tea;

import java.util.Date;

public class Test {

	public static void main(String[] args) {
		new TimeThread("~~~~~~~~~~~").start();
		new TimeThread("$$$$$$$$$$$").start();
	}
}

class TimeThread extends Thread{

	public TimeThread(String name) {
		super(name);
	}
	
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(getName() + new Date());
		}
	}
}

从运行结果来看,两个线程确实遵循抢占式执行的原则,二者有来有往互不相让,那么这种交叉执行的方式就是并发执行。

还有一种执行方式叫串行执行,它便是指同时开启的线程不出现交叉执行,总是先执行完其中一个线程再去执行下一个线程。那么串行执行应该如何实现呢?这就要使用接下来要说的数据共享了。

二.synchronized数据共享

1.同一个类创建的多个线程

当多个线程都是由同一个类创建的,实现串行的数据共享操作示例如下:

执行到第8行:主线程中创建一个Object类对象object。

执行到第9、10行:开启两个TimeThread类线程,向其中分别传入线程的名字和object对象。

执行到第18行:调用父类的构造方法为本线程起名字,传入的是从主线程传过来的字符串。

执行到第19行:将主线程传过来的object对象赋值给本线程中的全局变量object,由于引用类的赋值是地址传递,所以主线程开启的两个TimeThread线程中的全局变量object所指向的是同一个Object类对象,都是在主线程中创建的object。

执行到第24行:使用synchronized共享object数据,此时被该语句括起来的代码块同一时间只能有一个线程来执行了,也就是说假如“~~~”线程先抢占到段代码的执行权,则它在执行这段代码的时候,“$”线程必须处于等待状态,直到“~”线程将该段代码块完全执行完,“$”才能处于运行状态。

package tea;

import java.util.Date;

public class Test {

	public static void main(String[] args) {
		Object object = new Object();
		new TimeThread("~~~~~~~~~~~",object).start();
		new TimeThread("$$$$$$$$$$$",object).start();
	}
}

class TimeThread extends Thread{

	Object object;
	public TimeThread(String name,Object object) {
		super(name);
		this.object=object;
	}
	
	@Override
	public void run() {
		synchronized (object) {
			for (int i = 0; i < 100; i++) {
				System.out.println(getName() + new Date());
			}
		}
	}
}

以上这种实现串行的方式叫做对象锁,不难理解,synchronized关键字就像是一把锁,将所括起来的代码块都锁了起来,而共享的数据就相当于唯一的一把钥匙,当“$”线程执行到对象锁时,如果“~”线程正在执行锁中的代码块,共享的钥匙便在“~~”手中,此时“$$”线程必须等到“~~”线程执行完毕才能拿到对象锁的钥匙,然后进入代码块开始执行。

以下是运行结果:

2.不同类创建的多个线程

在上面的示例中,多个线程是由同一个类创建的,此时被对象锁锁住的代码块是同一段代码块,那么如果多个线程是由不同的类创建的,此时在用对象锁还能锁住不同类中的不同代码块吗?

示例如下:

在TimeThread类线程和CountThread线程中分别使用synchronized共享同一个Object类对象,用对象锁锁住不同的代码块,这时运行程序,发现执行结果还是串行的,说明即使是不同的类创建的多个线程,只要共享的数据是同一个,就可以锁住不同的代码块。

package tea;

import java.util.Date;

public class Test {

	public static void main(String[] args) {
		Object object = new Object();
		new TimeThread("时间线程",object).start();
		new CountThread("计数器线程",object).start();
	}
}

class TimeThread extends Thread{

	Object object;
	public TimeThread(String name,Object object) {
		super(name);
		this.object=object;
	}
	
	@Override
	public void run() {
		synchronized (object) {
			for (int i = 0; i < 100; i++) {
				System.out.println(getName() + new Date());
			}
		}
	}
}


class CountThread extends Thread{

	Object object;
	public CountThread(String name,Object object) {
		super(name);
		this.object=object;
	}
	
	@Override
	public void run() {
		synchronized (object) {
			for (int i = 0; i < 100; i++) {
				System.out.println(new Date()+getName());
			}
		}
	}
}

3.可以共享的数据

以上两个示例中,共享的数据都是一个Object类对象,然而其实只要是同一个数据都可以作为对象锁的“钥匙”的。

1.Object等类的同一个对象

2.""也可以作为共享的数据,因为它作为一个String类字符串存储在常量池中,所以synchronized时共享的一定是同一个数据。

3.XXX.class,某类的class对象也也是对象,所以也可以作为对象锁的“钥匙”。

4.this不可以,因为this代指的是调用run方法的对象,而每一个线程对象都是不同的对象,所以肯定不是同一个数据,不能作为共享的数据。

4.synchronized修饰run方法

synchronized除了可以包裹代码块以外,还可以修饰run方法,就像如下代码,但是运行结果发现还是并发执行的

package tea;

import java.util.Date;

public class Test {

	public static void main(String[] args) {
		new TimeThread("~~~~~~~~~").start();
		new TimeThread("$$$$$$$$$").start();
	}
}

class TimeThread extends Thread{

	public TimeThread(String name) {
		super(name);
	}
	
	@Override
	public synchronized void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(getName() + new Date());
		}
	}
}

这是因为在run方法上加synchronized修饰就相当于用synchronized(this)包裹代码块,其效果就是以调用run方法的对象作为对象锁的钥匙,那么这种用法真的实现不了串行执行吗?其实也不然,假如像如下代码这样使用,便可以实现串行运行了:

创建TimeThread类线程实现Runnable接口,然后通过Thread类创建TimeThread类线程,这时创建的线程对象是同一个,所以即可实现串行执行,运行结果如下:

package tea;

import java.util.Date;

public class Test {

	public static void main(String[] args) {
		TimeThread timeThread=new TimeThread();
		new Thread(timeThread,"~~~~~~~~~").start();
		new Thread(timeThread,"$$$$$$$$$").start();
	}
}

class TimeThread implements Runnable{

	@Override
	public synchronized void run() {
		Thread thread = Thread.currentThread();
		for (int i = 0; i < 100; i++) {
			System.out.println(thread.getName() + new Date());
		}
	}
}

发布了99 篇原创文章 · 获赞 93 · 访问量 5239

猜你喜欢

转载自blog.csdn.net/DangerousMc/article/details/100049674