并发编程-同步机制(一)

一、synchronized 内置锁

java 关键字synchronized 用于保证线程对变量访问的可见性与排他性,又可以称之为内置锁机制。

synchronized 可以修饰方法跟同步块上来进行使用,确保多个线程只有一个线程处于方法或者同步块中。

同步方法与同步块

使用 前面学习过的CountDownLatch 做并发测试


import java.util.concurrent.CountDownLatch;

/**
 * 演示synchronized 同步方法,同步块基本使用方法
 * 
 * @author ckj
 *
 */
public class SynchronizedTest {
	
	static CountDownLatch latch = new CountDownLatch(10);
	
	private Object obj = new Object();

	private int num = 0;
	/**
	 * 同步方法加锁
	 */
	public synchronized void synIncNum() {
		num++;
	}

	/**
	 * 同步块加锁
	 */
	public void  synIncNum2() {
		synchronized(this) {
			num++;
		}
		//也可以写成这样
//		synchronized(obj) {
//			num++;
//		}
	}

	public void incNum() {
		num++;
	}
	static class MyThread extends Thread {

		private SynchronizedTest syn;

		public MyThread(SynchronizedTest syn) {
			this.syn = syn;
		}

		public void run() {
			for (int i = 0; i < 10000; i++) {
				//几个测试方法自己手动去切换测试
				syn.incNum();
				//syn.synIncNum();
				//syn.synIncNum2();
			}
			latch.countDown();
		};
	}

	public static void main(String[] args) throws InterruptedException {
		SynchronizedTest syn = new SynchronizedTest();
		//启动10个线程满足latch 计数器
		for(int i =0 ;i<10;i++) {
			new MyThread(syn).start();
		}
		latch.await();
		System.out.println(syn.num); //理想值应该是100000

	}

}

从测试类的测试结果可以看的出来,如果没有加同步机制在多线程的情况下会出现并发问题。

那么synchronized 到底锁的是什么呢。 从上面的例子看来synchronized 锁的是其实就是对象

其实你可以认为在方法上使用synchronized关键字其实他的含义就是synchronized(this) 。从这个方向去理解synchronized锁的话那么synchronized锁不同对象的话那么线程就可以并行了。

对象锁


/**
 * 演示锁的对象不同线程并行
 * 
 * @author ckj
 *
 */
public class InstanceSynTest {

	static class MyThread implements Runnable {

		private InstanceSynTest syn;

		public MyThread(InstanceSynTest syn) {
			this.syn = syn;
		}

		public void run() {
			System.out.println("开始执行MyThread" + Thread.currentThread().getName());
			syn.syncInstance1();
		}

	}

	static class MyThread2 implements Runnable {

		private InstanceSynTest syn;

		public MyThread2(InstanceSynTest syn) {
			this.syn = syn;
		}

		public void run() {
			System.out.println("开始执行MyThread2" + Thread.currentThread().getName());
			syn.syncInstance2();
		}

	}

