Java多线程之同步控制

时间:2019-03-19
本文章向大家介绍Java多线程之同步控制,主要包括Java多线程之同步控制使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

这里将介绍synchronized关键字和其他丰富多彩的多线程控制方法。

一,synchronized同步锁

synchronized关键字的作用:1. 线程同步。2. 保证线程的可见性和有序性。

synchronized关键字有三种用法分别是:

  1. 修饰非静态方法。获得的是对象锁。
  2. 修饰静态方法。获得类锁。
  3. 静态代码块。两种用法:1. synchronized(对象)对象锁。2. synchronized(Object.class)类锁,与修饰静态方法相同。

 

public class Test1 {
	public synchronized void test() {
		System.out.println(Thread.currentThread().getName());
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) {
		Test1 test1 = new Test1();
		//线程1
		new Thread(new Runnable() {
			@Override
			public void run() {
				test1.test();
				System.out.println(System.currentTimeMillis());
			}
		}, "Thread1").start();
		//线程2
		new Thread(new Runnable() {
			@Override
			public void run() {
				test1.test();
				System.out.println(System.currentTimeMillis());
			}
		}, "Thread2").start();
	}
}

结果:时间相差3000ms。 

Thread1
1552904209801
Thread2
1552904212804

这里是使用的test1的锁,获得到锁的线程进入执行状态,未获得锁的线程等待。类锁用法相似只是将synchronized修饰的方法变为static方法。

同步代码块 :使用对象锁

