Java多线程编程-Thread synchronized使用
前面我们是在方法上面进行同步的,这样有一些问题:
如果一个线程调用一个同步方法时间比较长,那么另外一个现场就需要等待比较长的时间
我们可以进行验证:
线程引用的对象:
public class ThreadObject {
public synchronized void longTimeMethod() throws InterruptedException {
System.out.println("start do longtime method");
Thread.sleep(5000);
System.out.println("current do it:" + Thread.currentThread().getName());
System.out.println("do end");
}
}
线程1:
public class ThreadLongTimeT1 extends Thread {
private ThreadObject threadObject;
public ThreadLongTimeT1(ThreadObject threadObject, String threadName) {
this.setName(threadName);
this.threadObject = threadObject;
}
@Override
public void run() {
try {
threadObject.longTimeMethod();
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
}
}
线程2:
public class ThreadLongTimeT2 extends Thread {
private ThreadObject threadObject;
public ThreadLongTimeT2(ThreadObject threadObject, String threadName) {
this.setName(threadName);
this.threadObject = threadObject;
}
@Override
public void run() {
try {
threadObject.longTimeMethod();
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
}
}
运行类:
public class ThreadLongTimeMain {
public static void main(String[] args) {
ThreadObject threadObject = new ThreadObject();
ThreadLongTimeT1 threadLongTimeT1 = new ThreadLongTimeT1(threadObject, "thread01");
ThreadLongTimeT2 threadLongTimeT2 = new ThreadLongTimeT2(threadObject, "thread02");
threadLongTimeT1.start();
threadLongTimeT2.start();
}
}
运行结果:
这里我故意将线程sleep调大一点,那个线程先获得资源运行方法之后,另一个线程需要等很长的时间,这样不太友好。我们可以使用同步代码块解决。
synchronized同步代码块
当多个线程并发访问同一个对象中的synchronized(this)的代码块时,同一时间内只能有一个线程被执行,另一个线程必须等待当前线程执行完这个代码块以后才能执行同步的代码块。
我们可以将代码改为如下试试:
public void longTimeMethod() throws InterruptedException {
synchronized (this){
System.out.println("start do longtime method");
Thread.sleep(5000);
System.out.println("current do it:" + Thread.currentThread().getName());
System.out.println("do end");
}
}
运行结果其实优化不大,这个很好理解。直接将方法中的所有代码进行同步的话,和在方法中添加基本没有什么区别。因为代码运行到这个方法之后就会获取锁了。
我们测试一样,将同步代码块放在中间的位置
操作对象:
public class ThreadSynchronizedCodeBlockObj {
public void testFun() throws InterruptedException {
System.out.println("start do it current thread name:" + Thread.currentThread().getName());
synchronized (this) {
Thread.sleep(2000);
System.out.println("this is a SynchronizedCodeBlock");
}
System.out.println("end do it current thread name:" + Thread.currentThread().getName());
}
}
线程1:
public class ThreadSynchronizedCodeBlockT1 extends Thread {
private ThreadSynchronizedCodeBlockObj threadSynchronizedCodeBlockObj;
public ThreadSynchronizedCodeBlockT1(ThreadSynchronizedCodeBlockObj threadSynchronizedCodeBlockObj, String threadName) {
this.threadSynchronizedCodeBlockObj = threadSynchronizedCodeBlockObj;
this.setName(threadName);
}
@Override
public void run() {
try {
threadSynchronizedCodeBlockObj.testFun();
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
}
}
线程2:
public class ThreadSynchronizedCodeBlockT2 extends Thread {
private ThreadSynchronizedCodeBlockObj threadSynchronizedCodeBlockObj;
public ThreadSynchronizedCodeBlockT2(ThreadSynchronizedCodeBlockObj threadSynchronizedCodeBlockObj, String threadName) {
this.threadSynchronizedCodeBlockObj = threadSynchronizedCodeBlockObj;
this.setName(threadName);
}
@Override
public void run() {
try {
threadSynchronizedCodeBlockObj.testFun();
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
}
}
运行结果如下:
有运行可以看出,在没有使用同步操作的代码块会异步执行,而在同步代码块中的代码是同步执行的.
synchronized(this)同步的对象
当一个线程访问对象的一个synchronized(this)同步代码块时,其他线程对同一个对象中所有其他synchronized(this)同步代码块的访问将被阻塞,说明synchronized(this)同步的对象是同一个。
public class ThreadSynchronizedObjV1 {
public void testFun() {
try {
synchronized (this) {
System.out.println("start do testFun,current thread name:" + Thread.currentThread().getName());
Thread.sleep(2000);
System.out.println("end do testFun,current thread name:" + Thread.currentThread().getName());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void testFun2() {
System.out.println("start do testFun2,current thread name:" + Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
System.out.println("end do testFun2,current thread name:" + Thread.currentThread().getName());
}
}
线程1:
public class ThreadSynchronizedObjT1 extends Thread {
private ThreadSynchronizedObjV1 threadSynchronizedObjV1;
public ThreadSynchronizedObjT1(ThreadSynchronizedObjV1 threadSynchronizedObjV1, String threadName) {
this.setName(threadName);
this.threadSynchronizedObjV1 = threadSynchronizedObjV1;
}
@Override
public void run() {
threadSynchronizedObjV1.testFun();
}
}
线程2:
public class ThreadSynchronizedObjT2 extends Thread {
private ThreadSynchronizedObjV1 threadSynchronizedObjV1;
public ThreadSynchronizedObjT2(ThreadSynchronizedObjV1 threadSynchronizedObjV1, String threadName) {
this.setName(threadName);
this.threadSynchronizedObjV1 = threadSynchronizedObjV1;
}
@Override
public void run() {
threadSynchronizedObjV1.testFun2();
}
}
运行结果:
有运行结果可以看出,testFun和testFun2的方法进行同步运行了,因此说明testFun和testFun2两个方法锁定的是同一个对象,都为ThreadSynchronizedObjV1 对象。
synchronized 作用不在对象上
之前我们是作用在方法上或者对象(this)上,我们现在不作用在此,我们也可以作用在实例对象和方法参数上。
synchronized 实例对象
线程对象:
public class ThreadSynInstanceObj {
private int count;
private int totalCount;
private String strObj = new String(); //锁住实例对象
public void setData(int count) {
try {
synchronized (strObj) {
System.out.println("start do setData method,current thread name:" + Thread.currentThread().getName());
this.count = count;
Thread.sleep(2000);
this.totalCount = count++;
System.out.println(count + " and " + totalCount);
System.out.println("end do setData method,current thread name:" + Thread.currentThread().getName());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
线程1:
public class ThreadSynInstanceObjT1 extends Thread {
private ThreadSynInstanceObj threadSynInstanceObj;
public ThreadSynInstanceObjT1(ThreadSynInstanceObj threadSynInstanceObj, String threadName) {
this.threadSynInstanceObj = threadSynInstanceObj;
this.setName(threadName);
}
@Override
public void run() {
threadSynInstanceObj.setData(100);
}
}
线程2:
public class ThreadSynInstanceObjT2 extends Thread {
private ThreadSynInstanceObj threadSynInstanceObj;
public ThreadSynInstanceObjT2(ThreadSynInstanceObj threadSynInstanceObj, String threadName) {
this.threadSynInstanceObj = threadSynInstanceObj;
this.setName(threadName);
}
@Override
public void run() {
threadSynInstanceObj.setData(100);
}
}
运行结果:
上面锁住同一个实例对象的时候,另一个调用这个方法也许排队处理。
得到如下结论:
1、在多个线程持有为同一个对象的前提下,同一时间只有一个线程可以执行同步代码块
2、当持有为同一个对象的前提下,同一时间只有一个线程可以执行同步代码块
同步实例变量或者方法中的局部变量有如下优点:
如果在一个类中有多个同步方法,虽然能实现同步但是会阻塞,会影响运行效率,不同步this的话,则在同步代码块中的程序与同步方法是异步的。不会和其他同步this增抢this锁。这样可以大大代码运行效率。
synchronized 作用在静态方法上
上面我们有将同步synchronized作用的非静态方法上,我们现在将synchronized作用在静态方法上。
我们进行验证一下:
对象类:
public class ThreadSynStaticMethodObj {
public synchronized static void testFun() throws InterruptedException {
System.out.println("start do testFun method,current thread name:" + Thread.currentThread().getName());
Thread.sleep(3000);
System.out.println("end do testFun method,current thread name:" + Thread.currentThread().getName());
}
public synchronized static void testFun1() throws InterruptedException {
System.out.println("start do testFun1 method,current thread name:" + Thread.currentThread().getName());
Thread.sleep(3000);
System.out.println("end do testFun1 method,current thread name:" + Thread.currentThread().getName());
}
}
线程1:
public class ThreadSynStaticMethodObjT1 extends Thread {
public ThreadSynStaticMethodObjT1(String threadName) {
this.setName(threadName);
}
@Override
public void run() {
try {
ThreadSynStaticMethodObj.testFun();
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
}
}
线程2:
public class ThreadSynStaticMethodObjT2 extends Thread {
public ThreadSynStaticMethodObjT2(String threadName) {
this.setName(threadName);
}
@Override
public void run() {
try {
ThreadSynStaticMethodObj.testFun1();
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
}
}
运行结果:
通过上面的运行可以看出,还是同步执行的,线程2需要等待。但是和在非静态方法同步是有不一样的。非静态方法同步锁定的是对象,而静态方法同步是锁定的Class类上锁。
我们修改以上代码如下,将testFun1改为非静态的试试。
public synchronized void testFun1() throws InterruptedException {
System.out.println("start do testFun1 method,current thread name:" + Thread.currentThread().getName());
Thread.sleep(3000);
System.out.println("end do testFun1 method,current thread name:" + Thread.currentThread().getName());
}
线程类中有相应的调整,需要将ThreadSynStaticMethodObj 引入,代码如下:
private ThreadSynStaticMethodObj threadSynStaticMethodObj;
public ThreadSynStaticMethodObjT1(ThreadSynStaticMethodObj threadSynStaticMethodObj, String threadName) {
this.setName(threadName);
this.threadSynStaticMethodObj = threadSynStaticMethodObj;
}
private ThreadSynStaticMethodObj threadSynStaticMethodObj;
public ThreadSynStaticMethodObjT2(ThreadSynStaticMethodObj threadSynStaticMethodObj, String threadName) {
this.setName(threadName);
this.threadSynStaticMethodObj = threadSynStaticMethodObj;
}
@Override
public void run() {
try {
threadSynStaticMethodObj.testFun1();
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
}
运行结果:
可以看出线程1和线程2异步执行testFun和testFun1的,说明这两个同步锁定的对象是不一样的,如果一样的话,就是同步操作了哦。我们在上面所过,我们在非静态方法同步的时候,创建多个对象的话,也会进行异步操作,但是同步静态方法的时候,是对Class上锁,在进行多个对象创建的时候,也是同步的,不会应为多个对象而变为异步的。这也是和同步非静态方法的区别。
在同步的代码块的时候,有一个需要特别注意的,就是不要作用在字符串常量上。
当锁定的是字符串常量,并且字符串都一样,就会出现问题。
public class ThreadStringContantObj {
public void testFun(String str) throws InterruptedException {
synchronized (str) {
while (true) {
System.out.println("current thread name:" + Thread.currentThread().getName());
Thread.sleep(1000);
}
}
}
}
线程1:
public class ThreadStringContantT1 extends Thread {
private ThreadStringContantObj threadStringContantObj;
public ThreadStringContantT1(ThreadStringContantObj threadStringContantObj, String threadName) {
this.setName(threadName);
this.threadStringContantObj = threadStringContantObj;
}
@Override
public void run() {
try {
threadStringContantObj.testFun("tony");
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
}
}
线程2:
public class ThreadStringContantT2 extends Thread {
private ThreadStringContantObj threadStringContantObj;
public ThreadStringContantT2(ThreadStringContantObj threadStringContantObj, String threadName) {
this.setName(threadName);
this.threadStringContantObj = threadStringContantObj;
}
@Override
public void run() {
try {
threadStringContantObj.testFun("tony");
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
}
}
运行结果:
从运行上面来看的话,哪个线程获得CPU执行权就一直会占用这个锁。我们将两个值改为不一样试试:
threadStringContantObj.testFun("tony1");
运行结果:
运行结果可以看出两个是不一样的,是交替运行的。由此得出结论:
同步的时候尽量不要使用string对象进行同步限定,这样会有上面的问题,一般使用会通过一个Object类进行限定,这样就不会String相关问题了。
线程死锁问题
在多线程的情况下,会出现线程死锁问题,出现该问题的原因是线程之前相互等待对方释放锁,但是双方都不释放,就只有相互等待了。
我们实现上图中的情况并用jconsole分析一下
对象类:
public class ThreadDeadLockObj {
private Object lock1 = new Object();
private Object lock2 = new Object();
public void testFun() {
System.out.println("current thread name:" + Thread.currentThread().getName());
synchronized (lock1) {
System.out.println("get lock1");
synchronized (lock2) {
System.out.println("get lock2");
}
}
}
public void testFun1() {
System.out.println("current thread name:" + Thread.currentThread().getName());
synchronized (lock2) {
System.out.println("get lock2");
synchronized (lock1) {
System.out.println("get lock1");
}
}
}
}
线程1:
public class ThreadDeadLockT1 extends Thread {
public ThreadDeadLockObj threadDeadLockObj;
public ThreadDeadLockT1(ThreadDeadLockObj threadDeadLockObj, String threadName) {
this.setName(threadName);
this.threadDeadLockObj = threadDeadLockObj;
}
@Override
public void run() {
threadDeadLockObj.testFun();
}
}
线程2:
public class ThreadDeadLockT2 extends Thread {
public ThreadDeadLockObj threadDeadLockObj;
public ThreadDeadLockT2(ThreadDeadLockObj threadDeadLockObj, String threadName) {
this.setName(threadName);
this.threadDeadLockObj = threadDeadLockObj;
}
@Override
public void run() {
threadDeadLockObj.testFun1();
}
}
运行结果:
程序还没有运行结束,已经发生死锁了。
我使用jconsole看看:
我们看到线程1和线程2之间产生了死锁。