	public synchronized void syncInstance1() {
		try {
			Thread.sleep(2000);

			System.out.println("syncInstance1 开始执行..." + this.toString());
			Thread.sleep(2000);
			System.out.println("syncInstance1 结束执行 " + this.toString());
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public synchronized void syncInstance2() {
		try {
			Thread.sleep(2000);

			System.out.println("synInstance2 开始执行..." + this.toString());
			Thread.sleep(2000);
			System.out.println("synInstance2 结束执行 " + this.toString());
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public static void main(String[] args) throws InterruptedException {
		
		InstanceSynTest ins1 = new InstanceSynTest();
		InstanceSynTest ins2 = new InstanceSynTest();
		Thread thread1 = new Thread(new MyThread(ins1));
		Thread thread2 = new Thread(new MyThread2(ins2)); // 由于传入的对象是不同对象,线程执行的顺序是并行的
		//Thread thread2 = new Thread(new MyThread2(ins1)); // 修改为同一对象的话那么,线程执行变为串行
		thread1.start();
		thread2.start();
		
		Thread.sleep(6000);
		
	}
}

如果我我们把锁加在static 方法上面呢,那么又会是说明结果。我们知道类的对象可以有许多,但是类只有一个class 对象只有一个,所以不同对象实例的对象锁是是互不干扰的,但是每个类都只有一个类锁。但是有一点类锁只是概念上的东西。

类锁

稍微修改上面测试类


/**
 * 演示实例锁和类锁的不同,可以并行开始 
 * 
 * @author ckj
 *
 */
public class InstanceClassSynTest {

	static class MyThread implements Runnable {

		private InstanceClassSynTest syn;

		public MyThread(InstanceClassSynTest syn) {
			this.syn = syn;
		}

		public void run() {
			System.out.println("开始执行MyThread" + Thread.currentThread().getName());
			syn.syncInstance1();
		}

	}

	static class MyThread2 implements Runnable {

		
		public void run() {
			System.out.println("开始执行MyThread2" + Thread.currentThread().getName());
			syncInstance2();
		}

	}

	public synchronized void syncInstance1() {
		try {
			Thread.sleep(2000);

			System.out.println("syncInstance1 开始执行..." + this.toString());
			Thread.sleep(2000);
			System.out.println("syncInstance1 结束执行 " + this.toString());
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public static synchronized void syncInstance2() {
		try {
			Thread.sleep(2000);

			System.out.println("synInstance2 开始执行...");
			Thread.sleep(2000);
			System.out.println("synInstance2 结束执行 " );
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public static void main(String[] args) throws InterruptedException {
		
		InstanceClassSynTest ins1 = new InstanceClassSynTest();
		Thread thread1 = new Thread(new MyThread(ins1));
		Thread thread2 = new Thread(new MyThread2()); // 由于传入的对象是不同对象,线程执行的顺序是并行的
		
		thread1.start();
		thread2.start();
		
		Thread.sleep(6000);
		
	}
}

下面演示一个比较经典的错误加锁的案例


/**
 * 演示错误加锁
 * 
 * @author ckj
 *
 */
public class IntegerSynTest {

	static class MyThread implements Runnable {
		private Integer i;

		public MyThread(Integer i) {
			this.i = i;
		}

		public void run() {
			synchronized (i) {
				Thread thread = Thread.currentThread();
				i++;
				 System.out.println(i);
				 try {
					thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}

		}

	}
	
	public static void main(String[] args) {
		MyThread myThread = new MyThread(1);
		for(int i = 0;i<5;i++) {
			new Thread(myThread).start();
		}
	
	}

}

按照我们预期结果的话输入应该是

2
3
4
5
6

结果应该如上是2->3->4->5->6

实际结果是:

4
6
5
4
4
那哪里出问题了呢

增加日记打印

		public void run() {
			synchronized (i) {
				Thread thread = Thread.currentThread();
				i++;
				 System.out.println(thread.getName()+"-------"+i+"-@"
	                        +System.identityHashCode(i)+"----------"+"-@"
	                        +System.identityHashCode(this));
				 try {
					thread.sleep(1000);
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
			}

		}

System.identityHashCode 方法打印出原生Object 的hashCode 可以认为他是内存地址

Thread-1-------3-@1073512853-----------@825980954
Thread-2-------4-@923214033-----------@825980954
Thread-3-------5-@1428845299-----------@825980954
Thread-4-------6-@1534375319-----------@825980954
Thread-0-------3-@1073512853-----------@825980954
发现了原来 i 的对象一直在变,每次锁的对象都发生的变化当然得不到预期结果咯,那么为什么会这样呢。通过反编译工具打开class 类

 public void run()
    {
      synchronized (this.i)
      {
        Thread thread = Thread.currentThread();
        this.i = Integer.valueOf(this.i.intValue() + 1);
        System.out.println(thread.getName() + "-------" + this.i + "-@" + 
          System.identityHashCode(this.i) + "----------" + "-@" + 
          System.identityHashCode(this));
        try
        {
          Thread.sleep(1000L);
        }
        catch (Exception e)
        {
          e.printStackTrace();
        }
      }
    }
  }

发现他调用了Integer.valueof 方法,查看一下

 public static Integer valueOf(String s, int radix) throws NumberFormatException {
        return Integer.valueOf(parseInt(s,radix));
    }
 public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

他重新new 了一个出来,所以锁不住了。

那我们怎么处理呢?上面打印结果就可以看出了,我们直接锁this 对象就可以解决问题了。

	public void run() {
			synchronized (this) {
				Thread thread = Thread.currentThread();
				i++;
				 System.out.println(thread.getName()+"-------"+i+"-@"
	                        +System.identityHashCode(i)+"----------"+"-@"
	                        +System.identityHashCode(this));
				 try {
					thread.sleep(1000);
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
			}

		}


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值