跟着实例学习java多线程-2

上一篇文章我们通过一个实例来说明了并发编程为什么要做同步处理,下面我们再来巩固一下。

对象如果拥有可变状态的变量,并且被多线程访问,那么这个时候我们要对可变状态变量的状态改变做原子操作处理。

锁机制是保证这样的操作的一个有效的方法,它可以保证变量的状态在被更新时是在一个原子操作中进行的。

java提供了一种内置锁机制来支持原子性:同步代码块(Synchronized Block)。

同步代码块包括两个部分:一个是作为锁的对象引用,一个是作为由这个锁保护的代码块。

让我们在来回忆上一篇文章最后的问题:

1:我们可以看到上面例子的synchronized是加在了方法上,那么我们还可以怎么写呢?

加在方法上的synchronized锁住的是整个方法体的同步代码块,同步代码块有了,那锁的对象是什么呢?让我们来看这样一段代码:

<span style="font-size:18px;">package com.home.thread;
/**
 * @author gaoxu
 *
 */
public class SafeThread {
 int id = 0;
 @safe
 public <span style="color:#ff0000;">synchronized </span>int getId(){
   <span style="color:#ff0000;">return ++id;//锁代码块</span>
 }
}
</span>
<span style="font-size:18px;">package com.home.thread;

/**
 * @author gaoxu
 *
 */
public class ThreadStart {

	public static void main(String[] para){		</span>
<span style="font-size:18px;">		<span style="color:#ff0000;">//这里的safe就是锁对象,它是</span><span style="color:#ff0000;">SafeThread()的一个实例的引用
</span>		SafeThread safe = new SafeThread();
		for(int i=0;i<10;i++){
			ThreadRead1 t1 = new ThreadRead1(safe);
			ThreadRead2 t2 = new ThreadRead2(safe);
			t1.start();
			t2.start();
		}
	}

}
</span>

我们弄清楚了,锁对象和锁的代码块,那也就可以想出来,锁代码块的写法可以有很多种,只要我们获得了锁对象,那么不管锁代码块是怎么写的都可以实现多线程同步。

下面我们来看一下以下这几种写法:

写法1:

<span style="font-size:18px;">package com.home.thread;

/**
 * @author gaoxu
 *
 */
public class SafeThread {
	int id = 0;
	@safe
	public  int getId(){
		synchronized(SafeThread.class){
			return ++id;
		}
	}
}

</span>

写法2

<span style="font-size:18px;">package com.home.thread;

/**
 * @author gaoxu
 *
 */
public class SafeThread {
	int id = 0;
	Object sync = new Object();
	@safe
	public  int getId(){
		synchronized(sync){
			return ++id;
		}
	}
}
</span>

写法3:

<span style="font-size:18px;">package com.home.thread;

/**
 * @author gaoxu
 *
 */
public class SafeThread {
	static int id = 0;

	@safe
	public static synchronized int getId(){

			return ++id;

	}
}
</span>

使用static来修饰的方法默认是以Class对象作为锁的。

2:synchronized锁住的是对象还是代码或方法?

通过对上面问题的分析,我可以想象一下,我们锁住的到底是什么?

答案:应该是对象。

让我们使用实例来说明一切!

<span style="font-size:18px;">package com.home.thread;

/**
 * @author gaoxu
 *
 */
public class SafeThread {
	static int id = 0;

	@safe
	public static synchronized int getId(){

			return ++id;

	}

	public  synchronized void testPrint(){
		System.out.println("Enter testPrint method !");
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {

			e.printStackTrace();
		}
		System.out.println("Exit testPrint method !");
	}
}
</span>

testPrint方法已经加上了synchronized,下面我们来多线访问调用一下这个方法。

<span style="font-size:18px;">package com.home.thread;

/**
 * @author gaoxu
 *
 */
public class ThreadStart {

	public static void main(String[] para){
		for(int i=0;i<10;i++){
			ThreadRead1 t1 = new ThreadRead1();
			t1.start();
		}
	}
}
</span>
<span style="font-size:18px;">package com.home.thread;

/**
 * @author gaoxu
 *
 */
public class ThreadRead1 extends Thread{
	SafeThread safe = null;
	public ThreadRead1(){
	}
	public ThreadRead1(SafeThread o){
		safe = o;
	}

	public void run()
	{
	        safe = new SafeThread();
		safe.testPrint();

	}

}</span>

看一下结果:

<span style="font-size:18px;">Enter testPrint method !
Enter testPrint method !
Enter testPrint method !
Enter testPrint method !
Enter testPrint method !
Enter testPrint method !
Enter testPrint method !
Enter testPrint method !
Enter testPrint method !
Enter testPrint method !
Exit testPrint method !
Exit testPrint method !
Exit testPrint method !
Exit testPrint method !
Exit testPrint method !
Exit testPrint method !
Exit testPrint method !
Exit testPrint method !
Exit testPrint method !
Exit testPrint method !</span>

