一、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();
}
}
}