在多个线程同时操作相同资源的时候,就会遇到并发的问题,如银行转账啊、售票系统啊等。为了避免这些问题的出现,我们可以使用synchronized关键字来解决,下面针对synchronized常见的用法做一个总结。首先写一个存在并发问题的程序,如下:

public class TraditionalThreadSynchronized {

	public static void main(String[] args) {
//在静态方法中不能new内部类的实例对象
//private Outputer outputer = new Outputer();
new TraditionalThreadSynchronized().init();
} private void init() {
final Outputer outputer = new Outputer();
//线程1打印:duoxiancheng
new Thread(new Runnable() {
@Override
public void run() {
while(true) {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
outputer.output1("duoxiancheng");
} }
}).start();; //线程2打印:eson15
new Thread(new Runnable() {
@Override
public void run() {
while(true) {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
outputer.output1("eson15");
} }
}).start();;
} static class Outputer {
//自定义一个字符串打印方法,一个个字符的打印
public void output1(String name) {
int len = name.length();
for(int i = 0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println("");
}
}
}

在内部类Outputer中定义了一个打印字符串的方法,一个字符一个字符的打印,这样比较容易直观的看出并发问题,因为字符顺序打乱了就说明出现问题了。然后在init方法中开启两个线程,一个线程打印“duoxiancheng”,另一个线程打印“eson15”。看一下运行结果:

eson15duoxianche

ng

eson15

duoxiancheng

duoxiancheng

eson15

esduoxiancheng

on15

duoxiancheng

已经出现问题了,为了解决这个问题,可以使用synchronized同步代码块来对公共部分进行同步操作,但是需要给它一把锁,这把锁是一个对象,可以是任意一个对象,但是前提是,两个线程使用的必须是同一个对象锁才可以,这很好理解。那么下面在output1()方法中加入synchronized代码块:

static class Outputer {
private String token = ""; //定义一个锁
public void output1(String name) {
synchronized(token) //任何一个对象都可以作为参数,但是该对象对于两个线程来说是同一个才行
//如果用name就不行了,因为不同的线程进来name是不一样的,不是同一个name
{
int len = name.length();
for(int i = 0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println("");
}
}
}

经过上面的改造,线程安全问题就基本解决了,但是还可以再往下引申,如果在方法上加synchronized关键字的话,那么这个同步锁是什么呢?我们在Outputer类中再写一个output2()方法:

static class Outputer {
private String token = ""; //定义一个锁
public void output1(String name) {
synchronized(token) //任何一个对象都可以作为参数,但是该对象对于两个线程来说是同一个才行
{
int len = name.length();
for(int i = 0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println("");
}
} public synchronized void output2(String name) { int len = name.length();
for(int i = 0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println("");
}
}

方法内部实现逻辑一模一样,唯一不同的就是synchronized加在了方法上,那么我们让init()方法中的两个线程中,一个调用output1(String name)方法,另一个调用output2(String name)方法,从结果中能看出,线程安全问题又出现了。产生问题的原因不难发现:现在两个方法都加了synchronized,但是两个线程在调用两个不同的方法还是出现了问题,也就是说,还是各玩各的……那么问题就出在这个锁上,说明两者并没有使用同一把锁!

如果我们把output1()方法中synchronized中的token改成this,再运行就没问题了,这说明一点:synchronized关键字修饰方法的时候,同步锁是this,即等效于代码块synchronized(this) {...}

再继续往下引申,现在在Outputer类中再写一个静态方法output3(String name),并且也让synchronized去修饰这个静态方法。

static class Outputer {
private String token = ""; //定义一个锁
public void output1(String name) {
synchronized(token) //任何一个对象都可以作为参数,但是该对象对于两个线程来说是同一个才行
{
int len = name.length();
for(int i = 0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println("");
}
} public static synchronized void output3(String name) { int len = name.length();
for(int i = 0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println("");
}
}
}

然后在init()方法中一个线程调用output1()方法,另一个线程调用output3()方法。毫无疑问,肯定又会出现线程安全问题。但是如何解决呢?因为static方法在类加载的时候就加载了,所以这个锁应该是类的字节码对象。那么将token改为Outputer.class就解决问题了,这说明一点:synchronized关键字修饰static方法的时候,同步锁是该类的字节码对象,即等效于代码块synchronized(classname.class) {...}

  

最后再总结一下:

  • 同步代码块的锁是任意对象。只要不同的线程都执行同一个同步代码块的时候,这个锁随便设。
  • 同步函数的锁是固定的this。当需要和同步函数中的逻辑实行同步的时候,代码块中的锁必须为this。
  • 静态同步函数的锁是该函数所属类的字节码文件对象。该对象可以用this.getClass()方法获取,也可以使用 当前类名.class 表示。

最新文章

  1. 解决Visual C++ Redistributable for Visual Studio 2015的安装问题
  2. centos虚拟机网络桥接配置
  3. BZOJ2301 [HAOI2011]Problem b
  4. Navi.Soft30.产品.代码生成器.操作手册
  5. WinForm给控件加入hint文字
  6. Linux学习之三——操作档案与目录
  7. HTML框架与布局
  8. HTML--9表单和验证事件
  9. Ubuntu14.04安装配置SVN及Trac
  10. strlen源码剖析
  11. 最简单的视频编码器:基于libx265(编码YUV为H.265)
  12. incallui中如何查询联系人数据
  13. Django:之Sitemap站点地图、通用视图和上下文渲染器
  14. java定时任务(三):timerTask定时任务
  15. mean项目的分模块开发
  16. IOS开发中获取当前WIFI的名字
  17. Docker面试题
  18. Ubuntu安装最新版nodejs
  19. C# Mongo Client 2.4.2判断是否存在表
  20. list的四种遍历方式

热门文章

  1. python框架Django实战商城项目之工程搭建
  2. idea最下方视图中没有spring框解决方法
  3. XiaoQi.Study项目(一)
  4. Fink SQL 实践之OVER窗口
  5. SpringBoot怎么自动部署到内置的Tomcat的?
  6. 李飞飞团队最新论文:基于anchor关键点的类别级物体6D位姿跟踪
  7. 我们是怎么实现Grpc CodeFirst
  8. leetcode 签到 914. 卡牌分组
  9. IO多路复用(IO Multiplexing)
  10. Python第六章-函数04-递归函数和拉姆达表达式