MENU

线程同步

June 30, 2018 • Read: 3700 • Java阅读设置

对访问同一个资源的多个线程进行协调的过程,就叫线程同步

用一个简单的例子讲述线程同步问题:

小明账户里有 3000 元钱,他拿存折去银行取 2000,银行的机器首先判断账户里的钱够不够 2000,判断够。与此同时,小明的老婆拿着小明的银行卡去了另一家银行取钱,也取 2000,判断也够,小明的老婆取出来了,账户里的钱变为了 1000,这时小明这边的机器已经判断过足够了,所以小明也取出了 2000,账户的钱变为 1000。最终的结果就是,小明和他老婆分别取了 2000 块钱,账户还剩 1000。

解决这个问题的方法也很简单,只要规定,访问统一账户的人同时只能有一个。就比方说,两台电脑不能同时登陆同一个 qq

下面先看一个示例

  • public class TestSync implements Runnable{
  • Timer timer = new Timer();
  • public static void main(String[] args) {
  • TestSync test = new TestSync();
  • Thread t1 = new Thread(test);
  • Thread t2 = new Thread(test);
  • t1.setName("t1");
  • t2.setName("t2");
  • t1.start();
  • t2.start();
  • }
  • public void run() {
  • timer.add(Thread.currentThread().getName());
  • }
  • }
  • class Timer{
  • private static int num = 0;
  • public void add(String name) {
  • num++;
  • try {
  • Thread.sleep(1);
  • }catch(InterruptedException e) {}
  • System.out.println(name + ",你是第" + num + "个使用timer的线程");
  • }
  • }

首先用一个内存图分析一下整个程序:

image
输出结果为:

  • t1,你是第2个使用timer的线程
  • t2,你是第2个使用timer的线程

分析一下这个程序的执行:

首先一个线程在执行 add 方法的过程中,执行了一次 num++,此时 num 的值是 1,然后当前线程 sleep,另一个线程开始执行 add 方法,又执行了一次 num++,此时 num 的值是 2,然后这个线程 sleep。上一个线程 sleep 结束了,输出,num 的值就是 2,然后另一个线程 sleep 也结束了,输出,num 的值也是 2

其实这就跟前面的取钱例子一样,解决办法就是给 add 方法加一把锁,让他同时只能有一个线程访问,不论当前线程是否睡眠,只有当访问 add 方法的线程结束了,才能允许另一个线程访问

  • public class TestSync implements Runnable{
  • Timer timer = new Timer();
  • public static void main(String[] args) {
  • TestSync test = new TestSync();
  • Thread t1 = new Thread(test);
  • Thread t2 = new Thread(test);
  • t1.setName("t1");
  • t2.setName("t2");
  • t1.start();
  • t2.start();
  • }
  • public void run() {
  • timer.add(Thread.currentThread().getName());
  • }
  • }
  • class Timer{
  • private static int num = 0;
  • public void add(String name) {
  • synchronized (this) {
  • num++;
  • try {
  • Thread.sleep(1);
  • }catch(InterruptedException e) {}
  • System.out.println(name + ",你是第" + num + "个使用timer的线程");
  • }
  • }
  • }

另一种写法,直接给方法加锁

  • public class TestDeadLock implements Runnable{
  • public int flag = 1;
  • static Object o1 = new Object(),o2 = new Object();
  • public void run() {
  • System.out.println("flag = " + flag);
  • if(flag == 1) {
  • synchronized(o1) {
  • try {
  • Thread.sleep(500);
  • }catch(InterruptedException e) {
  • e.printStackTrace();
  • }
  • synchronized(o2) {
  • System.out.println("1");
  • }
  • }
  • }
  • if(flag == 0) {
  • synchronized(o2) {
  • try {
  • Thread.sleep(500);
  • }catch(Exception e) {
  • e.printStackTrace();
  • }
  • synchronized(o1) {
  • System.out.println("2");
  • }
  • }
  • }
  • }
  • public static void main(String[] args) {
  • TestDeadLock td1 = new TestDeadLock();
  • TestDeadLock td2 = new TestDeadLock();
  • td1.flag = 1;
  • td2.flag = 0;
  • Thread t1 = new Thread(td1);
  • Thread t2 = new Thread(td2);
  • t1.start();
  • t2.start();
  • }
  • }

运行结果如下图:

image
首先分别给两个线程内的 flag 变量分别赋值为 1 和 0,那么他们就会分别执行对应的 if 语句。对于 flag = 1 来说,它先锁住 o1,让它 sleep500 毫秒,只要再锁住 o2,就可以打印 1 了;对于 flag = 0 来说,它先锁住 o2,让它 sleep500 毫秒,只要再锁住 o1,就可以打印 2 了,所以他们分别锁住一个线程对象,等待另一个线程对象,但是他们永远不可能锁住另一个线程对象了,所以程序进入了死锁

一道面试题展开目录

  • public class TT implements Runnable{
  • static int b = 100;
  • public synchronized static void m1() throws InterruptedException {
  • b = 1000;
  • Thread.sleep(5000);
  • System.out.println("b = " + b);
  • }
  • public static void m2() {
  • b = 2000;
  • System.out.println(b);
  • }
  • public void run() {
  • try {
  • m1();
  • } catch (InterruptedException e) {
  • e.printStackTrace();
  • }
  • }
  • public static void main(String[] args) throws InterruptedException {
  • TT tt = new TT();
  • Thread t = new Thread(tt);
  • t.start();
  • Thread.sleep(1000);
  • tt.m2();
  • }
  • }

上述程序输出结果是什么?

正确答案:

  • 2000
  • b = 2000

面试题变形展开目录

  • public class TT implements Runnable{
  • static int b = 100;
  • public synchronized static void m1() throws InterruptedException {
  • b = 1000;
  • Thread.sleep(5000);
  • System.out.println("b = " + b);
  • }
  • public synchronized static void m2() throws InterruptedException {
  • Thread.sleep(2500);
  • b = 2000;
  • }
  • public void run() {
  • try {
  • m1();
  • } catch (InterruptedException e) {
  • e.printStackTrace();
  • }
  • }
  • public static void main(String[] args) throws InterruptedException {
  • TT tt = new TT();
  • Thread t = new Thread(tt);
  • t.start();
  • tt.m2();
  • }
  • }

最后结果是:b = 1000

用一段文字解释一下上面两个问题,首先是第一个程序,m1 方法带锁,m2 不带锁,主线程中创建线程 t,t 调用 m1 方法,将 b 的值改为 1000,然后 sleep,此时主线程也在继续执行,调用了 m2 方法,将 b 的值改为 2000,打印 b。随后 m1 继续执行,打印的 b 当然就是 2000

对于第二个程序,m1 和 m2 两个方法都带锁,那么这两个方法就不能被同时访问,同时只能有一个方法在执行,另一个方法会等待。首先 t 开始,然后主线程继续执行 m2,此时 m1 是不能会被执行的,因为两个方法都带锁,m2 执行完,b 的值被改为 2000,然后执行 m1,b 的值又被改为 1000

Last Modified: October 19, 2018
Archives Tip
QR Code for this page
Tipping QR Code
Leave a Comment