先介绍synchronized方法和synchronized块的使用,分割线后边是不断的修改一个例子,去探索synchronized的作用
(补充:
1、当synchronized锁住一个对象后,别的线程如果也想拿到这个对象的锁,就必须等待这个线程执行完成释放锁,才能再次给对象加锁,这样才达到线程同步的目的。即使两个不同的代码段,都要锁同一个对象,那么这两个代码段也不能在多线程环境下同时运行。
2、我们在用synchronized关键字的时候,能缩小代码段的范围就尽量缩小,能在代码段上加同步就不要再整个方法上加同步。这叫减小锁的粒度,使代码更大程度的并发。
3、static synchronized是类锁,synchronized是对象锁
)
一、synchronized方法
当一个对象所有的方法全部被synchronized关键字标识以后,当一个线程A访问一个带synchronized关键字的方法时(比如method1),其他的方法就会被阻塞(不允许其他现场访问这个类的方法),得等A线程访问任务完成以后,才能让其他线程访问。但是要达到这样,必须是所有方法全部标记synchronized关键字。
public class Try{
public synchronized void method1() {
for(int i=0;i<4;i++) {
System.out.println(Thread.currentThread().getName()+" : "+i);
}
}
public synchronized void method2() {
for(int i=0;i<4;i++) {
System.out.println(Thread.currentThread().getName()+" : "+i);
}
}
public static void main(String[] args) {
final Try tt = new Try();
Thread A = new Thread(new Runnable() { public void run() { tt.method1(); } }, "A" );
Thread B = new Thread( new Runnable() { public void run() {tt.method2(); } }, "B" );
A.start();
B.start();
}
}
输出结果为:
B : 0
B : 1
B : 2
B : 3
A : 0
A : 1
A : 2
A : 3
二、synchronized块
顾名思义,就是类似于 synchronized(Object){}这样的块,功能跟synchronized方法是一样的。代码必须获得对象Object的锁才能执行。
例:
public class Try{
public void method1() {
synchronized(this) {
for(int i=0;i<4;i++) {
System.out.println(Thread.currentThread().getName()+" : "+i);
}
}
}
public void method2() {
synchronized(this) {
for(int i=0;i<4;i++) {
System.out.println(Thread.currentThread().getName()+" : "+i);
}
}
}
public static void main(String[] args) {
final Try tt = new Try();
Thread A = new Thread(new Runnable() { public void run() { tt.method1(); } }, "A" );
Thread B = new Thread( new Runnable() { public void run() {tt.method2(); } }, "B" );
A.start();
B.start();
}
}
输出结果为:
B : 0
B : 1
B : 2
B : 3
A : 0
A : 1
A : 2
A : 3
下面是通过几个例子对synchronized的探索:
**理解1:**两个并发线程访问同一个对象object的同步代码块时,我们通过synchronized的手段,使得只有一个线程在执行任务
例1:
public class Try{
public void method1() {
synchronized(this) {
for(int i=0;i<4;i++) {
System.out.println(Thread.currentThread().getName()+" : "+i);
}
}
}
public void method2() {
synchronized(this) {
for(int i=0;i<4;i++) {
System.out.println(Thread.currentThread().getName()+" : "+i);
}
}
}
public static void main(String[] args) {
Thread A = new Thread(new Runnable() { public void run() { new Try().method1(); } }, "A" );
Thread B = new Thread(new Runnable() { public void run() {new Try().method2(); } }, "B" );
A.start();
B.start();
}
}
很简单的例子,创建两个线程,名字分别为A和B,希望他们能不是并发的执行任务,
输出结果却是:
B : 0
A : 0
B : 1
A : 1
B : 2
A : 2
B : 3
A : 3
解释:显然,创建的A、B两个线程是同步进行的,A线程执行的时候并不会阻塞B线程,可是明明已经使用了synchronized了啊?原因在于:synchronized解决的是对类的对象实例进行加锁,当线程调用一个实例运行的,另外的线程调用这个实例时候阻塞,达到上锁的目的,但是这里明显创建的是两个对象new Try,创建了两个对象实例。(即A访问的是一个实例,B访问是另一个)(对比理解3)
应该把main方法里边的创建方式改成:同一个对象监控器,即只new一个。(还有一个解决方式,见理解4)
final Try tt = new Try();
Thread A = new Thread(new Runnable() { public void run() { tt.method1(); } }, "A" );
Thread B = new Thread( new Runnable() { public void run() {tt.method2(); } }, "B" );
A.start();
B.start();
结果为:
B : 0
B : 1
B : 2
B : 3
A : 0
A : 1
A : 2
A : 3
**理解2:**前面说到,该对象的所有方法都需要添加synchronized,为了确保访问一个方法的时候,对其他线程的阻塞,如果不呢?
把上边例子的method2去掉了synchronized,变成
public void method2() {
for(int i=0;i<4;i++) {
System.out.println(Thread.currentThread().getName()+" : "+i);
}
}
(注:main方法采用上边那个修改过的方法)
输出结果是:
B : 0
B : 1
A : 0
B : 2
A : 1
B : 3
A : 2
A : 3
解释:很明显,创建两个进程,但是method2没有synchronized关键字(即method1对他没有阻塞作用),所以method2可以跟method1一起执行,即:对其它不是synchronized同步方法或不是synchronized(this)同步代码块调用不是堵塞状态的
**理解3:**使用synchronized(非this对象)同步代码块格式进行同步操作时,对象监视器必须是同一个对象,如果不是同一个对象监视器,运行的结果就是异步调用了,就会交叉运行
例子3:
public class Try{
public void method1() {
String lock = new String();
synchronized(lock) {
for(int i=0;i<4;i++) {
System.out.println(Thread.currentThread().getName()+" : "+i);
}
}
}
public void method2() {
String lock = new String();
synchronized(lock) {
for(int i=0;i<4;i++) {
System.out.println(Thread.currentThread().getName()+" : "+i);
}
}
}
public static void main(String[] args) {
final Try tt = new Try();
Thread A = new Thread(new Runnable() { public void run() { tt.method1(); } }, "A" );
Thread B = new Thread( new Runnable() { public void run() {tt.method2(); } }, "B" );
A.start();
B.start();
}
}
可以看到对象监视是lock,但是分别是两个new,他们是不一样的,所以结果会交叉:
A : 0
B : 0
A : 1
B : 1
A : 2
B : 2
A : 3
B : 3
应该改成:变成同一个对象监视器
public class Try{
private String lock = new String();
public void method1() {
synchronized(lock) {
for(int i=0;i<4;i++) {
System.out.println(Thread.currentThread().getName()+" : "+i);
}
}
}
public void method2() {
synchronized(lock) {
for(int i=0;i<4;i++) {
System.out.println(Thread.currentThread().getName()+" : "+i);
}
}
}
public static void main(String[] args) {
final Try tt = new Try();
Thread A = new Thread(new Runnable() { public void run() { tt.method1(); } }, "A" );
Thread B = new Thread( new Runnable() { public void run() {tt.method2(); } }, "B" );
A.start();
B.start();
}
}
**理解4:*使用静态同步synchronized方法和.Class方式持锁
理解1的例子,也可以这样修改:
public class Try{
public void method1() {
synchronized(Try.class) {
for(int i=0;i<4;i++) {
System.out.println(Thread.currentThread().getName()+" : "+i);
}
}
}
public void method2() {
synchronized(Try.class) {
for(int i=0;i<4;i++) {
System.out.println(Thread.currentThread().getName()+" : "+i);
}
}
}
public static void main(String[] args) {
Thread A = new Thread(new Runnable() { public void run() { new Try().method1(); } }, "A" );
Thread B = new Thread( new Runnable() { public void run() {new Try().method2(); } }, "B" );
A.start();
B.start();
}
}
把synchronized里的对象监视器变成相同的(不用this了,因为创建线程的时候,实例对象,this所指的,是两个对象)
或者静态同步synchronized方法
public class Try{
public synchronized static void method1() {
for(int i=0;i<4;i++) {
System.out.println(Thread.currentThread().getName()+" : "+i);
}
}
public synchronized static void method2() {
for(int i=0;i<4;i++) {
System.out.println(Thread.currentThread().getName()+" : "+i);
}
}
public static void main(String[] args) {
Thread A = new Thread(new Runnable() { public void run() { method1(); } }, "A" );
Thread B = new Thread( new Runnable() { public void run() {method2(); } }, "B" );
A.start();
B.start();
}
}
注意:变成静态方法以后,就不用new Try().method1 直接用method1就可以。
解释:为什么这样的方法可以呢?因为静态方法与类相关,而不是对象,线程获取的是Class锁。