由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题。Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问。
需要明确的几个问题:
1、synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。如果 再细的分类,synchronized可作用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上。
2、无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问。
每个对象只有一个锁(lock)与之相关联。
3、实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
(这段文字来源于https://www.jianshu.com/p/ea9a482ece5f)
4、synchronized关键字出现在实例方法上锁住的是对象,即对象锁;synchronized关键字出现在静态方法上锁住的是类,即类锁;对象锁可以有多个,new几个对象就有几个对象锁,但是类锁只有一把。
例一:
public class Main {
public static void main(String[] args) {
//new了一个对象所以只有一个对象锁
myClass m = new myClass();
Thread t1 = new Thread(new myThread(m));
Thread t2 = new Thread(new myThread(m));
t1.setName("t1");
t2.setName("t2");
t1.start();
try { //保证t1先执行
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
class myClass{
//synchronized出现在实例方法上表示锁住的就是this,即整个方法体都需要线程同步
public synchronized void doSome(){
System.out.println("doSome begin...");
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("doSome over...");
}
public void doOther(){
System.out.println("doOther begin...");
System.out.println("doOther over...");
}
}
class myThread implements Runnable {
private myClass m;
public myThread(myClass m) {
this.m = m;
}
@Override
public void run() {
if(Thread.currentThread().getName().equals("t1")){
m.doSome();
}
if(Thread.currentThread().getName().equals("t2")){
m.doOther();
}
}
}
结果:
总结:
在t1先执行的情况下t2并没有等待t1执行结束后才执行,因为t2线程执行的方法没有用到线程同步,所以执行前不需要获得对象锁就可以执行;
例二:
//把上面的doOther方法加上synchronized关键字
public synchronized void doOther(){
System.out.println("doOther begin...");
System.out.println("doOther over...");
}
结果:
总结:t2线程执行的方法必须等待t1线程结束才会执行,因为他们两个线程执行的方法上都有synchronized关键字,即执行前都要获得对象锁;而两个线程共享一个实例对象m,所以谁先拿到对象锁谁先执行,然后另一个线程等着对象锁释放,直到拿到对象锁才会进入运行状态;
例三:
//Main中更改:
//new了两个myClass对象
myClass3 m1 = new myClass3();
myClass3 m2 = new myClass3();
Thread t1 = new Thread(new myThread3(m1));
Thread t2 = new Thread(new myThread3(m2));
//把上面的doOther方法加上synchronized关键字
public synchronized void doOther(){
System.out.println("doOther begin...");
System.out.println("doOther over...");
}
结果:
总结:
t2线程的执行不会等待t1线程结束后执行,虽然两个方法都用到了线程同步,但是这两个线程各自拥有一个myClass对象实例,因此一共会有两把对象锁;每个线程各自有自己的对象锁,所以在执行的过程中互不影响。
例四:
//Main中更改:
//new了两个myClass对象
myClass3 m1 = new myClass3();
myClass3 m2 = new myClass3();
Thread t1 = new Thread(new myThread3(m1));
Thread t2 = new Thread(new myThread3(m2));
//myClass更改为
class myClass4{
public static synchronized void doSome(){
System.out.println("doSome begin...");
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("doSome over...");
}
public static synchronized void doOther(){
System.out.println("doOther begin...");
System.out.println("doOther over...");
}
}
结果:
总结:
t2线程的执行会等待t1线程的结束后执行,因为在静态方法上添加synchronized关键字之后就相当于加上了类锁,而类锁只有一把,不管new多少了对象也是只有一把,一个线程拿到这个类锁之后,其他线程想要执行public static synchronized修饰的方法就必须等待。