java多线程同步 synchronized
来源:互联网 发布:三星网络电视50寸 编辑:程序博客网 时间:2024/04/29 12:30
注:本文内容参考自网络,源头以无法追溯,但是纯手工总结敲打。如有不准确之处,还请各位指教。
多线程同步概述
提到多线程就不得不提共享资源。当有多个线程去访问同一个共享资源时,会造成错误。比如比较常见的:同时写一个变量i。初始时i = 0,当一个线程读取 i 要对其加一操作时,另一个线程也到来了,读取 i 并对其加一操作,这时最后得到的结果i = 1。但是,理想情况下应该是 i = 2。这就是需要对多线程进行同步的典型情况。
下面举一个比较好理解的、发生在日常生活中的、略微有些dirty的,但是很形象的例子。(也是来自一个网友举的例子)假设在一个餐厅中只有一个洗手间,每个人如厕时就会锁上门,这样其他想如厕的人就要在外面等待,只有里面的人如厕完,打开锁出来之后,在外面等待的人才可以去竞争洗手间门的钥匙。在这里,每个人可以看成是一个线程,这个洗手间就是共享资源,洗手间门锁就是防止多人如厕的互斥锁。
多线程数数
假设有5个线程,每个线程都从1数到10。要求这5个线程依次从1数到10。
- 第一种实现
package org.fan.learn.synchronize;public class ThreadSynchronized extends Thread{ private int threadNo; public ThreadSynchronized(int threadNo) { this.threadNo = threadNo; } @Override public synchronized void run() { for (int i = 0; i < 10; i++) { System.out.println("No." + threadNo + " : " + i); } } public static void main(String[] args) { for (int i = 0; i < 5; i++) { new ThreadSynchronized(i).start(); } }}
这个例子的执行结果如下所示:
从上面的执行结果中可以看出,多个线程都很随意的数完了,而没有一个一个的数。虽然细心的读者可能也看到run方法使用了synchronized。为什么呢?因为,每一个线程都有一个synchronized,这样每个线程只是在自己内部做同步,没有任何意义。这相当于在一个餐厅中有5个洗手间,而5个顾客每人都有一把钥匙其匹配这5个洗手间。
2.第二种实现
既然已经知道第一种实现的症结所在,现在就可以解决它了。需要考虑这多个线程共用synchronized。可以使用下面这种实现。
package org.fan.learn.synchronize;public class ThreadSynchronized2 extends Thread{ private int threadNo; private String lock; public ThreadSynchronized2(int threadNo, String lock) { this.threadNo = threadNo; this.lock = lock; } @Override public void run() { synchronized (lock) { for (int i = 0; i < 10; i++) { System.out.println("No." + threadNo + " : " + i); } } } public static void main(String[] args) { String lock = new String("lock"); for (int i = 0; i < 5; i++) { new ThreadSynchronized2(i, lock).start(); } }}
这种实现的执行结果如下所示:
从上图可以看到这次满足了需求。
由于java方法参数的传递都是按值传递,所以在这5个线程中的成员变量lock都是指向的同一个地址,因此在synchronized(lock)时会请求同一个锁。最终,可以满足要求,每个线程依次数数。当然,每个线程的执行顺序就无法保证了。
3.第三种实现
那么还有没有其他的实现方式呢?其实所需要做的就是:使这5个线程能够共享某一个“对象”,这样请求锁时是请求的同一个,就可以满足依次数数的需求。我们知道静态变量和静态方法都是属于这个类的,而不是每个对象都有。这样就有了第三种实现。
package org.fan.learn.synchronize;public class ThreadSynchronized3 extends Thread{ private int threadNo; public ThreadSynchronized3(int threadNo) { this.threadNo = threadNo; } private static synchronized void abc(int threadNo) { for (int i = 0; i < 10; i++) { System.out.println("No." + threadNo + " : " + i); } } @Override public void run() { abc(threadNo); } public static void main(String[] args) { for (int i = 0; i < 5; i++) { new ThreadSynchronized3(i).start(); } }}
这种实现的执行结果如下所示:
从程序执行结果来看也满足了要求。
这里使用了一个static synchronized的方法,使得5个线程共享这个锁资源。
多线程累加
下面再看一个例子。需求是:创建5个,每个线程都能够从0加到9,也就会得出45这个数。
- 第一种实现
代码如下:
package org.fan.learn.synchronize;public class ThreadTest { class Count { private int num; public Count() { this.num = 0; } public void count() { for (int i = 0; i < 10; i++) { num += i; } System.out.println(Thread.currentThread().getName() + " num = " + num); } } public static void main(String[] args) { Runnable runnable = new Runnable() { ThreadTest threadTest = new ThreadTest(); ThreadTest.Count count = threadTest.new Count(); public void run() { count.count(); } }; for (int i = 0; i < 5; i++) { new Thread(runnable).start(); } }}
这个程序的执行的结果可以说是千奇百怪,当然也会有满足需求的时候,而且一些奇怪的数据“可遇而不可求”,我运行了好多次才得到两个奇怪的数据:
为什么会得到这么奇怪的数据呢?
因为,对象count的实例化ThreadTest.Count count = threadTest.new Count();在run方法的外面,这样多个线程就会共享这个count对象,count中的num成员变量也是被多个线程共享的。所以,多线程在同时运行时会对相同的num进行操作,而没有互斥。
2.第二种实现
第一种实现中既然没有互斥,那么,可以考虑给run方法加上个synchronized,代码如下所示:
package org.fan.learn.synchronize;public class ThreadTest2 { class Count { private int num; public Count() { this.num = 0; } public synchronized void count() { for (int i = 0; i < 10; i++) { num += i; } System.out.println(Thread.currentThread().getName() + " num = " + num); } } public static void main(String[] args) { Runnable runnable = new Runnable() { ThreadTest2 threadTest2 = new ThreadTest2(); ThreadTest2.Count count = threadTest2.new Count(); public void run() { count.count(); } }; for (int i = 0; i < 5; i++) { new Thread(runnable).start(); } }}
这个代码的执行结果如下所示:
从执行结果来看也没有满足需求。为什么呢?其实,跟第一种实现方式的原因是一样的。因为,对象count的实例化ThreadTest.Count count = threadTest.new Count();在run方法的外面,这样多个线程就会共享这个count对象,count中的num成员变量也是被多个线程共享的。由于,为run方法添加了synchronized,这样当一个线程在操作num时,其他线程就要等待。
那么,这个与“多线程数数”的第一种实现有什么不同呢?他们同样都是为run方法添加了synchronized修饰,“多线程数数”的第一种实现的执行结果就是杂乱的,没有一个线程一个线程的数数,但是这个累加却能一个线程一个线程的累加,而不出现第一种情况中奇怪的数据呢?因为,在“多线程数数”的第一种实现中,每个线程对象都有一个run方法,而“多线程累加”中每个线程共享count对象,使用同一个run方法。PS:虽然这种解释方式能够解释得通,但是java的内存对象模型是什么样的?对于“多线程数数”的第一种情况实现来说,是不是每个线程对象,都有一个run方法?看样是这样的。在“多线程数数”的第三种实现中,添加了方法private static synchronized void abc(int threadNo);使得多个线程共享这个方法。 内部的原理不是很清楚,”待从头,收拾旧山河,朝内存对象模型”。
3.第三种实现
在第二种实现中,实现了多线程累加的互斥,但是依然没有满足需求。其症结无非在:多个线程共享count对象。现在,使得每个线程都有一个count对象不就好了。
代码实现如下:
package org.fan.learn.synchronize;public class ThreadTest3 { class Count { private int num; public Count() { this.num = 0; } public void count() { for (int i = 0; i < 10; i++) { num += i; } System.out.println(Thread.currentThread().getName() + " num = " + num); } } public static void main(String[] args) { Runnable runnable = new Runnable() { ThreadTest3 threadTest3 = new ThreadTest3(); public void run() { ThreadTest3.Count count = threadTest3.new Count(); count.count(); } }; for (int i = 0; i < 5; i++) { new Thread(runnable).start(); } }}
这种实现的执行结果如下所示:
将count对象的实例化ThreadTest3.Count count = threadTest3.new Count();放在run方法内,这样每个线程在执行时都会new出一个新的对象,每个count对象都有一个num,这样就解决了问题。但是,每个线程都new一个count对象,是不是有点浪费?现在线程只有5个,多了怎么办?
4.第四种实现
在第三种实现中,有了新的问题。其实无非还是关于num是不是会被共享的问题。我们能不能让多个线程都共享一个count对象,但是不共享num呢?
代码如下:
package org.fan.learn.synchronize;public class ThreadTest4 { class Count { public void count() { int num = 0; for (int i = 0; i < 10; i++) { num += i; } System.out.println(Thread.currentThread().getName() + " num = " + num); } } public static void main(String[] args) { Runnable runnable = new Runnable() { ThreadTest4 threadTest4 = new ThreadTest4(); ThreadTest4.Count count = threadTest4.new Count(); public void run() { count.count(); } }; for (int i = 0; i < 5; i++) { new Thread(runnable).start(); } }}
代码的执行结果如下所示:
满足需求,而且多个线程共享同一个count对象。只是将int num = 0;放在了Count类的count方法内。
5.第五种实现
上面一种实现其实就是在说,不要存在“状态性对象”。但是,往往很难不存在状态性对象。如果,存在“状态性对象”,就像第四种实现一样。但是第四种实现,有一种很显然的弊端,就是每一个线程都存在一个独立的count对象,但是在web中,很多对象都是单例的。如何实现count对象是单例的,而又是“状态性对象”呢。ThreadLocal就可以做到这一点。代码如下:
package threadlocal;/** * Created by fan on 15-12-2. */public class Count { private ThreadLocal<Integer> num = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return 0; } }; public Integer count() { for (int i = 0; i < 10; i++) { num.set(num.get() + i); } return num.get(); }}
package threadlocal;/** * Created by fan on 15-12-2. */public class ThreadTest extends Thread { private Count count; public ThreadTest(Count count) { this.count = count; } public void run() { System.out.println("Current Thread : " + Thread.currentThread().getName() + " " + count.count()); } public static void main(String[] args) { Count count = new Count(); ThreadTest[] threadTest = new ThreadTest[5]; for (int i = 0; i < 5; i++) { threadTest[i] = new ThreadTest(count); threadTest[i].start(); } }}
TheadLocal,顾名思义,就是说,线程的本地化。他使得对象能够在每个执行线程中都有一个本地化的副本,这样,每个线程操作就是不同的、独立的对象。这是一种典型的“空间换时间”的例子。
运行结果如下所示:
关于ThreadLocal的详细介绍请参考这边文章(待写)。
- java Synchronized 多线程同步
- java多线程同步,Synchronized
- Java 多线程同步--synchronized
- Java 多线程 synchronized同步
- java多线程同步 synchronized
- Java多线程同步Synchronized详解
- Java多线程同步 synchronized关键字
- Java多线程同步与synchronized
- Java多线程同步 synchronized使用
- Java 多线程:synchronized 多线程同步关键字
- Java 多线程:synchronized 多线程同步关键字
- Java多线程入门:Synchronized同步类方法
- Java 多线程同步 锁机制与synchronized
- Java 多线程同步 锁机制与synchronized
- Java多线程同步Synchronized使用分析
- java 多线程的同步 wait notifyAll synchronized
- Java多线程:线程同步与关键字synchronized
- Java多线程同步Synchronized使用分析
- java基础 环境变量的作用
- 结构体字节对齐
- SQL语句中的日期计算
- 股票
- uva 10319 - Manhattan(2 sat)
- java多线程同步 synchronized
- 幸运抽奖程序
- 字符串去重
- c++之new, delete
- HttpClient的用法小结
- 创建二叉树以及 前序、中序、后序遍历二叉树
- Lock的简单使用
- CCMenuItem解析
- 剑指offer:斐波那契数列