这样的结果有些意思,我们已经给方法加上了synchronized了,为什么不起作用呢?

原因很简单,我们看一下线程ThreadRead1实现代码,SafeThread对象是在线程中才创建的,那也就是说10个线程是创建了十个SafeThread的实例,所以即使synchronized的锁定的代码块也不起作用。

那我们修改一下代码:

<span style="font-size:18px;">package com.home.thread;

/**
 * @author gaoxu
 *
 */
public class ThreadStart {

	public static void main(String[] para){
		<span style="color:#ff0000;">SafeThread safe = new SafeThread();</span>
		for(int i=0;i<10;i++){
			ThreadRead1 t1 = new <span style="color:#ff6666;">ThreadRead1(safe)</span>;
			t1.start();
		}
	}
}
</span>
<span style="font-size:18px;">package com.home.thread;

/**
 * @author gaoxu
 *
 */
public class ThreadRead1 extends Thread{
	SafeThread safe = null;
	public ThreadRead1(){
	}
	public ThreadRead1(SafeThread o){
		safe = o;
	}

	public void run()
	{
		safe.testPrint();
	}

}
</span>

我们看下运行结果:

<span style="font-size:18px;">Enter testPrint method !
Exit testPrint method !
Enter testPrint method !
Exit testPrint method !
Enter testPrint method !
Exit testPrint method !
Enter testPrint method !
Exit testPrint method !
Enter testPrint method !
Exit testPrint method !
Enter testPrint method !
Exit testPrint method !
Enter testPrint method !
Exit testPrint method !
Enter testPrint method !
Exit testPrint method !
Enter testPrint method !
Exit testPrint method !
Enter testPrint method !
Exit testPrint method !</span>

这样的结果才是我们想要的,为什么这次synchronized管事了呢?我们看到ThreadStart类中main方法中红色的部分,我们在启动线程之前创建了SafeThread的实例及其引用,并把它的引用作为参数来初始化线程类ThreadRead1,那这时候SafeThread的实例的引用就是一个香饽饽了,谁得到谁就可以锁定synchronized锁定的代码块并执行完该块代码。

下面还有个例子请大家自己实践一下,看看结果如何?

示例:

<span style="font-size:18px;">package com.home.thread;

/**
 * @author gaoxu
 *
 */
public class ThreadStart {

	public static void main(String[] para){
		for(int i=0;i<3;i++){
			ThreadRead1 t1 = new ThreadRead1();
			t1.start();
		}
	}
}
</span>
<span style="font-size:18px;">package com.home.thread;

/**
 * @author gaoxu
 *
 */
public class ThreadRead1 extends Thread{
	SafeThread safe = null;
	public ThreadRead1(){
	}
	public ThreadRead1(SafeThread o){
		safe = o;
	}

	public void run()
	{
		safe = new SafeThread();
		safe.testPrint();
	}

}
</span>

被调用的类的两种实现如下:

实现1:

<span style="font-size:18px;">package com.home.thread;

/**
 * @author gaoxu
 *
 */
public class SafeThread {
	static int id = 0;

	@safe
	public static synchronized int getId(){

			return ++id;

	}

	public   void testPrint(){
		<span style="color:#ff0000;">synchronized(SafeThread.class)</span>{
			System.out.println("Enter testPrint method !");
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {

				e.printStackTrace();
			}
			System.out.println("Exit testPrint method !");
		}

	}
}
</span>

实现2:

<span style="font-size:18px;">package com.home.thread;

/**
 * @author gaoxu
 *
 */
public class SafeThread {
	static int id = 0;

	@safe
	public static synchronized int getId(){

			return ++id;

	}

	public <span style="color:#ff0000;">static synchronized </span>  void testPrint(){

			System.out.println("Enter testPrint method !");
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {

				e.printStackTrace();
			}
			System.out.println("Exit testPrint method !");
		}

}
</span>

这个例子也十分明显说明了,synchronized锁定的是对象,而非代码,大家运行一下将会理解的更深刻。

今天的跟着实例学java多线程就到这里,我们来思考下面两个问题。

1:不同的synchronized的写法有什么区别,又该怎么写创建线程的代码呢?

2:死锁、活跃性问题都是怎么产生的。



时间: 2024-10-13 01:38:40

跟着实例学习java多线程-2的相关文章

跟着实例学习java多线程7-对象的组合发布

