Java多线程的两种实现方式的区别以及深刻同步问题中的锁对象

首先我们知道创建线程有两种方式:

1.继承Thread类;2.实现Runnable接口。

但是这两这并非完全一样的。下面谈谈区别:

因为Java并不支持多继承的(接口是可以多继承接口的。不过一般我们不提),但支持多实现。当一个类继承了父类,就不能再继承Thread类,只能通过实现接口的形式创建线程。

继承Runnable接口更加符合面向对象的思想。线程分为两部分,一是线程对象,二是线程任务。继承Thread类,线程对象和线程任务(run方法内的代码)耦合在一起。一旦创建了Thread类的子类对象,既是线程对象又是线程任务。而实现Runnable接口是将线程任务单独分离出来封装成对象,类型就是Runnable接口类型。线程任务由传递的参数决定。Runable接口将线程对象和线程任务解耦。

第三个区别就是写同步函数的时候这两种方式有明显的差异

综上我们一般采用实现接口的形式来创建线程。

下面我们来说关于同步锁的问题。

Java的关于同步的问题有两种写法,一是代码块写同步,而是写同步函数。二者确是有区别的。

同步函数使用的锁是固定的this(即实现Runnable接口的类的对象),当线程任务只需要一个同步时,完全可以由同步函数来体现;

同步代码块的锁使用的是任意对象,当线程任务需要多同步时,必须用锁来区分。

下面给出详细的解释:首先看一个死锁的示例程序:

/*
 * 思路:死锁,嵌套同步时最简单的。
 * 两个锁对象进行嵌套。两个线程执行同样的两个锁对象嵌套
 */
class MyLock {
	public static final Object LOCKA = new Object();
	public static final Object LOCKB = new Object();
}

class Test implements Runnable {
	private boolean flag;

	Test(boolean flag) {
		this.flag = flag;
	}

	public void run() {
		if (flag) {
			while (true) {
				synchronized (MyLock.LOCKA) {
					System.out.println(Thread.currentThread().getName()
							+ "...if.....LOCKA");
					synchronized (MyLock.LOCKB) {
						System.out.println(Thread.currentThread().getName()
								+ "...if.....LOCKB");
					}
				}
			}
		} else {
			while (true) {
				synchronized (MyLock.LOCKB) {
					System.out.println(Thread.currentThread().getName()
							+ "...else.....LOCKB");
					synchronized (MyLock.LOCKA) {
						System.out.println(Thread.currentThread().getName()
								+ "...else.....LOCKA");
					}
				}
			}
		}
	}
}

public class DeadLock {
	public static void main(String[] args) {
		Test test1 = new Test(true);
		Test test2 = new Test(false);

		Thread t1 = new Thread(test1);
		Thread t2 = new Thread(test2);

		t1.start();
		t2.start();
	}
}

其中MyLockA和MyLockB指代的是不同的Object对象,当然也可以是其他的类的对象,因为此处的对象锁可以是任意对象

但若写的是同步函数的形式,则锁对象是固定的this,我们还知道this永远都是指向当前对象的(也就是谁调用这个函数,this就指代的谁。)所以当采用继承Thread类来实现多线程时并不能实现同步的效果,因为不同的线程是不同的Thread子类对象,this指代的也不一样,从而锁也不一样。下面给出程序证明观点:

class Bank {

	private int sum;

	public void add (int num) {
		sum += num;
		System.out.println(Thread.currentThread().getName()+"...sum=" + sum);
	}

}

class Customer extends Thread{
	 private Bank b = new Bank();

	@Override
	public void run() {
		this.storage();
	}

	public synchronized void storage () {
		for (int i = 0; i < 5; i++) {
			if(i == 3)
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				b.add(100);
		}
	}
}

public class BankAndCustomer {

	public static void main(String[] args) {
		Customer c1 = new Customer();
		Customer c2 = new Customer();

		c1.start();
		c2.start();
	}
}

以上代码的输出结果是(当然多线程的输出结果不唯一):

其中sleep方法时不是放锁对象的,但当其中一个线程休眠之后其他线程立即得到了cpu执行权,来执行storage方法,这足以证明上述观点。

但用通过实现Runnable接口的形式来实现多线程则上述问题可以避免,因为实现线程时我们传递的是Runnable接口的子类对象,两个线程都是子类对象调用的run方法(建议查看API中的Thread类的run方法,一探究竟。),故this所指代的对象是一致的,故当一个线程输出三次循环后,休眠两秒并未被打断,而是苏醒后继续执行。下面给出代码:

class Bank {

	private int sum;

	public void add (int num) {
		sum += num;
		System.out.println(Thread.currentThread().getName()+"...sum=" + sum);
	}

}

class Customer implements Runnable{
	 private Bank b = new Bank();

	@Override
	public void run() {
		this.storage();
	}

