当使用synchronzied关键字修饰实例变量或者方法的时候,它会为临界资源上锁,只有访问的线程获得锁后才可以使用临界资源,在单核CPU上,并行算法不一定优于串行算法,因为单核CPU需要不停的在线程之间切换和调度,在并行算法中,合理的使用锁可以降低线程之前对锁的竞争导致的时间的浪费。

使用锁的建议:

⑴在同步方法中,减少锁的持有时间,最简单的是对于不需要同步的方法或者变量,我们不需要将其放在同步方法中,使其占用锁的占用时间,只对必要的方法使用synchronized,当然我们也可以在逻辑上进行处理;减少锁中临时变量的创建,使用全局变量等方法来减少线程在临界资源中的消耗;

package com.dong.Lock_Optimization;
/**
 * 锁优化,减少锁的持有时间;
 * @author liuD
 *
 *
 *结果分析:(运行结果根据不同的电脑配置结果不一)
 *当将锁放在addI()方法上时,它会对整个方法加锁,临界资源包括三个语句,完成操作的时间是:
 *i=1000---> 1000 个线程 共花费373 Time ,注意: 每次运行的结果不一样,我的电脑最好是300多
 *
 *当减少不需要的临界资源,即将锁放在i++操作上时;临界资源只有一个语句,完成操作的时间是:
 *i=1000---> 1000 个线程 共花费222 Time , 结果不一样,有300左右,最好222,
 *
 */
public class TestReduceLockTime {
	private static int i = 0;
	
	public static synchronized void addI() {
		//System.out.println(Thread.currentThread().getName()+ "准备添加");
		synchronized (TestReduceLockTime.class) {
			i++;
		}
		//System.out.println(Thread.currentThread().getName()+ "添加完毕");
	}
	public static void main(String[] args) throws InterruptedException {
		ThreadObject to = new ThreadObject();
		//获取自1970年1月1日0时起的毫秒数
		long start =System.currentTimeMillis();
		for(int i = 0 ;i < 1000 ;i++) {
			Thread t = new Thread(to);
			t.start();
			t.join();
		}
		long end = System.currentTimeMillis();
		System.out.println("i="+TestReduceLockTime.i+"---> 1000 个线程 共花费" + (end-start)+" Time");
	}
}
class ThreadObject implements Runnable{
	public void run() {
		TestReduceLockTime.addI();
	}
}

⑵缩小锁定对象的范围,从而减少锁冲突的可能性;减少锁的粒度重点在于使用最小的临界资源,减少线程之间竞争锁资源的冲突,当然使用最小临界资源,也就变相的减少了锁的持有时间;代码和上面同理,减少锁的范围;

⑶锁分离:对于同步资源,针对不同的操作,当各个操作对于同步资源可以不需要同步的时候,我们可以针对这些操作不设置同步限制,而对于需要同步的操作,我们通过加锁来设置其同步的执行,例如读写锁的分离,当没有写操作的时候,多个线程进行读时,是不需要同步的,只有涉及写操作的时候,才会对临界资源访问临界资源的读操作和写操作使用同步机制。

package com.dong.Lock_Optimization;

import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
/**
 * 测试读写锁的效率,即锁分离
    Thread-990在1540881178608时刻点在设置i的新值3
	Thread-992在1540881178608时刻点在设置i的新值9
	Thread-993正在读取i的新值9
	Thread-994在1540881178609时刻点在设置i的新值1
	Thread-995正在读取i的新值1
	Thread-996在1540881178609时刻点在设置i的新值1
	Thread-997正在读取i的新值1
	Thread-998在1540881178610时刻点在设置i的新值0
	Thread-999正在读取i的新值0
	执行所有读写线程共花费:336毫秒
	
	运行5次: 387 499 455 457 507 336
 * @author liuD
 */
public class TestLockSeparation {
	static ReentrantReadWriteLock lock  = new ReentrantReadWriteLock();
	static WriteLock  wlock = lock.writeLock();
	static ReadLock rlock = lock.readLock();
	public static void main(String[] args) throws InterruptedException {
		
		long start = System.currentTimeMillis();
		for (int i = 0; i < 500; i++) {
			Thread write = new Thread ( new writeThread());
			Thread read = new Thread(new readThread());
			write.start();
			read.start();
			read.join();
			write.join();
		}
		long end = System.currentTimeMillis();
		System.out.println("执行所有读写线程共花费:" + (end - start)+ "毫秒");
	}
}
class Data{
	static int i ;
	public static int getI(){
		
		System.out.println(Thread.currentThread().getName()+"正在读取i的新值"+ i);
		return i;
	}
	public static void setI(int j) {
		System.out.println(Thread.currentThread().getName()+"在"+System.currentTimeMillis()+"时刻点在设置i的新值"+ j);
		i = j;
	}
}
class  readThread implements Runnable{
	public void run() {
		TestLockSeparation.rlock.lock();
		Data.getI();
		TestLockSeparation.rlock.unlock();
	}
}
class writeThread implements Runnable{
	public void run() {
		TestLockSeparation.wlock.lock();
		Data.setI((int)(Math.random()*10));
		TestLockSeparation.wlock.unlock();
	}
}
package com.dong.Lock_Optimization;
/**
 * 使用Synchronized关键字,对读写操作都是用锁
 * Thread-993正在读取i的新值0
	Thread-992在1540880987915时刻点在设置i的新值7
	Thread-994在1540880987915时刻点在设置i的新值0
	Thread-995正在读取i的新值0
	Thread-996在1540880987916时刻点在设置i的新值0
	Thread-997正在读取i的新值0
	Thread-999正在读取i的新值0
	Thread-998在1540880987917时刻点在设置i的新值9
	执行所有读写线程共花费:338毫秒
	
	运行了5次,440,313,439,304,338,
 * @author liuD
 *
 */
