本来地址:Java多线程干货系列—(二)synchronized
博客地址:tengj.top/java
本篇主要介绍Java多线程中的同步,也就是如何在Java语言中写出线程安全的程序,如何在Java语言中解决非线程安全的相关问题。没错就是使用synchronized。程序员
那么通常来讲,是如何解决线程安全问题的呢?面试
基本上全部的并发模式在解决线程安全问题时,都采用“序列化访问临界资源”的方案,即在同一时刻,只能有一个线程访问临界资源,也称做同步互斥访问。安全
一般来讲,是在访问临界资源的代码前面加上一个锁,当访问完临界资源后释放锁,让其余线程继续访问。微信
在Java中,提供了两种方式来实现同步互斥访问:synchronized和Lock。多线程
本文主要讲述synchronized的使用方法,Lock的使用方法在下一篇博文中讲述。并发
synchronized是Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,可以保证在同一时刻最多只有一个线程执行该段代码。在了解synchronized关键字的使用方法以前,咱们先来看一个概念:互斥锁,顾名思义:能到达到互斥访问目的的锁。ide
举个简单的例子:若是对临界资源加上互斥锁,当一个线程在访问该临界资源时,其余线程便只能等待。oop
在Java中,每个对象都拥有一个锁标记(monitor),也称为监视器,多线程同时访问某个对象时,线程只有获取了该对象的锁才能访问。post
在Java中,可使用synchronized关键字来标记一个方法或者代码块,当某个线程调用该对象的synchronized方法或者访问synchronized代码块时,这个线程便得到了该对象的锁,其余线程暂时没法访问这个方法,只有等待这个方法执行完毕或者代码块执行完毕,这个线程才会释放该对象的锁,其余线程才能执行这个方法或者代码块。
synchronized代码块,被修饰的代码成为同步语句块,其做用的范围是调用这个代码块的对象,咱们在用synchronized关键字的时候,能缩小代码段的范围就尽可能缩小,能在代码段上加同步就不要再整个方法上加同步。这叫减少锁的粒度,使代码更大程度的并发。
synchronized方法,被修饰的方法成为同步方法,其做用范围是整个方法,做用对象是调用这个方法的对象。
synchronized静态方法,修饰一个static静态方法,其做用范围是整个静态方法,做用对象是这个类的全部对象。
synchronized类,其做用范围是Synchronized后面括号括起来的部分synchronized(className.class),做用的对象是这个类的全部对象。
synchronized(),()中是锁住的对象, synchronized(this)锁住的只是对象自己,同一个类的不一样对象调用的synchronized方法并不会被锁住,而synchronized(className.class)实现了全局锁的功能,全部这个类的对象调用这个方法都受到锁的影响,此外()中还能够添加一个具体的对象,实现给具体对象加锁。
synchronized (object) {
//在同步代码块中对对象进行操做
}复制代码
当两个并发线程访问同一个对象中的synchronized
代码块时,在同一时刻只能有一个线程获得执行,另外一个线程受阻塞,必须等待当前线程执行完这个代码块之后才能执行该代码块。两个线程间是互斥的,由于在执行synchronized
代码块时会锁定当前的对象,只有执行完该代码块才能释放该对象锁,下一个线程才能执行并锁定该对象。
当一个线程访问object
的一个synchronized(this)
同步代码块时,另外一个线程仍然能够访问该object
中的非synchronized(this)
同步代码块。(两个线程使用的是同一个对象)
object
的一个synchronized(this)
同步代码块时,其余线程对object
中全部其它synchronized(this)
同步代码块的访问将被阻塞(同上,两个线程使用的是同一个对象)。下面经过代码来实现:
1)当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程获得执行。另外一个线程必须等待当前线程执行完这个代码块之后才能执行该代码块。
package ths;
public class Thread1 implements Runnable {
public void run() {
synchronized(this) {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " synchronized loop " + i);
}
}
}
public static void main(String[] args) {
Thread1 t1 = new Thread1();
Thread ta = new Thread(t1, "A");
Thread tb = new Thread(t1, "B");
ta.start();
tb.start();
}
}复制代码
输出结果:
A synchronized loop 0
A synchronized loop 1
A synchronized loop 2
A synchronized loop 3
A synchronized loop 4
B synchronized loop 0
B synchronized loop 1
B synchronized loop 2
B synchronized loop 3
B synchronized loop 4复制代码
2)然而,当一个线程访问object的一个synchronized(this)同步代码块时,另外一个线程仍然能够访问该object中的非synchronized(this)同步代码块。
package ths;
public class Thread2 {
public void m4t1() {
synchronized(this) {
int i = 5;
while( i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
}
public void m4t2() {
int i = 5;
while( i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
public static void main(String[] args) {
final Thread2 myt2 = new Thread2();
Thread t1 = new Thread( new Runnable() { public void run() { myt2.m4t1(); } }, "t1" );
Thread t2 = new Thread( new Runnable() { public void run() { myt2.m4t2(); } }, "t2" );
t1.start();
t2.start();
}
}复制代码
输出结果:
t1 : 4
t2 : 4
t1 : 3
t2 : 3
t1 : 2
t2 : 2
t1 : 1
t2 : 1
t1 : 0
t2 : 0复制代码
3)尤为关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其余线程对object中全部其它synchronized(this)同步代码块的访问将被阻塞。
//修改Thread2.m4t2()方法:
public void m4t2() {
synchronized(this) {
int i = 5;
while( i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
}复制代码
输出结果:
t1 : 4
t1 : 3
t1 : 2
t1 : 1
t1 : 0
t2 : 4
t2 : 3
t2 : 2
t2 : 1
t2 : 0复制代码
4)第三个例子一样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就得到了这个object的对象锁。结果,其它线程对该object对象全部同步代码部分的访问都被暂时阻塞。
//修改Thread2.m4t2()方法以下:
public synchronized void m4t2() {
int i = 5;
while( i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}复制代码
输出结果:
t1 : 4
t1 : 3
t1 : 2
t1 : 1
t1 : 0
t2 : 4
t2 : 3
t2 : 2
t2 : 1
t2 : 0复制代码
5)每一个类也会有一个锁,它能够用来控制对static数据成员的并发访问。
而且若是一个线程执行一个对象的非static synchronized方法,另一个线程须要执行这个对象所属类的static synchronized方法,此时不会发生互斥现象,由于访问static synchronized方法占用的是类锁,而访问非static synchronized方法占用的是对象锁,因此不存在互斥现象。
代码以下:
public class Test {
public static void main(String[] args) {
final InsertData insertData = new InsertData();
new Thread(){
@Override
public void run() {
insertData.insert();
}
}.start();
new Thread(){
@Override
public void run() {
insertData.insert1();
}
}.start();
}
}
class InsertData {
public synchronized void insert(){
System.out.println("执行insert");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行insert完毕");
}
public synchronized static void insert1() {
System.out.println("执行insert1");
System.out.println("执行insert1完毕");
}
}复制代码
输出结果:
执行insert
执行insert1
执行insert1完毕
执行insert完毕复制代码
第一个线程里面执行的是insert方法,不会致使第二个线程执行insert1方法发生阻塞现象。
当一个线程进入一个对象的synchronized方法A以后,其它线程是否可进入此对象的synchronized方法B?
答:不能。其它线程只能访问该对象的非同步方法,同步方法则不能进入。由于非静态方法上的synchronized修饰符要求执行方法时要得到对象的锁,若是已经进入A方法说明对象锁已经被取走,那么试图进入B方法的线程就只能在等锁池(注意不是等待池哦)中等待对象的锁。
synchronized关键字的用法?
答:synchronized关键字能够将对象或者方法标记为同步,以实现对对象和方法的互斥访问,能够用synchronized(对象) { … }定义同步代码块,或者在声明方法时将synchronized做为方法的修饰符。
简述synchronized 和java.util.concurrent.locks.Lock的异同?
答:Lock是Java 5之后引入的新的API,和关键字synchronized相比主要相同点:Lock 能完成synchronized所实现的全部功能;主要不一样点:Lock有比synchronized更精确的线程语义和更好的性能,并且不强制性的要求必定要得到锁。synchronized会自动释放锁,而Lock必定要求程序员手工释放,而且最好在finally 块中释放(这是释放外部资源的最好的地方)
以上就是synchronized的概念和基本使用用法,下一篇博文中将介绍Lock,但愿对你有所帮助。
一直以为本身写的不是技术,而是情怀,一篇篇文章是本身这一路走来的痕迹。靠专业技能的成功是最具可复制性的,但愿个人这条路能让你少走弯路,但愿我能帮你抹去知识的蒙尘,但愿我能帮你理清知识的脉络,但愿将来技术之巅上有你也有我。
订阅博主微信公众号:嘟爷java超神学堂(javaLearn)三大好处: