用关键字synchronized声明方法在某些情况下是有弊端的,比如A线程调用公布方法执行一个长时间的任务,那么B线程则必须等待比较长时间。在这样的情况下可以使用synchronized同步语句块来解决。
1.synchronized方法的弊端
我们来证明一下synchronized关键字声明方法是有弊端的。
代码示例:
方法类:
package test;
/**
* @Author LiBinquan
*/
public class Task {
private String getData1;
private String getData2;
public synchronized void doLongTimeTask(){
try{
System.out.println("开始 task");
Thread.sleep(3000);
getData1 = "长时间处理任务后远程返回的值 1 threadName = "+Thread.currentThread().getName();
getData2 = "长时间处理任务后远程返回的值 2 threadName = "+Thread.currentThread().getName();
System.out.println(getData1);
System.out.println(getData2);
System.out.println("end");
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
工具类:
package test;
/**
* @Author LiBinquan
*/
public class CommonUtils {
public static long beginTime1;
public static long endTime1;
public static long beginTime2;
public static long endTime2;
}
线程1:
package test;
/**
* @Author LiBinquan
*/
public class ThreadTest1 extends Thread{
private Task task;
public ThreadTest1(Task task){
super();
this.task = task;
}
@Override
public void run() {
super.run();
CommonUtils.beginTime1 = System.currentTimeMillis();
task.doLongTimeTask();
CommonUtils.endTime1 = System.currentTimeMillis();
}
}
线程2:
package test;
/**
* @Author LiBinquan
*/
public class ThreadTest2 extends Thread{
private Task task;
public ThreadTest2(Task task){
super();
this.task = task;
}
@Override
public void run() {
super.run();
CommonUtils.beginTime2 = System.currentTimeMillis();
task.doLongTimeTask();
CommonUtils.endTime2 = System.currentTimeMillis();
}
}
运行类:
package test;
/**
* @Author LiBinquan
*/
public class Run {
public static void main(String[] args) throws InterruptedException {
Task task = new Task();
ThreadTest1 threadTest1 = new ThreadTest1(task);
threadTest1.start();
ThreadTest2 threadTest2 = new ThreadTest2(task);
threadTest2.start();
try{
Thread.sleep(10000);
}catch (InterruptedException e){
e.printStackTrace();
}
long beginTime = CommonUtils.beginTime1;
if(CommonUtils.beginTime2 < CommonUtils.beginTime1){
beginTime = CommonUtils.beginTime2;
}
long endTime = CommonUtils.endTime1;
if(CommonUtils.endTime2 < CommonUtils.endTime1){
endTime = CommonUtils.endTime2;
}
System.out.println("耗时:"+ (endTime - beginTime)/1000);
}
}
输出:
在使用synchronized关键字俩声明方法 public synchronized void doLongTimeTask()时从运行的时间上来看,弊端很明显,解决这样的问题可以使用synchronized同步块。
2.synchronized同步代码块的使用
当两个并发线程访问同一个对象object中的synchronized(this)同步代码块时,一段时间内只能有一个线程被执行,另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
示例代码:
方法类:
package test;
/**
* @Author LiBinquan
*/
public class ObjectService {
public void serviceMethod(){
try{
synchronized (this){
System.out.println("begin beginTime = "+System.currentTimeMillis());
Thread.sleep(2000);
System.out.println("end endTime = "+System.currentTimeMillis());
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
线程1:
package test;
/**
* @Author LiBinquan
*/
public class ThreadTest1 extends Thread{
private ObjectService service;
public ThreadTest1(ObjectService service){
super();
this.service = service;
}
@Override
public void run() {
super.run();
service.serviceMethod();
}
}
线程2:
package test;
/**
* @Author LiBinquan
*/
public class ThreadTest2 extends Thread{
private ObjectService service;
public ThreadTest2(ObjectService service){
super();
this.service = service;
}
@Override
public void run() {
super.run();
service.serviceMethod();
}
}
运行类:
package test;
/**
* @Author LiBinquan
*/
public class Run {
public static void main(String[] args) throws InterruptedException {
ObjectService service = new ObjectService();
ThreadTest1 threadTest1 = new ThreadTest1(service);
threadTest1.setName("A");
threadTest1.start();
ThreadTest2 threadTest2 = new ThreadTest2(service);
threadTest2.setName("B");
threadTest2.start();
}
}
输出:
虽然这个示例中我们使用了synchronized同步代码块,但执行的效率还是没有提高,执行的效果还是同步运行的。
3.同步代码块来解决同步方法的弊端
这边修改一下 上面的方法就行,代码如下:
package test;
/**
* @Author LiBinquan
*/
public class Task {
private String getData1;
private String getData2;
public void doLongTimeTask(){
try{
System.out.println("开始 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");
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
输出:
通过上面的测试可以得知,当一个线程访问object的一个synchronized同步代码块时,另一个线程仍然可以访问该object对象中的非synchronized(this)同步代码块。
这边我们可以看到运行时间缩短但是同步synchronized代码块真的是同步的吗?真的持有当前调用对象的锁吗?答案为:是 ,这边我们就需要验证一下。
4.一半异步,一半同步
这边我们要证明一点:不在synchronized块中就是异步执行,在synchronized块中就是同步执行。
方法类:
package test;
/**
* @Author LiBinquan
*/
public class Task {
public void doLongTimeTask(){
for (int i = 0; i < 100; i++) {
System.out.println("非同步代码块 线程名 = "+Thread.currentThread().getName()+ " i = "+(i +1));
}
synchronized (this){
for (int i = 0; i < 100; i++) {
System.out.println("同步代码块 线程名 = "+Thread.currentThread().getName()+ " i = "+(i +1));
}
}
}
}
线程1:
package test;
/**
* @Author LiBinquan
*/
public class ThreadTest1 extends Thread{
private Task task;
public ThreadTest1(Task task){
super();
this.task = task;
}
@Override
public void run() {
super.run();
task.doLongTimeTask();
}
}
线程2:
package test;
/**
* @Author LiBinquan
*/
public class ThreadTest2 extends Thread{
private Task task;
public ThreadTest2(Task task){
super();
this.task = task;
}
@Override
public void run() {
super.run();
task.doLongTimeTask();
}
}
运行类:
package test;
/**
* @Author LiBinquan
*/
public class Run {
public static void main(String[] args) throws InterruptedException {
Task task = new Task();
ThreadTest1 threadTest1 = new ThreadTest1(task);
threadTest1.start();
ThreadTest2 threadTest2 = new ThreadTest2(task);
threadTest2.start();
}
}
输出:
非同步代码块交叉打印
同步代码块排队执行