public class TestLockSeparation2 {
	public static void main(String[] args) throws InterruptedException {
		long start = System.currentTimeMillis();
		for (int i = 0; i < 500; i++) {
			Thread write = new Thread ( new newwriteThread());
			Thread read = new Thread(new newreadThread());
			write.start();
			read.start();
			read.join();
			write.join();
		}
		long end = System.currentTimeMillis();
		System.out.println("执行所有读写线程共花费:" + (end - start)+ "毫秒");
	}
}
class newData{
	static int i ;
	public synchronized static int getI() {
	
		System.out.println(Thread.currentThread().getName()+"正在读取i的新值"+ i);
		return i;
	}
	public synchronized static void setI(int j) {
		System.out.println(Thread.currentThread().getName()+"在"+System.currentTimeMillis()+"时刻点在设置i的新值"+ j);
		i = j;
	}
}
class  newreadThread implements Runnable{
	public void run() {
		Data.getI();
	}
}
class newwriteThread implements Runnable{
	public void run() {
		Data.setI((int)(Math.random()*10));
	}
}

有时候synchronized同步方法的效率高于读写锁,有时候低,一是synchronized在后期被官方优化,效率大大提升,其次,本次有关读的操纵中,没有太多耗时操作,如果读操作复杂,在读写锁的优势就会出来,因为读写锁中,读读操作不需要同步,而synchronized同步方法中,只要使用临界资源,都需要同步;

⑷锁粗化:对于同步资源,如果对同一锁的要求一直在进行,且是同一对象需要访问,如果按照原来的想法,同一个访问对象会一直存在于获取锁,释放锁的过程中,这样会浪费资源,所以对于同一对象的连续访问同一资源,我们可以将这些请求整合成一次请求,从而减少对锁请求同步次数。

//如果读完要立刻进行重新设值,上一个例子使用synchronized同步读,写,现在我们可以将读和写操作放在一起,只使用一个锁就可以,可以减少加锁和解锁的次数;	
    public synchronized static int getI() {
		System.out.println(Thread.currentThread().getName()+"正在读取i的新值"+ i);
		Data.setI((int)(Math.random()*10));
		return i;
	}

JVM对锁做出哪些优化:

⑴偏向锁:当一个线程获得锁,那么锁就进入偏向模式,当这个线程再次请求锁时,无需在做任何同步操作,适用场景是:同一个线程连续的获取锁,访问临界资源的情况下,可以避免不必要的加锁和释放锁,但是,当多个不同的线程竞争锁资源的时候,对于不同的线程,需要分别尽心加锁和释放锁,偏向锁的效果就一般般了。

⑵自旋锁:当线程没有获取锁的时候,他会进入等待队列,即在操作系统层面别挂起,但是挂起一个线程比较浪费时间,所以虚拟机做了一个优化,即假设线程马上就可以获取到锁,所以虚拟机会让当前线程做几个空循环,在几个循环后,如果获得锁,就进入临界资源,如果没有获得锁,则线程将在操作系统层面挂起;

⑶锁消除:JIT编译时,通过对运行上下文的扫描,将剔除不可能存在共享资源竞争的锁,因为在使用某些内置的API的时候,它的内部是使用锁机制的。

⑷轻量级锁:

简单的将对象头部作为指针,指向只有锁的线程堆栈的内部,来判断一个线程是否持有对象锁,如果线程获得轻量级锁成功,则可以顺利进入临界区,如果获取失败,则当前线程就会成为重量级锁;

无锁:

CAS(比较交换):不存在线程之间的调用和不存在锁竞争的系统开销。

cas算法过程:它包含是三个参数CAS(U,E,N)=》Compare And Swap(Update,Expected,New) ,U表示要更新的变量,E表示预期的值,N表示新值,当且仅当U == E时,才会将U的值设置为N,如果U != E ,则说明其他线程已经对该值做了更新,则当前线程什么都不做,最后,CAS返回当前U的真实值。

当多个线程使用CAS访问一个变量的时候,只有一个会成功的改变值,其他的都会失败,失败的线程要么继续使用CAS访问该变量,要么做其他事情,不存在锁和挂起;

最后:内容来自《Java高并发程序设计》   作者葛一鸣 郭超  由衷感谢作者为我们提供书籍内容;