在Java多线程编程下,当线程运行环境中有临界资源时,需要保证该资源在同一时刻只能被一个线程使用访问,此时就需要Synchronized关键字
synchronized的使用方式
synchronized在java中有三种应用方式
1.修饰实例方法,对当前实例加锁,进入同步代码前要获得当前实例的锁
2.修饰静态方法,对当前类对象加锁,进入同步代码前要获得当前类对象的锁
3.修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
对静态、实例方法加锁
对静态方法进行加锁,锁住的是当前类的Class对象,执行静态方法的前提是得到得到类锁。
public class ThreadSynDemo1 {
public static void main(String[] args) {
new Thread_A().start();
new Thread_B().start();
}
}
class StaSynMethod{
static synchronized void compute1() {
while(true) {
System.out.println(Thread.currentThread().getName()+"正在使用(静态方法111)");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static synchronized void compute2() {
while(true) {
System.out.println(Thread.currentThread().getName()+"正在使用(静态方法222)");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
void out() {
while(true) {
System.out.println(Thread.currentThread().getName()+"正在使用(实例方法)");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Thread_A extends Thread{
@Override
public void run() {
super.run();
StaSynMethod.compute1();
}
}
class Thread_B extends Thread{
@Override
public void run() {
super.run();
//情况1
StaSynMethod s = new StaSynMethod();
s.out();
//情况2
//StaSynMethod.compute1();
//情况3
//StaSynMethod.compute2();
}
}
情况1:
结论:给类的静态方法加锁(锁住的是类对象),并不影响其他线程使用该类的实例方法(因为访问静态 synchronized 方法占用的锁是当前类的class对象,而访问非静态 synchronized 方法占用的锁是当前实例对象锁)
情况2:
结论:给类的静态方法加锁(锁住的是类对象),其他线程不能正常使用执行该静态方法(没有获得类锁),毕竟一个对象只有一把锁,当一个线程获取了该对象的锁之后,其他线程无法获取该对象的锁
情况3:
结论:给类的静态方法加锁(锁住的是类对象)后,该类的其他被锁定的静态方法都不能被其他线程执行。
再补充来个情况4: 将compute2()静态方法的synchronized去掉执行
//去点原有的synchronized
static void compute2() {
while(true) {
System.out.println(Thread.currentThread().getName()+"正在使用(静态方法222)");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
结论:给类的静态方法加锁(锁住的是类对象)后,其他未加锁的静态方法任然能被其他线程执行使用。
再再补充情况5: 也是比较常见的一种情况,验证调用同一对象时,多线程能否同时执行该对象的被锁住的同一实例方法,或者被锁住的不同的实例方法
代码修改如下
synchronized void out() {
while(true) {
System.out.println(Thread.currentThread().getName()+"正在使用(实例方法)");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//新增实例方法out2()
synchronized void out2() {
while(true) {
System.out.println(Thread.currentThread().getName()+"正在使用(实例方法222)");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
将同一对象传参进多个线程中
class Thread_A extends Thread{
private StaSynMethod s;
public Thread_A(StaSynMethod s) {
this.s = s;
}
@Override
public void run() {
super.run();
s.out();
}
}
class Thread_B extends Thread{
private StaSynMethod s;
public Thread_B(StaSynMethod s) {
this.s = s;
}
@Override
public void run() {
super.run();
//情况5
s.out();
}
}
结论:给同一对象(注意:必须是同一对象)的实例方法加锁(锁住的是该实例对象)后,该实例方法不能被其他线程执行使用。
情况6:执行被锁住的不同的实例方法
结论:给同一对象(注意:必须是同一对象)的实例方法加锁(锁住的是该实例对象)后,其他被加锁的实例方法不能被其他线程执行使用。
为什么要给静态方法加锁
上面栗子仅为测试,并没有说出使用静态方法锁的场景究竟是什么。其实,当多线程操作共享数据数据时,线程安全是没有保障的,就是说可能会出现脏读,幻读等现象,所以为静态方法加锁,可保证同一时间只能有一个线程执行该静态方法(参考情况2),从而保证同一时间数据的读写是唯一的,安全的。
对代码块加锁
这是比较推荐的加锁方式,试想:当我们的方法体庞大,耗时长的情况下,我们仍然给方法提加锁,可能效率上是非常底下的,这在实际的生产中是不允许的,所以我们可以选择给需要同步的一小部分代码加锁就可以了,这样提高了效率,安全性上也同样有保障
public class ThreadSynDemo2 implements Runnable {
static int i=0;
@Override
public void run() {
//省略其他耗时操作....
synchronized(this){
for(int j=0;j<1000000;j++){
i++;
}
System.out.println(i);
}
}
public static void main(String[] args) throws InterruptedException {
ThreadSynDemo2 t = new ThreadSynDemo2();
Thread t1=new Thread(t);
Thread t2=new Thread(t);
t1.start();
t2.start();
}
}
可以看到数据是安全的,在多个线程同时访问下,没有出现同时加的情况
代码块的测试结论如下:
一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
二、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。
三、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
四、第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。