一、加synchronized的两种方法
1. 加在普通方法上
加在普通方法上本质上是synchronized(this),是对调用本方法的对象上锁,即理解为:调用obj.test()方法时,只有拿到obj这个对象的对象锁,才可以执行test方法里的代码,否则就会阻塞在当前位置;
public class Synchronized {
public synchronized void test(){
}
}
等价于:
public class Synchronized {
public void test(){
synchronized (this){
}
}
}
2. 加在静态方法上
synchronized加在静态方法上,相当于对整个类的对象上锁,即当通过”类名.test()”调用方法时,会先看当前线程是否持有”类名.class”对象的对象锁,如果持有才可以进一步访问方法内的代码,否则就会阻塞到当前位置。
public class Synchronized {
public synchronized static void test(){
}
}
等价于:
public class Synchronized {
public static void test(){
synchronized (Synchronized.class){
}
}
}
二、不加synchronized的方法
不加synchronized的方法就好比不遵守规则的人,不去老实排队,就会胡乱访问临界资源,造成临界资源被随意篡改,不能保证原子性。
三、对象锁类设计总结
- 将共享资源作为该对象锁类的成员变量;
- 访问该共享资源成员变量的方法都要加上synchronized块;
- 创建对象锁类对象,在不同线程中通过该对象调用方法以实现分割对临界资源的访问;
四、线程八锁
synchronized作用在方法上的习题,其实是考察synchronized锁住的是哪个对象
情况1:
public class Test8Blocks {
public static void main(String[] args) {
Number n1 =new Number();
//因为是在方法上的synchronized,所以是在锁n1对象;
new Thread(()->{
System.out.println("begin");
n1.a();
}).start();
new Thread(()->{
System.out.println("begin");
n1.b();
}).start();
}
}
class Number{
public synchronized void a(){
System.out.println("1");
}
public synchronized void b(){
System.out.println("2");
}
}
线程1与线程2启动后,比如线程1先执行到n1.a()方法,那么线程1则会占用该n1对象的对象锁,所以当线程2执行到n1.b()时则会阻塞住,当n1.a()方法执行完毕后,锁释放,线程2的b方法才可以继续执行。
情况2:
public class Test8Blocks {
public static void main(String[] args) {
Number n1 =new Number();
//因为是在方法上的synchronized,所以是在锁n1对象;
new Thread(()->{
System.out.println("begin");
n1.a();
}).start();
new Thread(()->{
System.out.println("begin");
n1.b();
}).start();
}
}
class Number{
public synchronized void a(){
Thread.sleep(1);
System.out.println("1");
}
public synchronized void b(){
System.out.println("2");
}
}
同情况1类似,共有两种执行情况:
1)1秒后,打印1和2
2)先打印2,然后1秒后打印1
注意sleep并不会释放锁,线程执行完毕才会释放锁。
情况3:
public class Test8Blocks {
public static void main(String[] args) {
Number n1 =new Number();
//因为是在方法上的synchronized,所以是在锁n1对象;
new Thread(()->{
System.out.println("begin");
n1.a();
}).start();
new Thread(()->{
System.out.println("begin");
n1.b();
}).start();
new Thread(()->{
System.out.println("begin");
n1.c();
}).start();
}
}
class Number{
public synchronized void a(){
System.out.println("1");
}
public synchronized void b(){
System.out.println("2");
}
public void c(){
System.out.println("3");
}
}
加入没有synchronized的c方法,当n1.c()调用的时候则不需要考虑是否互斥,因为只有方法上加了synchronized才会在执行的时候考虑当前线程是否拿到了n1的对象锁,而不加synchronized的方法只是正常方法,随意调用。
情况4:
public class Test8Blocks {
public static void main(String[] args) {
Number n1 =new Number();
Number n2 = new Number();
//因为是在方法上的synchronized,所以是在锁n1对象;
new Thread(()->{
System.out.println("begin");
try {
n1.a();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
System.out.println("begin");
n2.b();
}).start();
}
}
class Number{
public synchronized void a() throws InterruptedException {
Thread.sleep(1);
System.out.println("1");
}
public synchronized void b(){
System.out.println("2");
}
}
因为拥有两个对象n1和n2,所以实际上两个线程互不干扰,因为不会存在两个线程抢占一把锁的情况,线程1独占n1对象锁,线程2独占n2对象锁。注意只有多个线程共用同一把对象锁,才会达到互斥的目的,因为调用方法时会考虑当前线程是否拿到当前对象的锁。
情况5:
public class Test8Blocks {
public static void main(String[] args) {
Number n1 =new Number();
Number n2 = new Number();
//因为是在方法上的synchronized,所以是在锁n1对象;
new Thread(()->{
System.out.println("begin");
try {
n1.a();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
System.out.println("begin");
n2.b();
}).start();
}
}
class Number{
public static synchronized void a() throws InterruptedException {
Thread.sleep(1);
System.out.println("1");
}
public synchronized void b(){
System.out.println("2");
}
}
注意改动点是:a方法变成static synchronized方法,故调用该方法时会查看当前线程是否拥有“类对象锁”,而调用b方法还是查看当前线程是否拥有“实例n1对象锁”
所以实际上两个线程是拥有两个不同的对象锁,互不干扰。
情况6:
public class Test8Blocks {
public static void main(String[] args) {
Number n1 =new Number();
Number n2 = new Number();
//因为是在方法上的synchronized,所以是在锁n1对象;
new Thread(()->{
System.out.println("begin");
try {
n1.a();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
System.out.println("begin");
n2.b();
}).start();
}
}
class Number{
public static synchronized void a() throws InterruptedException {
Thread.sleep(1);
System.out.println("1");
}
public static synchronized void b(){
System.out.println("2");
}
}
较情况5的改动是,将b方法也变成了static synchronized方法,则线程1和线程2会抢占”类对象锁”,会出现两线程互斥的情况,达到了控制资源访问的目的。
情况7:
public class Test8Blocks {
public static void main(String[] args) {
Number n1 =new Number();
Number n2 = new Number();
//因为是在方法上的synchronized,所以是在锁n1对象;
new Thread(()->{
System.out.println("begin");
try {
n1.a();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
System.out.println("begin");
n2.b();
}).start();
}
}
class Number{
public static synchronized void a() throws InterruptedException {
Thread.sleep(1);
System.out.println("1");
}
public synchronized void b(){
System.out.println("2");
}
}
创建了两个对象,n1和n2,n1.a()实际上锁住的是类对象,n2.b()锁住的是n2对象,所以两个线程仍然是锁住的不同对象,故可以看出两个线程仍然不互斥。
情况8:
public class Test8Blocks {
public static void main(String[] args) {
Number n1 =new Number();
Number n2 = new Number();
//因为是在方法上的synchronized,所以是在锁n1对象;
new Thread(()->{
System.out.println("begin");
try {
n1.a();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
System.out.println("begin");
n2.b();
}).start();
}
}
class Number{
public static synchronized void a() throws InterruptedException {
Thread.sleep(1);
System.out.println("1");
}
public static synchronized void b(){
System.out.println("2");
}
}
两个方法都经static synchronized修饰,故调用方法时n1.a()和n2.b()锁住的是同一个类对象Number.CLASS,所以即使是两个对象在访问,仍然可以实现互斥。