第七篇:静态同步synchronized方法、synchronized(class)代码块、数据类型String常量池
一、静态同步synchronized方法
关键字synchronized还可以应用在static静态方法上,如果这样写,那是对当前的*.java文件对应的Class类进行持锁
public class Service { synchronized public static void printA() { try { System.out.println("threadName:" + Thread.currentThread().getName() + "进入printA"); Thread.sleep(3000); System.out.println("threadName:" + Thread.currentThread().getName() + "离开printA"); } catch (InterruptedException e) { e.printStackTrace(); } } synchronized public static void printB() { try { System.out.println("threadName:" + Thread.currentThread().getName() + "进入printB"); Thread.sleep(3000); System.out.println("threadName:" + Thread.currentThread().getName() + "离开printB"); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { ThreadA a = new ThreadA(); a.setName("a"); a.start(); ThreadB b = new ThreadB(); b.setName("b"); b.start(); } } class ThreadA extends Thread { @Override public void run() { Service.printA(); } } class ThreadB extends Thread { @Override public void run() { Service.printB(); } }
运行结果
threadName:a进入printA threadName:a离开printA threadName:b进入printB threadName:b离开printB
结论:从结果来看,是同步效果,和将synchronized关键字加到非static方法上使用的效果是一样的,其实还是有本质上的不同的,synchronized关键字加到static静态方法上是给Class类上锁,而synchronized关键字加到非static静态方法上是给对象上锁
public class Service { synchronized public static void printA() { try { System.out.println("threadName:" + Thread.currentThread().getName() + "进入printA"); Thread.sleep(3000); System.out.println("threadName:" + Thread.currentThread().getName() + "离开printA"); } catch (InterruptedException e) { e.printStackTrace(); } } synchronized public static void printB() { try { System.out.println("threadName:" + Thread.currentThread().getName() + "进入printB"); Thread.sleep(3000); System.out.println("threadName:" + Thread.currentThread().getName() + "离开printB"); } catch (InterruptedException e) { e.printStackTrace(); } } synchronized public void print() { try { System.out.println("threadName:" + Thread.currentThread().getName() + "进入printC"); Thread.sleep(3000); System.out.println("threadName:" + Thread.currentThread().getName() + "离开printC"); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { ThreadA a = new ThreadA(); a.setName("a"); a.start(); ThreadB b = new ThreadB(); b.setName("b"); b.start(); Service service = new Service(); ThreadC c = new ThreadC(service); c.setName("c"); c.start(); } } class ThreadA extends Thread { @Override public void run() { Service.printA(); } } class ThreadB extends Thread { @Override public void run() { Service.printB(); } } class ThreadC extends Thread { private Service service; public ThreadC(Service service) { this.service = service; } @Override public void run() { service.print(); } }
运行结果
threadName:a进入printA threadName:c进入printC threadName:a离开printA threadName:c离开printC threadName:b进入printB threadName:b离开printB
结论:异步原因是持有不同的锁,一个是对象锁,另外一个是Class锁,而Class锁可以对所有对象实例起作用
二、synchronized(class)代码块
同步synchronized(class)代码块的作用其实和synchronized static方法的作用一样,看例子:
public class Service { public static void printA() { synchronized (Service.class) { try { System.out.println("threadName:" + Thread.currentThread().getName() + "进入printA"); Thread.sleep(3000); System.out.println("threadName:" + Thread.currentThread().getName() + "离开printA"); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void printB() { synchronized (Service.class) { try { System.out.println("threadName:" + Thread.currentThread().getName() + "进入printB"); Thread.sleep(3000); System.out.println("threadName:" + Thread.currentThread().getName() + "离开printB"); } catch (InterruptedException e) { e.printStackTrace(); } } } synchronized public void print() { try { System.out.println("threadName:" + Thread.currentThread().getName() + "进入printC"); Thread.sleep(3000); System.out.println("threadName:" + Thread.currentThread().getName() + "离开printC"); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { ThreadA a = new ThreadA(); a.setName("a"); a.start(); ThreadB b = new ThreadB(); b.setName("b"); b.start(); Service service = new Service(); } } class ThreadA extends Thread { @Override public void run() { Service.printA(); } } class ThreadB extends Thread { @Override public void run() { Service.printB(); } }
运行结果:
threadName:a进入printA threadName:a离开printA threadName:b进入printB threadName:b离开printB
结论:结果顺序执行
三、数据类型String的常量池特性
JVM中具有String常量池缓存的功能,所以将synchronized(string)同步块与String结合使用时,要注意常量池以带来的一些例外
public class Service { public static void print(String str) { try { synchronized (str) { while (true) { System.out.println(Thread.currentThread().getName()); Thread.sleep(1000); } } } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { Service service = new Service(); ThreadA a = new ThreadA(service); a.setName("a"); a.start(); ThreadB b = new ThreadB(service); b.setName("b"); b.start(); } } class ThreadA extends Thread { private Service service; public ThreadA(Service service) { this.service = service; } @Override public void run() { service.print("AA"); } } class ThreadB extends Thread { private Service service; public ThreadB(Service service) { this.service = service; } @Override public void run() { service.print("AA"); } }
运行结果:
a a a a a ...
结论:这种情况是因为String的两个值都是AA,两个线程持有相同的锁,所以造成线程B不能执行。这就是String常量池所带来的问题,因此大多数情况下,同步synchronized代码块都不使用String作为锁对象,而改用其他,比如new Object()实例化一个Object对象,但它并不放入缓存中。
四、同步synchronized方法无限等待与解决
public class Service {
synchronized public static void methodA() {
System.out.println("methodA begin");
boolean isRun = true;
while (true) {
}
System.out.println("ethodA end");
}
synchronized public static void methodB() {
System.out.println("methodB begin");
System.out.println("ethodB end");
}
public static void main(String[] args) {
Service service = new Service();
ThreadA a = new ThreadA(service);
a.setName("a");
a.start();
ThreadB b = new ThreadB(service);
b.setName("b");
b.start();
}
}
class ThreadA extends Thread {
private Service service;
public ThreadA(Service service) {
this.service = service;
}
@Override
public void run() {
service.methodA();
}
}
class ThreadB extends Thread {
private Service service;
public ThreadB(Service service) {
this.service = service;
}
@Override
public void run() {
service.methodB();
}
}
运行结果:
methodA begin
结论:线程B永远得不到运行机会,锁死了
可以使用同步块解决这样的问题
Object o1 = new Object(); public void methodA() { synchronized (o1) { System.out.println("methodA begin"); boolean isRun = true; while (true) { } System.out.println("ethodA end"); } } Object o2 = new Object(); public void methodB() { synchronized (o2) { System.out.println("methodB begin"); System.out.println("ethodB end"); } }
运行结果:
methodA begin methodB begin ethodB end
五、多线程死锁
Java多线程死锁是一个经典的多线程问题,因为不同线程都在等待根本不可能被释放的锁,从而导致所有的任务都无法继续完成。在多线程技术中,“死锁”是必须避免的,因为这会造成“假死”
//死锁实现1
public class Service implements Runnable { public String username; public Object lock1 = new Object(); public Object lock2 = new Object(); public void setFlag(String username) { this.username = username; } @Override public void run() { if (username.equals("a")) { synchronized (lock1) { try { System.out.println("username=" + username); Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock2) { System.out.println("按lock1->lock2代码的顺序执行"); } } } if (username.equals("b")) { synchronized (lock2) { try { System.out.println("username=" + username); Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock1) { System.out.println("按lock2->lock1代码的顺序执行"); } } } } public static void main(String[] args) { try { Service s1 = new Service(); s1.setFlag("a"); Thread thread1 = new Thread(s1); thread1.start(); Thread.sleep(100); s1.setFlag("b"); Thread thread2 = new Thread(s1); thread2.start(); } catch (InterruptedException e) { e.printStackTrace(); } } }
//死锁实现2
public static void main(String[] args) throws Exception{
String o1 = new String("a");
String o2 = new String("b");
new Thread(() ->{
synchronized (o1){
System.out.println(o1);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2){
System.out.println("o1->o2");
}
}
}).start();
new Thread(() ->{
synchronized (o2){
System.out.println(o2);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1){
System.out.println("o2->o1");
}
}
}).start();
}
运行结果:
username=a username=b
使用JDK自带的工具来监测是否有死锁的现象,进入JDK安卓文件夹的bin目录下面执行jps
得到线程运行的id是30550,在执行jstack命令(命令为:jstack -l 30550),查看结果:
死锁是程序设计的bug,在程序设计的时候要避免设计出多线程互相等待对方释放锁出现死锁的情况