synchronized修饰方法的弊端
如果A线程调用同步方法执行一个长时间的任务,那么B线程等待的时间就比较长,这种情况可以使用synchronized同步语句块来解决,以提高运行效率。
synchronized方法是将当前对象作为锁,而synchronized代码块是将任意对象作为锁。可以将锁看成一个标识,哪个线程持有这个标识,就可以执行同步方法
用同步代码块解决同步方法的弊端,提升效率
public class Task {
private String getData1;
private String getData2;
public void doLongTimeTask() {
try {
System.out.println("begin task");
Thread.sleep(3000);
String privateGetData1 =
"长时间处理任务后从远程返回的值1 threadName=" + Thread.currentThread().getName();
String privateGetData2 =
"长时间处理任务后从远程返回的值2 threadName=" + Thread.currentThread().getName();
synchronized (this) {
getData1 = privateGetData1;
getData2 = privateGetData2;
System.out.println(getData1);
System.out.println(getData2);
System.out.println("end task");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadA extends Thread {
private Task task;
public ThreadA(Task task) {
super();
this.task = task;
}
@Override
public void run() {
super.run();
task.doLongTimeTask();
}
}
public class ThreadB extends Thread {
private Task task;
public ThreadB(Task task) {
super();
this.task = task;
}
@Override
public void run() {
super.run();
task.doLongTimeTask();
}
}
public class Test {
public static void main(String[] args) {
Task task = new Task();
ThreadA threadA = new ThreadA(task);
ThreadB threadB = new ThreadB(task);
threadA.start();
threadB.start();
}
}
output:
begin task
begin task
长时间处理任务后从远程返回的值1 threadName=Thread-1
长时间处理任务后从远程返回的值2 threadName=Thread-1
end task
长时间处理任务后从远程返回的值1 threadName=Thread-0
长时间处理任务后从远程返回的值2 threadName=Thread-0
end task
当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object对象中的非synchronized(this)同步代码块。
synchronized代码块间的同步性
当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对同一个object中所有其他synchronized(this)同步代码块的访问将被阻塞,这说明synchronized使用的对象监视器是同一个,即使用的锁是同一个
public class MyObject {
synchronized public void methodA() {
try {
System.out.println("begin methodA threadName=" + Thread.currentThread().getName());
Thread.sleep(5000);
System.out.println("methodA end");
} catch (Exception e) {
e.printStackTrace();
}
}
synchronized public void methodB() {
try {
System.out.println("begin methodB threadName=" + Thread.currentThread().getName());
Thread.sleep(5000);
System.out.println("methodB end");
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class Test {
public static void main(String[] args) {
Service service = new Service();
ThreadA threadA = new ThreadA(service);
ThreadB threadB = new ThreadB(service);
threadA.start();
threadB.start();
}
}
output:
A begin time=1652974614229
A end end=1652974616244
B begin time=1652974616244
B end end=1652974616244
println()方法也是同步的
jdk源码如下:
public void println(String x) {
synchronized (this) {
print(x);
newLine();
}
}
public void println(Object x) {
String s = String.valueOf(x);
synchronized (this) {
print(s);
newLine();
}
}
使用sychronized(this)代码块,执行按照顺序执行,输出的数据是完整的,不会出现信息交叉混乱的情况。
synchronized(this)代码块是锁定当前对象的
和synchronized方法一样,synchronized(this)代码块也是锁定当前对象的。
将任意对象作为锁
多个线程调用同一个对象中的不同名称的synchronized同步方法或synchronized(this)同步代码块时,调用的效果是按顺序执行,即同步。
synchronized同步方法的作用:
- 对其他synchronized同步方法或者synchronized(this)同步代码块调用呈同步效果。
- 同一时间只有一个线程可以执行synchronized同步方法中的代码。
synchronized(this)同步代码块的作用:
- 对其他synchronized同步方法或者synchronized(this)同步代码块调用呈同步效果。
- 同一时间只有一个线程可以执行synchronized(this)同步代码块中的代码。
java还支持将“任意对象”作为锁来实现同步功能,这个“任意对象”大多数是实例变量及方法的参数,使用格式为synchronized(非this对象)。
锁非this对象的优点:
synchronized(非this)代码块中的程序与同步方法是异步的,因为有两把锁,不与其他锁this同步方法抢this锁,可以大大提高效率。
多个锁就是异步执行
public class Service {
private String username;
private String password;
public void setUsername(String username, String password) {
try {
String anyString = new String();
synchronized (anyString) {
System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入同步块");
this.username = username;
Thread.sleep(3000);
this.password = password;
System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开同步块");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Test {
public static void main(String[] args) {
Service service = new Service();
ThreadA threadA = new ThreadA(service);
ThreadB threadB = new ThreadB(service);
threadA.start();
threadB.start();
}
}
output:
线程名称为:Thread-0在1653052403930进入同步块
线程名称为:Thread-1在1653052403945进入同步块
线程名称为:Thread-1在1653052406960离开同步块
线程名称为:Thread-0在1653052406960离开同步块
anyString 在两个线程中分别对应两个String对象,不是同一个锁,运行结果异步调用。
public class Service {
private String username;
private String password;
private String anyString = new String();
public void a() {
try {
synchronized (anyString) {
System.out.println("a begin");
Thread.sleep(3000);
System.out.println("a end");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized public void b() {
System.out.println("b begin");
System.out.println("b end");
}
}
output:
a begin
b begin
b end
a end
验证方法被调用是随机的
同步代码块并不能保证调用方法的线程的执行同步(顺序性),也就是线程调用方法的顺序是无序的,虽然在同步块中执行的顺序是同步的。
public class Service {
private List list = new ArrayList();
synchronized public void add(String username) {
System.out.println("ThreadName=" + Thread.currentThread().getName() + "执行了add方法!");
list.add(username);
System.out.println("ThreadName=" + Thread.currentThread().getName() + "退出了add方法!");
}
}
public class ThreadA extends Thread {
private Service service;
public ThreadA(Service service) {
this.service = service;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
service.add("threadA" + (i + 1));
}
}
}
public class ThreadB extends Thread {
private Service service;
public ThreadB(Service service) {
this.service = service;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
service.add("threadB" + (i + 1));
}
}
}
output:
ThreadName=A执行了add方法!
ThreadName=A退出了add方法!
ThreadName=B执行了add方法!
ThreadName=B退出了add方法!
ThreadName=B执行了add方法!
ThreadName=B退出了add方法!
ThreadName=B执行了add方法!
ThreadName=B退出了add方法!
ThreadName=A执行了add方法!
ThreadName=A退出了add方法!
不同步导致的逻辑错误及其解决方法
public class MyOneList {
private List list = new ArrayList();
synchronized public void add(String data) {
list.add(data);
}
synchronized public int getSize() {
return list.size();
}
}
public class Service {
public MyOneList addServiceMethod(MyOneList list, String data) {
try {
if (list.getSize() < 1) {
Thread.sleep(2000);
list.add(data);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return list;
}
}
public class ThreadA extends Thread {
private MyOneList list;
public ThreadA(MyOneList list){
this.list = list;
}
@Override
public void run(){
Service service = new Service();
service.addServiceMethod(list,"A");
}
}
public class ThreadB extends Thread {
private MyOneList list;
public ThreadB(MyOneList list) {
this.list = list;
}
@Override
public void run() {
Service service = new Service();
service.addServiceMethod(list, "B");
}
}
output:
listSize=2
出现错误的原因是两个线程异步的方式返回list参数的size()大小,解决办法就是“同步化”
public class Service {
public MyOneList addServiceMethod(MyOneList list, String data) {
try {
synchronized (list) {
if (list.getSize() < 1) {
Thread.sleep(2000);
list.add(data);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return list;
}
}
output:
listSize=1
细化验证3个结论
synchronized(非this对象x)格式的写法是将x对象本身作为“对象监视器”,可以得出3个结论:
- 当多个线程同时执行synchronized(x){}同步代码块时呈同步效果。
- 当其他线程执行x对象中synchronized同步方法时呈同步效果。
- 当其他线程执行x对象方法里面的synchronized(this)代码块时呈现同步效果。