我们学习线程安全与同步的知识目的就是要实现一些可复用组件或编写出更大的程序. java中类是对象抽象,那么怎么实现一个线程安全类是我们必须要知道的并正确使用的技术. 在设计线程安全类的过程中,需要包含以下三个基本元素: 找出构成对象状态的所有变量. 找出约束状态变量的不变性条件. 建立对象状态的并发访问管理策略. package com.home.thread.thread7; import com.home.thread.safe; public class Counter { /** * @

跟着实例学习java多线程6-如何正确发布线程安全的对象

我们前面所讲的一切其实都只是为了一个目标那就是能正确发布一个线程安全的对象. 一:线程封闭 这个很好理解如果一个变量是在一个线程中完成的状态改变,那么这个变量肯定是线程安全的. 我们常使用的是栈封闭和ThreadLocal类. 在java运行时内存区中有一个虚拟机栈,栈封闭说的就是这个栈,这个栈是线程私有的,它的生命周期与线程相同.虚拟机栈描述描述的是java方法执行的内存模型:每个方法被执行的时候会同时创建一个栈帧用于存储局部变量.操作数栈等.每一个方法被调用直至执行完成的过程,就对应着一个栈

跟着实例学习java多线程8-同步容器类的问题

我们知道java有很多线程安全的容器类,我们也知道如果把可变状态的管理交给这些线程安全类来管理就可以实现线程安全,但是我们还可能遇到不可想象的问题. 例如: package com.home.thread.thread8; import java.util.Vector; /** * @author gaoxu * 实践出真知! */ public class VectorQueue { private static VectorQueue instance; private static Ve

跟着实例学习java多线程5-初识volatile变量

同步机制可以保证原子操作和内存可见性,但是同步机制对变量的访问性能是我们不得不考虑的问题,java语言提供了一种弱同步机制,volatile变量. 它的原理大致是这样的,当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将变量上的操作与其他内存操作一起重排序.volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量总会返回最新写入的值.(参考<java并发编程实践>一书) 让我们来看一个实例: package

跟着实例学习java多线程9-定时任务实例

定时任务是我们经常遇到的业务场景,我们有很多的功能都需要这样的技术来实现,例如:定时获取一些数据push出去,定时处理一些清理任务,定时检查某个值等.那么我们该怎么实现,在实现中又该注意一些什么? 定时任务就是另开一个线程来执行,其实也是并发的一类,大家可能不好理解,说定时不就是到时间执行一下,怎么还会产生并发,这里主要是看两个指标,一是看执行频率,二是看每次执行的时间,如果执行频率高并且执行任务又会很耗时,那么这时候就形成了并发,当然还有一种情况那就是,定时的job中调用其它服务的方法,而正常

跟着实例学习java多线程4-内存可见性

前三篇我们主要说了多线程访问共享可变状态时需要进行正确的同步处理,保证同一时刻只有一个线程访问相同的数据,我们使用synchronized关键字来实现原子性操作. 今天我们在来认识一下同步的另一个重要方面:内存可见性,这个概念其实很好理解,就是保证在同一个时刻,共享可变状态对访问它的线程呈现出自己最新的状态变化. 我们经常遇到的情景是这样的,一个全局变量计数器,一个线程负责更新该数值,另一些线程获取这个值,那么可见性就是获取值的线程,可以获取到更新线程更新的最新的值. 让我们先来看一个例子,在没

跟着实例学习java多线程-3

同步代码块是一种有效实现操作原子性的方法,上一章我们讲了一些同步的原子操作的基础. 现在我们回忆一下上一章的两个问题. 1:不同的synchronized的写法有什么区别,又该怎么写创建线程的代码呢? 以class实例对象作为锁的写法 写法1 package com.home.thread; /** * @author gaoxu * */ public class SafeThread { @safe public void testPrint(){ synchronized(SafeThre

[转载] 跟着实例学习zookeeper 的用法

原文: http://ifeve.com/zookeeper-curato-framework/ zookeeper 的原生客户端库过于底层, 用户为了使用 zookeeper需要编写大量的代码, 为此Curator框架对 zookeeper 进行了高层次的语义封装, 简化使用 zookeeper 的成本. 只可惜 curator 框架仅仅支持 java 语言, 期待 c++版本出现(或者我们自己尝试实现一个) 跟着实例学习ZooKeeper的用法: Curator框架应用 前面的几篇文章介绍了

JAVA读书推荐----《深入分析Java Web技术内幕》--《java多线程编程核心技术》--《大型网站技术架构 核心原理与案例分析》-《Effective Java中文版》

(1)  首先推荐的不是一本书,而是一个博客,也是我们博客园另外一位博友java_my_life. 目前市面上讲解设计模式的书很多,虽然我前面讲了看书是最好的,但是对设计模式感兴趣的朋友们,我推荐的是这个博客.这位博友的设计模式讲得非常非常好,我认为90%的内容都是没有问题且很值得学习的,其讲解设计模式的大体路线是: 1.随便开篇点明该设计模式的定义 2.图文并茂讲解该设计模式中的结构 3.以详细的代码形式写一下该种设计模式的实现 4.补充内容 5.讲解该设计模式的优缺点 对于一个设计模式我们关