1.synchronized代码块的同步性
在使用同步synchronized(this)代码块时需要注意的是,当一个线程访问object的一个synchronized(this)同步代码块时,其它线程对同一个object中所有气他synchronized(this)同步代码块的访问将被阻塞,这说明synchronized使用的“对象监视器”是同一个。
测试用例如下
package test;
/**
* @Author LiBinquan
*/
public class ObjectService {
public void serviceMethodA(){
try{
synchronized (this){
System.out.println("A begin beginTime = "+System.currentTimeMillis());
Thread.sleep(2000);
System.out.println("A end endTime = "+System.currentTimeMillis());
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
public void serviceMethodB(){
synchronized (this){
System.out.println("B begin beginTime = "+System.currentTimeMillis());
System.out.println("B end endTime = "+System.currentTimeMillis());
}
}
}
线程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.serviceMethodA();
}
}
线程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.serviceMethodB();
}
}
运行类:
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();
}
}
输出:
2.验证synchronized(this)代码块是锁定当前对象
和synchronized方法一样,synchronized(this)代码块也是锁定当前对象的。
测试代码如下
package test;
/**
* @Author LiBinquan
*/
public class Task {
public void otherMethod(){
System.out.println("---------run otherMethod---------");
}
public void doLongTimeTask(){
synchronized (this){
for (int i = 0; i < 10000; 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.otherMethod();
}
}
运行类:
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();
Thread.sleep(100);
ThreadTest2 threadTest2 = new ThreadTest2(task);
threadTest2.start();
}
}
输出结果:
由输出可以看出是非同步的。
修改一下task类里面的方法,如下:
在这里插入代码片
由输出可得是同步打印的。
3.将任意对象作为对象的监听器
多个线程调用同一个对象中的不同名称的synchronized同步方法或synchronized(this)同步代码块时,调用的效果就是按顺序执行,也就是同步的、阻塞的。
这说明synchronized同步方法或synchronized(this)同步代码块分别有两种作用。
- synchronized同步方法
1)对其他synchronized同步方法或synchronized(this)同步代码块调用呈阻塞状态
2)同一时间只有一个线程可以执行synchronized同步方法中的代码。 - synchronized(this)同步代码块
1)对其他synchronized同步方法或synchronized(this)同步代码块调用呈阻塞状态
2)同一时间只有一个线程可以执行synchronized(this)
同步方法中的代码。
根据我之前讲到的,使用synchronized(this)格式来同步代码块,其实Java还支持对“任意对象”作为“对象监视器”来实现同步的功能。这个“任意对象”来实现同步的功能。这个“任意对象”大多数是实例变量及方法参数,使用格式为synchronized(非this对象)。
根据前面对synchronized(this)同步代码块的作用总结可知,synchronized(非this对象)格式的作用只有1种:synchronized(非this对象)同步代码块。
1)在多个线程持有“对象监视器”为同一个对象的前提下,同一时间只有一个线程可以执行synchronized(非this对象)同步代码块中的代码。
2)当持有“对象监视器”为同一对象的前提下,同一时间只有一个线程可以执行synchronized(非this对象)同步代码块中的代码。
第一点论证代码如下
package test;
/**
* @Author LiBinquan
*/
public class Service {
private String userNameParam;
private String passWordParam;
private String anyString = new String();
public void setUserNamePassWord(String userName,String passWord){
try{
synchronized (anyString) {
System.out.println("线程名称为:" + Thread.currentThread().getName()
+ " 在 " + System.currentTimeMillis()
+ " 进入同步块");
Thread.sleep(3000);
System.out.println("线程名称为:" + Thread.currentThread().getName()
+ " 在 " + System.currentTimeMillis()
+ " 离开同步块");
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
线程1:
package test;
/**
* @Author LiBinquan
*/
public class ThreadTest1 extends Thread{
private Service service;
public ThreadTest1(Service service){
super();
this.service = service;
}
@Override
public void run() {
super.run();
service.setUserNamePassWord("a","aa");
}
}
线程2:
package test;
/**
* @Author LiBinquan
*/
public class ThreadTest2 extends Thread{
private Service service;
public ThreadTest2(Service service){
super();
this.service = service;
}
@Override
public void run() {
super.run();
service.setUserNamePassWord("b","bb");
}
}
运行类:
package test;
/**
* @Author LiBinquan
*/
public class Run {
public static void main(String[] args) throws InterruptedException {
Service service = new Service();
ThreadTest1 threadTest1 = new ThreadTest1(service);
threadTest1.setName("A");
threadTest1.start();
ThreadTest2 threadTest2 = new ThreadTest2(service);
threadTest2.setName("B");
threadTest2.start();
}
}
输出:
锁非this对象具有一定的有点:如果在一个类中有很多个synchronized方法,这时虽然能实现同步,但会受到阻塞,所以影响运行效率;但如果使用同步代码块锁非this对象,则synchronized(非this)代码块中的程序与同步方法是异步的,不与其他锁this同步方法争抢this锁,则可大大提高运行效率。
修改Service类:
代码如下
package test;
/**
* @Author LiBinquan
*/
public class Service {
private String userNameParam;
private String passWordParam;
public void setUserNamePassWord(String userName,String passWord){
try{
String anyString = new String();
synchronized (anyString) {
System.out.println("线程名称为:" + Thread.currentThread().getName()
+ " 在 " + System.currentTimeMillis()
+ " 进入同步块");
Thread.sleep(3000);
System.out.println("线程名称为:" + Thread.currentThread().getName()
+ " 在 " + System.currentTimeMillis()
+ " 离开同步块");
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
输出:
可见,使用synchronized(非this对象)同步代码块格式进行同步操作时,对象监视器必须是同一个对象。如果不是同一个对象监视器,运行的结果就是异步调用了,就是交叉运行。
下面再用另一个测试用例来验证2.
package test;
/**
* @Author LiBinquan
*/
public class Service {
private String anyString = new String();
public void A(){
try{
synchronized (anyString) {
System.out.println("A 开始");
Thread.sleep(3000);
System.out.println("A结束");
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
synchronized public void B(){
System.out.println("B 开始");
System.out.println("B 结束");
}
}
线程1:
package test;
/**
* @Author LiBinquan
*/
public class ThreadTest1 extends Thread{
private Service service;
public ThreadTest1(Service service){
super();
this.service = service;
}
@Override
public void run() {
super.run();
service.A();
}
}
线程2:
package test;
/**
* @Author LiBinquan
*/
public class ThreadTest2 extends Thread{
private Service service;
public ThreadTest2(Service service){
super();
this.service = service;
}
@Override
public void run() {
super.run();
service.B();
}
}
运行类:
package test;
/**
* @Author LiBinquan
*/
public class Run {
public static void main(String[] args) throws InterruptedException {
Service service = new Service();
ThreadTest1 threadTest1 = new ThreadTest1(service);
threadTest1.setName("A");
threadTest1.start();
ThreadTest2 threadTest2 = new ThreadTest2(service);
threadTest2.setName("B");
threadTest2.start();
}
}
输出:
由于对象监视器不同,所以运行结果就是异步的。
同步代码块放在非同步synchronized方法中进行声明,并不能保证调用方法的线程的执行同步/顺序性,也就是线程调用方法的顺序是无需的,虽然在同步块中执行的顺序是同步的,这样极易出现“脏读”问题。
使用“synchronized(非this对象)同步代码块”格式也可以解决“脏读”问题。但在解决脏读问题之前,先做一个小测试,测试代码如下:
package test;
import java.util.ArrayList;
import java.util.List;
/**
* @Author LiBinquan
*/
public class MyList {
private List list = new ArrayList();
synchronized public void add(String userName){
System.out.println("线程名称 :"+Thread.currentThread().getName()
+ "执行了add方法");
list.add(userName);
System.out.println("线程名称 :"+Thread.currentThread().getName()
+ "退出了add方法");
}
synchronized public int getSize(){
System.out.println("线程名称 :"+Thread.currentThread().getName()
+ "执行了add方法");
int sizeValue = list.size();
System.out.println("线程名称 :"+Thread.currentThread().getName()
+ "退出了add方法");
return sizeValue;
}
}
线程1:
package test;
/**
* @Author LiBinquan
*/
public class ThreadTest1 extends Thread{
private MyList list;
public ThreadTest1(MyList list){
super();
this.list = list;
}
@Override
public void run() {
super.run();
for (int i = 0; i < 10000; i++) {
list.add("threadA = "+(i+1));
}
}
}
线程2:
package test;
/**
* @Author LiBinquan
*/
public class ThreadTest2 extends Thread{
private MyList list;
public ThreadTest2(MyList list){
super();
this.list = list;
}
@Override
public void run() {
super.run();
for (int i = 0; i < 10000; i++) {
list.add("threadB = "+(i+1));
}
}
}
运行类:
package test;
/**
* @Author LiBinquan
*/
public class Run {
public static void main(String[] args) throws InterruptedException {
MyList list = new MyList();
ThreadTest1 threadTest1 = new ThreadTest1(list);
threadTest1.setName("A");
threadTest1.start();
ThreadTest2 threadTest2 = new ThreadTest2(list);
threadTest2.setName("B");
threadTest2.start();
}
}
输出:
从运行结果来看,同步块中的代码是同步打印的,当前线程“执行”与“退出”是成对出现的,但是线程A和线程B的执行确实异步的,这就有可能出现脏读的环境。由于线程执行方法的顺序不确定,所以当A和B两个线程执行带有分支判断的方法时,就会出现逻辑上的错误,有可能出现脏读。
接下来以一段实际用例来说明:
package test;
import java.util.ArrayList;
import java.util.List;
/**
* @Author LiBinquan
*/
public class MyOneList {
private List list = new ArrayList();
synchronized public void add(String data){
list.add(data);
}
synchronized public int getSize(){
return list.size();
}
}
package test;
/**
* @Author LiBinquan
*/
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;
}
}
线程1:
package test;
/**
* @Author LiBinquan
*/
public class ThreadTest1 extends Thread{
private MyOneList list;
public ThreadTest1(MyOneList list){
super();
this.list = list;
}
@Override
public void run() {
Service service = new Service();
service.addServiceMethod(list,"A");
}
}
线程2:
package test;
/**
* @Author LiBinquan
*/
public class ThreadTest2 extends Thread{
private MyOneList list;
public ThreadTest2(MyOneList list){
super();
this.list = list;
}
@Override
public void run() {
Service service = new Service();
service.addServiceMethod(list,"B");
}
}
运行类:
package test;
/**
* @Author LiBinquan
*/
public class Run {
public static void main(String[] args) throws InterruptedException {
MyOneList list = new MyOneList();
ThreadTest1 threadTest1 = new ThreadTest1(list);
threadTest1.setName("A");
threadTest1.start();
ThreadTest2 threadTest2 = new ThreadTest2(list);
threadTest2.setName("B");
threadTest2.start();
Thread.sleep(6000);
System.out.println("ListSize = "+list.getSize());
}
}
输出:
“脏读”出现了。出现的原因是两个线程异步的方式返回了list参数的size()大小。解决办法是“同步化”
这边修改Service类,代码如下:
package test;
/**
* @Author LiBinquan
*/
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;
}
}
由于list参数对象在项目中是一份实例,是单例的,而且也正需要对list参数的getSize()方法做同步的调用,所以就对list参数进行了同步化处理。
输出如下:
转载:https://blog.csdn.net/qq_27581753/article/details/105931798