写点什么

java 培训:多个线程同时访问一个类是否有问题方法

作者:@零度
  • 2022 年 3 月 04 日
  • 本文字数:4772 字

    阅读完需:约 16 分钟

 一.synchronized

1.多个线程同时访问同一个类实例对象的两个同步方法:

package synchronizedTest;

public class Example1 {

private int num = 0 ;

(省略 getter.setter,后同)

public synchronized void method1() {

System.out.println("同步方法 1 进入");

for(int i = 0 ; i<10 ; i++) {

System.out.print("同步方法 1:"+num+"--");

num++ ;

}

System.out.println("同步方法 1 结束");

}


public synchronized void method2() {

System.out.println("method2 进入:");

for(int i = 0 ; i<10 ; i++) {

System.out.print("method2:"+num+"--");

num++ ;

}

System.out.println("method2 结束");

}


public static void main(String[] args) {

final Example1 example1 = new Example1() ;


Thread thread1 = new Thread(new Runnable() {

@Override

public void run() {

example1.method1();

}

}) ;


Thread thread2 = new Thread(new Runnable() {

@Override

public void run() {

example1.method2();

}

}) ;

try {

thread2.join();

thread1.join();

thread1.start();

thread2.start();

} catch (InterruptedException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}


}

输出结果:

method1 进入

同步方法 1:0--同步方法 1:1--同步方法 1:2--同步方法 1:3--同步方法 1:4--同步方法 1:5--同步方法 1:6--同步方法 1:7--同步方法 1:8--同步方法 1:9--method1 结束

method2 进入:

method2:10--method2:11--method2:12--method2:13--method2:14--method2:15--method2:16--method2:17--method2:18--method2:19--method2 结束

显然此时多个线程是不能访问同个类(的一个实例对象)的两个同步方法的


2.多个线程同时访问同一个类的不同实例对象的两个同步方法:

将上面的代码稍作修改,主函数中多 new 一个该类实例

final Example1 example2 = new Example1() ;

再修改 thread2 的 run 方法调用的类实例为 example2

Thread thread2 = new Thread(new Runnable() {

@Override

public void run() {

example2.method2();

}

}) ;

得到结果:

同步方法 1 进入

method2 进入:

method2:0--method2:1--同步方法 1:0--同步方法 1:1--同步方法 1:2--method2:2--同步方法 1:3--method2:3--同步方法 1:4--method2:4--同步方法 1:5--method2:5--同步方法 1:6--同步方法 1:7--method2:6--同步方法 1:8--同步方法 1:9--method2:7--同步方法 1 结束

method2:8--method2:9--method2 结束

这时候显然,多个线程是能访问同个类(的不同实例对象)的两个同步方法的。

小结:这是因为 synchronized 是对象锁,即线程获得的锁是施加在一个实例对象上的,如果不同的线程访问的是同一对象上的不同的同步方法,那么显然不能同时进行_北京java培训

如果是不同对象上的不同的同步方法,那么就是可以同时进行的。

3.多个线程同时访问同一个类实例对象的两个 Runnable 对象的 run 方法:

package synchronizedTest;

public class Example2 {

private int num ;

public Runnable runnable1 = new Runnable() {

@Override

public void run() {

//同步锁

synchronized (this) {

System.out.println("线程 1 进入");

for(int i = 0 ; i < 10 ; i ++) {

System.out.print("线程 1:"+num+"--");

}

System.out.println("线程 1 结束");

}

}

};


public Runnable runnable2 = new Runnable() {

@Override

public void run() {

//同步锁

synchronized (this) {

System.out.println("thread2 进入");

for(int i = 0 ; i < 10 ; i ++) {

System.out.print("thread2:"+num+"--");

}

System.out.println("thread2 结束");

}

}

};


public static void main(String[] args) {

Example2 example = new Example2() ; //创建一个对象

new Thread(example.runnable1).start(); //同步方法 1

new Thread(example.runnable2).start(); //同步方法 2

}

}

输出结果:

thread2 进入

线程 1 进入

thread2:0--线程 1:0--线程 1:0--thread2:0--线程 1:0--线程 1:0--线程 1:0--thread2:0--线程 1:0--thread2:0--thread2:0--线程 1:0--thread2:0--线程 1:0--thread2:0--thread2:0--线程 1:0--thread2:0--线程 1:0--thread2:0--线程 1 结束

thread2 结束

可见此时多个线程是能同时访问同个类的两个同步方法的。这是因为 synchronized(this){ //... }中锁住的不是代码块,即这个锁在 run 方法中,但是并不是同步了这个 run 方法,而是括号中的对象 this。

也就是说,多个线程会拿到各自的锁,就能够同时执行 run 方法。(在 run 方法前声明 synchronized 也是同样的效果)

new Thread(example.runnable1).start(); //同步方法 1

new Thread(example.runnable2).start(); //同步方法 2

打印出这个 this 对象,是两个不同的类实例对象:

synchronizedTest.Example2$1@65db6dfa

synchronizedTest.Example2$2@471fab

也说明了不同线程的实例对象不同,都是各自对象的锁,不可以认为是类似于例子 1 中的同一实例对象,而应该类似与例子 2 的不同类的实例对象

总结:分析 synchronized 同步锁的核心在于他是个对象锁,找清楚锁的对象

二.ReentrantLock 锁

1.多个线程同时访问同一个类实例对象的两个同步方法:

将例子 1 的 synchronized 改为引入 ReentrantLock

package ReentrantLockTest;

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;

public class LockExample {

private int num;

private Lock lock = new ReentrantLock();

public void method1() {

lock.lock();

System.out.println("同步方法 1 进入");

for(int i = 0 ; i<10 ; i++) {

System.out.print("同步方法 1:"+num+"--");

num++ ;

}

System.out.println("同步方法 1 结束");

lock.unlock();

}

public void method2() {

lock.lock();

System.out.println("method2 进入:");

for (int i = 0; i < 10; i++) {

System.out.print("method2:" + num + "--");

num++;

}

System.out.println("method2 结束");

lock.unlock();

}

public static void main(String[] args) {

final LockExample example = new LockExample() ;

Thread thread1 = new Thread(new Runnable() {

@Override

public void run() {

example.method1();

}

}) ;


Thread thread2 = new Thread(new Runnable() {

@Override

public void run() {

example.method2();

}

}) ;


try {

thread2.join();

thread1.join();

thread1.start();

thread2.start();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

输出结果:

同步方法 1 进入

同步方法 1:0--同步方法 1:1--同步方法 1:2--同步方法 1:3--同步方法 1:4--同步方法 1:5--同步方法 1:6--同步方法 1:7--同步方法 1:8--同步方法 1:9--同步方法 1 结束

method2 进入:

method2:10--method2:11--method2:12--method2:13--method2:14--method2:15--method2:16--method2:17--method2:18--method2:19--method2 结束

可见此时多个线程是不能访问同个类(的一个实例对象)的两个同步方法的

2.多个线程同时访问同一个类的不同实例对象的两个同步方法:

修改 main 函数的即可:

public static void main(String[] args) {

final LockExample example1 = new LockExample() ;//两个实例

final LockExample example2 = new LockExample() ;

Thread thread1 = new Thread(new Runnable() {

@Override

public void run() {

example1.method1(); //实例 1 的同步方法 1

}

}) ;


Thread thread2 = new Thread(new Runnable() {

@Override

public void run() {

example2.method2();//实例 2 的同步方法 2

}

}) ;


try {

thread2.join();

thread1.join();

thread1.start();

thread2.start();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

输出结果:

同步方法 1 进入

method2 进入:

同步方法 1:0--method2:0--method2:1--同步方法 1:1--method2:2--同步方法 1:2--同步方法 1:3--method2:3--同步方法 1:4--method2:4--同步方法 1:5--同步方法 1:6--method2:5--同步方法 1:7--method2:6--同步方法 1:8--同步方法 1:9--同步方法 1 结束

method2:7--method2:8--method2:9--method2 结束

可见,多个线程是能访问同个类(的不同实例对象)的两个同步方法的。

总结:ReentrantLock 和 synchronized 的前两个例子结论都相同

3.多个线程同时访问同一个类实例对象的两个 Runnable 对象的 run 方法:

package ReentrantLockTest;

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;

public class Lockexample2 {

private int num;

private Lock lock = new ReentrantLock();


public Runnable runnable1 = new Runnable() {

@Override

public void run() {

lock.lock();//上锁

System.out.println("线程 1 进入");

for(int i = 0 ; i < 10 ; i ++) {

System.out.print("线程 1:"+num+"--");

}

System.out.println("线程 1 结束");

lock.unlock();

}

};


public Runnable runnable2 = new Runnable() {

@Override

public void run() {

lock.lock();//上锁

System.out.println("thread2 进入");

for(int i = 0 ; i < 10 ; i ++) {

System.out.print("thread2:"+num+"--");

}

System.out.println("thread2 结束");

lock.unlock();

}

};


public static void main(String[] args) {

Lockexample2 example = new Lockexample2();

new Thread(example.runnable1).start();

new Thread(example.runnable2).start();


}

}

输出结果:

线程 1 进入

线程 1:0--线程 1:0--线程 1:0--线程 1:0--线程 1:0--线程 1:0--线程 1:0--线程 1:0--线程 1:0--线程 1:0--线程 1 结束

thread2 进入

thread2:0--thread2:0--thread2:0--thread2:0--thread2:0--thread2:0--thread2:0--thread2:0--thread2:0--thread2:0--thread2 结束

这里可以看到,与 synchronized 的第三个例子出现了不同的结果。在这个地方,ReentrantLock 不允许多线程同时访问一个类的不同同步方法。

这里要注意的是 ReentrantLock 与 synchronized 不同,ReentrantLock 的实现方式是要先创建 ReentrantLock 对象,然后用这个对象的方法来上锁。java培训机构而一个类的实例中只有一个 ReentrantLock 对象:

private Lock lock = new ReentrantLock();

而本例中,线程的创建是建立在同一个类实例上的:

Lockexample2 example = new Lockexample2();

new Thread(example.runnable1).start();

new Thread(example.runnable2).start();

因此,ReentrantLock 对象 lock 是同一个,因此第一个线程进入同步方法 1 后就获取了锁,第二个线程无法获取这个锁,只能等待。

如果换成是两个实例对象:

public static void main(String[] args) {

Lockexample2 example = new Lockexample2();

Lockexample2 example2 = new Lockexample2();

new Thread(example.runnable1).start();

new Thread(example2.runnable2).start();


}

输出结果

线程 1 进入

thread2 进入

线程 1:0--线程 1:0--线程 1:0--线程 1:0--thread2:0--线程 1:0--thread2:0--thread2:0--thread2:0--thread2:0--thread2:0--thread2:0--thread2:0--线程 1:0--thread2:0--线程 1:0--线程 1:0--thread2:0--线程 1:0--thread2 结束

线程 1:0--线程 1 结束

可见不同的实例对象中是不同的 ReentrantLock 对象,因此可以同时访问

小结:ReentrantLock 锁的核心在与 ReentrantLock 对象是不是同一个

三.结论

重新看看这个问题:一个类中的两个方法都加了同步锁,多个线程能同时访问这个类的两个方法吗?

现在应该比较清楚了,这个问题要分成 synchronized 和 ReentrantLock 两个情况:

一.对于 synchronized

  1. 一个类中的两个方法都加了同步锁,多个线程不能同时访问这个类的同一实例对象的两个方法

  2. 一个类中的两个方法都加了同步锁,多个线程能同时访问这个类的不同实例对象的两个方法

  3. 一个类中的两个方法**(Runnable 的 run 方法)都加了同步锁,多个线程能**同时访问这个类的两个方法(不论是不是同一实例对象)

二.对于 ReentrantLock

  1. 一个类中的两个方法都加了同步锁,多个线程不能同时访问这个类的同一实例对象的两个方法(不论同步加在实例方法中或是 run 方法中)

  2. 一个类中的两个方法都加了同步锁,多个线程能同时访问这个类的不同实例对象的两个方法(不论同步加在实例方法中或是 run 方法中)

文章来源于 Java 面试攻略宝典

用户头像

@零度

关注

关注尚硅谷,轻松学IT 2021.11.23 加入

还未添加个人简介

评论

发布
暂无评论
java培训:多个线程同时访问一个类是否有问题方法_JAVA开发_@零度_InfoQ写作平台