public class Test1 {
	public void test() {
		synchronized (this) {
			System.out.println(Thread.currentThread().getName());
			try {
				Thread.sleep(3000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

 同步代码块 :使用类锁

public class Test1 {
	public void test() {
		synchronized (Test1.class) {
			System.out.println(Thread.currentThread().getName());
			try {
				Thread.sleep(3000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

二,synchronized同步锁的扩展:重入锁

重入锁是synchronized功能的扩展,性能好于synchronized。

 重入锁由java.util.concurrent.locks.ReentrentLock类来实现的。

public class Test1 implements Runnable {
	private static ReentrantLock lock = new ReentrantLock();
	static int i = 0;
	@Override
	public void run() {
		lock.lock();
		for(int j = 0; j<10000; j++) {
			i++;
		}
		lock.unlock();
	}
	
	public static void main(String[] args) {
		Test1 test1 = new Test1();
		Thread thread1 = new Thread(test1);
		Thread thread2 = new Thread(test1);
		thread1.start();
		thread2.start();
		try {
			thread1.join();
			thread2.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("i="+i);
	}
}
i=20000

这样每个线程都会+10000次,结果什么时候都是20000。 

 可以看出重入锁在操作的过程中更加的灵活,在何时加锁都能灵活的控制,注意在退出临界区时一定要手动释放锁,否则其他线程将无法执行。

 重入锁之中断响应 

方法 Lock.lockInterruptibly()

我们知道对于synchronized,如果一个线程等待锁,那么结果只有两种1. 继续等待。2. 拿到锁,继续执行。而重入锁却提供了一种不同的机制,在线程的等待过程中可以响应中断,来结束等待。

import java.util.concurrent.locks.ReentrantLock;

public class Test1 implements Runnable {
	private static ReentrantLock lock1 = new ReentrantLock();
	private static ReentrantLock lock2 = new ReentrantLock();
	static int i = 0;
	@Override
	public void run() {
		if("thread1"==Thread.currentThread().getName()||"thread1".equals(Thread.currentThread().getName())) {
			try {
				//获得锁但优先响应中断
				lock1.lockInterruptibly();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"拿到了lock1");
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e1) {
				e1.printStackTrace();
			}
			try {
				System.out.println(Thread.currentThread().getName()+"尝试拿到lock2");
				//获得锁但优先响应中断
				lock2.lockInterruptibly();
				System.out.println(Thread.currentThread().getName()+"拿到了lock2");
			} catch (InterruptedException e) {
				if(lock1.isHeldByCurrentThread()) {
					//释放锁
					lock1.unlock();
					System.out.println(Thread.currentThread().getName()+"释放了lock1");
				}
			}
		}else {
			try {
				//获得锁但优先响应中断
				lock2.lockInterruptibly();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"拿到了lock2");
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e1) {
				e1.printStackTrace();
			}
			try {
				System.out.println(Thread.currentThread().getName()+"尝试拿到lock1");
				//获得锁但优先响应中断
				lock1.lockInterruptibly();
				System.out.println(Thread.currentThread().getName()+"拿到了lock1");
			} catch (InterruptedException e) {
				if(lock2.isHeldByCurrentThread()) {
					//释放锁
					lock2.unlock();
					System.out.println(Thread.currentThread().getName()+"释放了lock2");
				}
			}
		}
	}
	
	public static void main(String[] args) throws InterruptedException {
		Test1 test1 = new Test1();
		Thread thread1 = new Thread(test1,"thread1");
		Thread thread2 = new Thread(test1,"thread2");
		thread1.start();
		thread2.start();
		//运行到这里会产生死锁
		
		Thread.sleep(2000);
		//让线程1中断
		thread1.interrupt();
		try {
			thread1.join();
			thread2.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("全部结束");
	}
}

线程1和线程2都拿着自己的锁尝试去别人的锁就会产生死锁,给线程1一个中断请求,线程1在等待lock2的时候会去响应中断释放自己的锁并结束线程。 

重入锁之等待限时 

方法 Lock.tryLock(long time, TimeUnit unit) 等待一段时间如果拿不到锁就结束,参数unit时间单位,时间单位time个数。

方法 Lock.tryLock() 拿不到锁直接结束,不等待。

Lock.tryLock()的情况

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Test implements Runnable {
	private static Lock lock = new ReentrantLock();
	@Override
	public void run() {
		//线程拿不到锁会立即结束
		if(lock.tryLock()) {
			System.out.println(Thread.currentThread().getName()+"正在运行");
			//睡眠3000ms 模拟第一个进入的线程长时间占有锁资源
			try {
				Thread.sleep(3000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			lock.unlock();
		}else {
			System.out.println(Thread.currentThread().getName()+"拿不到锁结束");
		}
	}
	
	public static void main(String[] args) {
		Test test = new Test();
		Thread thread1 = new Thread(test,"t1");
		Thread thread2 = new Thread(test,"t2");
		thread1.start();
		thread2.start();
		try {
			thread1.join();
			thread2.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("全部运行结束");
	}
}
t2拿不到锁结束
t1正在运行
全部运行结束

t2拿不到锁结束
t1正在运行
全部运行结束 

 Lock.tryLock(long time, TimeUnit unit)的情况,这里让线程等待50000ms。

 

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Test implements Runnable {
	private static Lock lock = new ReentrantLock();
	@Override
	public void run() {
		
		try {
			//线程等待5000ms后还拿不到锁资源就结束
			//拿到锁资源就在继续执行
			if(lock.tryLock(5000, TimeUnit.SECONDS)) {
				System.out.println(Thread.currentThread().getName()+"正在运行");
				//睡眠3000ms 模拟第一个进入的线程长时间占有锁资源
				try {
					Thread.sleep(3000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				lock.unlock();
			}else {
				System.out.println(Thread.currentThread().getName()+"拿不到锁结束");
			}
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) {
		Test test = new Test();
		Thread thread1 = new Thread(test,"t1");
		Thread thread2 = new Thread(test,"t2");
		thread1.start();
		thread2.start();
		try {
			thread1.join();
			thread2.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("全部运行结束");
	}
}
t1正在运行
t2正在运行
全部运行结束

重入锁之公平锁 

 我们知道线程对资源的是抢占式的,不遵循先来后到的原则,而是随机抢占谁抢到谁用,这样会产生饥饿现象,重入锁就提供了一种公平的方案,排队,谁在前面谁先占有锁。

new ReentrantLock(boolean fair){}
Parameters:fair true if this lock should use a fair ordering policy
参数:如果该锁应使用公平订购策略,则为公平真

如果参数为ture将使用公平锁

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TestFair implements Runnable {
	//参数为ture 将重入锁设置成公平锁
	private static Lock lock = new ReentrantLock(true);
	public static TestFair testFait = new TestFair();
	@Override
	public void run() {
			while(true) {
				lock.lock();
				System.out.println(Thread.currentThread().getName()+"正在执行");
				lock.unlock();
			}
	}
	
	public static void main(String[] args) {
		Thread thread1 = new Thread(testFait, "t1");
		Thread thread2 = new Thread(testFait, "t2");
		//将thread1的优先级设置为1 将thread2优先级设置为5
		thread1.setPriority(1);
		thread2.setPriority(5);
		thread1.start();
		thread2.start();
	}
}

结果我截取了一部分

t1正在执行
t2正在执行
t1正在执行
t2正在执行
t1正在执行
t2正在执行
t1正在执行
t2正在执行
t1正在执行
t2正在执行

即使我设置了线程的优先级,线程依然是交替运行。 

 

重入锁的好搭档 Condition

Condition的作用与Object.wait() 和 Object.notify()/Object.notifyAll()的作用相似,前者是配合重入锁使用的,Condition的方法有

Condition.await() 使线程等待,与Object.wait()有一个不同之处是可以响应中断。

Condition.awaitUninterruptibly(),也是使线程等待但是并不会去响应中断。

Condition.singal()随机唤醒等待池的一个线程。

Condition.singalAll() 唤醒等待池的所有的线程,。

 Condition.await()和Condition.singal()

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TestCondition implements Runnable {
	private static Lock lock = new ReentrantLock();
	private static Condition condition = lock.newCondition();
	private static TestCondition Testcondition = new TestCondition();
	@Override
	public void run() {
		//condition.await()方法必须拿到重入锁
		lock.lock();
		System.out.println("线程正在运行状态");
		try {
			condition.await();
			System.out.println("线程继续执行");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}finally {
			//释放掉锁给唤醒线程
			lock.unlock();
		}
	}
	
	public static void main(String[] args) throws InterruptedException {
		Thread thread = new Thread(Testcondition);
		thread.start();
		Thread.sleep(1000);
		//condition.signal()方法必须拿到重入锁
		lock.lock();
		condition.signal();
		//释放掉锁资源给唤醒的线程  
		lock.unlock();
		Thread.sleep(1000);
		System.out.println("程序运行结束");
	}
}

切记在使用condition使线程等待或者唤醒后,一定要及时释放掉锁,否则即使线程被唤醒,由于拿不到锁也会处于阻塞状态。

 信号量

信号量是对同步锁和重入锁的扩展,与同步锁和重入锁不同的是每次可以有多个线程进入临界区。

信号量的构造方法

public Semaphore(int permits) //参数:每次有多少个线程进入代码区
public Semaphore(int permits, boolean fair) //第二个参数指定是否公正

提供的方法

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

public class TestSemaphore implements Runnable {
	private Semaphore semaphore = new Semaphore(3);
	
	@Override
	public void run() {
		try {
			//尝试获得许可
			semaphore.acquire();
			System.out.println(Thread.currentThread().getId()+"正在执行");
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}finally {
			//释放许可
			semaphore.release();
			System.out.println("有线程退出");
		}
	}
	
	public static void main(String[] args) {
		 //新建一个有固定线程的线程池
		 ExecutorService executorService = Executors.newFixedThreadPool(10);
		 TestSemaphore testSemaphore = new TestSemaphore();
		 for(int i=1;i<=10;i++) {
			 //传进去一个runnable ThreadFactory会自动创建线程
			 executorService.submit(testSemaphore); 
		 }
		
		 
	}
	
}
10正在执行
11正在执行
12正在执行
有线程退出
13正在执行
有线程退出
14正在执行
有线程退出
15正在执行
有线程退出
有线程退出
有线程退出
18正在执行
17正在执行
16正在执行
有线程退出
有线程退出
有线程退出
19正在执行
有线程退出

可见每次有五个线程在工作,没退出一个就有一个进来。 

 读写锁 ReadWriteLock

 读写锁是对锁的一种优化,实现了锁的分离。

当程序中的读操作的次数大大超过了写的次数,这种读写锁的效率非常的高。 

  • 读-读不阻塞
  • 读-写阻塞
  • 写-写阻塞

 同步锁和重入锁的不同

  1.  synchronized是java的关键字,而ReentrantLock是java中的类。
  2. ReentrantLock使用比较灵活,可以在任何地方加锁。synchronized不够灵活。
  3. 同步锁不需要手动释放掉锁资源,重入锁必须手动释放锁资源。
  4. 同步锁不能在线程等待时响应中断请求和等待限时,重入锁可以。
  5. 同步锁实现不了公平锁,重入锁可以。