	public synchronized void storage () {
		for (int i = 0; i < 5; i++) {
			if(i == 3)
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				b.add(100);
		}
	}
}

public class BankAndCustomer {

	public static void main(String[] args) {
		Customer c = new Customer();

		Thread t1 = new Thread(c);
		Thread t2 = new Thread(c);

		t1.start();
		t2.start();
	}
}

同时我们需要说明的是,当同步函数被标注为静态函数是他的默认锁对象就不是this了,因为静态中是没有this的。由类名直接调用,而不需要对象。锁对象变为类名.class,上例中若声明为public static synchronized void storage ()锁对象就是Customer.class

时间: 2024-08-03 11:26:26

Java多线程的两种实现方式的区别以及深刻同步问题中的锁对象的相关文章

Java多线程的两种实现方式:继承Thread类 &amp; 实现Runable接口

------Java培训.Android培训.iOS培训..Net培训.期待与您交流! 创建和启动线程的两种传统方式: Java提供了线程类Thread来创建多线程的程序.其实,创建线程与创建普通的类的对象的操作是一样的,而线程就是Thread类或其子类的实例对象.每个Thread对象描述了一个单独的线程.要产生一个线程,有两种方法: ◆需要从Java.lang.Thread类派生一个新的线程类,重载它的run()方法: ◆实现Runnalbe接口,重载Runnalbe接口中的run()方法.

Java 多线程的两种实现方式

继承Thead class IphoneThread extends Thread { public int iphone = 5; public String user; public IphoneThread(String str) { user = str; } @Override public void run() { while (iphone != 0) { iphone--; System.out.println(user + " get one and left iphone n

【Java多线程】两种基本实现框架

Java多线程学习1——两种基本实现框架 一.前言 当一个Java程序启动的时候,一个线程就立刻启动,改程序通常也被我们称作程序的主线程.其他所有的子线程都是由主线程产生的.主线程是程序开始就执行的,并且程序最终是以主线程的结束而结束的. Java编写程序都运行在在Java虚拟机(JVM)中,在JVM的内部,程序的多任务是通过线程来实现的.每用Java命令启动一个Java应用程序,就会启动一个JVM进程.在同一个JVM进程中,有且只有一个进程,就是它自己.在这个JVM环境中,所有程序代码的运行都

多线程两种实现方式的区别

请解释Thread类与Runnable接口实现多线程的区别?(请解释多线程两种实现方式的区别?) 1. Thread类时Runnable接口的子类,使用Runnable接口实现多线程可以避免单继承局限!2. Runnable接口实现的多线程可以比Thread类实现的多线程更加清楚的描述数据共享的概念! 请写出多线程两种实现操作?(写出Thread类继承的方式和Runnable接口实现的方式代码!) 实现Thread类: 类似于代理设计模式! class MyThread extends Thre

JavaLearning:多线程的两种实现方式

第一种 继承Thread类 package org.fan.threaddemo; public class MyThread extends Thread{ private String name; public MyThread(String name){ this.name=name; } public void run(){//覆写run()方法 for(int i=0;i<30;i++){ System.out.println("Thread运行:"+this.name

Java中String的两种赋值方式的区别

本文修改于:https://www.zhihu.com/question/29884421/answer/113785601 前言:在java中,String有两种赋值方式,第一种是通过"字面量"赋值,如:String str="hello",第二种是通过new关键字创建新对象,如:String str=new String("hello").那么这两种赋值的方式到底有什么区别呢,下面给出具体分析. 1.首先回顾Java虚拟机的结构图. 在上面的

Ajax 两种请求方式的区别onload和onreadystatechange

一. onreadystatechange 1. XMLHttpRequest对象有一个属性readyState,将其(xhr.readyState)打印后发现.进入onreadystatechange请求方式中时,可以打印其状态为2,状态为3,状态为4. <button id="btn">请求纯文本</button> <script> let btn = document.getElementById('btn'); btn.addEventLis

java基础——多线程的两种实现方式

第一种实现方式:继承Thread类,并实现run方法,这种情况下每个线程对象都有一个独立的run方法 package thread; /* @author zsben @create 2020-01-03 9:52 多线程创建的第一种方法:继承于Thread类 1.创建一个继承于Thread的类 2.重写Thread类的run方法-->将此线程执行的操作声明在run中 3.创建Thread子类对象 4.通过此对象调用start方法:启动当前线程,调用当前线程的run方法 */ //1.创建一个继

多线程的两种实现方式

java中多线程可以采用两种方式实现,分别是继承Thread类重写run方法和实现Runnable接口重写run方法. 继承Thread类重写run方法举例如下: /* 需求:在主线程之外继承Thread类创建两独立线程,分别打印1至50. */ class ThreadTest extends Thread{ public void run(){ for(int i = 1; i <= 50; i++) System.out.println(Thread.currentThread().get