一、线程锁:Lock 代替synchronized
Lock的作用类似于传统线程模型中的synchronized,更体现面向对象的思想,两个线程执行的代码片段要实现同步互斥的效果,必须要用同一个Lock对象。
1)ReentrantLock
//用Lock替换synchronized互斥
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadTest1 {
public static void main(String[] args) {
new ThreadTest1().init();
}
//起了两个线程,一个一直输出"AAAAAA",一个一直输出"BBBBBB",但调用方法的对象是同一个
public void init(){
final Outputer outputer = new Outputer();
new Thread(new Runnable() {
@Override
public void run() {
while(true){
outputer.output("AAAAAA");
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while(true){
outputer.output("BBBBBB");
}
}
}).start();
}
}
class Outputer {
Lock lock = new ReentrantLock(); //这里两个线程调用方法的是同一个对象,所以是用的同一把锁
public void output(String name){
//synchronized (this) {
lock.lock();
try{
int len = name.length();
for(int i=0;i<len;i++){
System.out.print(name.charAt(i));
}
System.out.println("");
}finally{
lock.unlock(); //无论如何都要释放锁
}
//}
}
}
锁中的代码出现异常可能会出现死锁,最好finally释放锁。
2)读写锁 ReentrantReadWriteLock
分为读锁和写锁,使用读写锁可以提高效率,还能实现互斥。ReentrantLock是互斥锁,一个线程获得后,别的线程无论读写都不允许。
多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥。
规则:
读中可以有读,
读中不能有写,
写中不能有读,写中不能有写。
只是读的时候不用互斥,用读锁即可,不用synchronized。
/**
* 起3个线程不停写,3个线程不停读
*/
public class ReadWriteLockTest {
public static void main(String[] args) {
final Queue q = new Queue();
for(int i=0;i<3;i++){
new Thread(){
@Override
public void run() {
while(true){
q.get();
}
}
}.start();
new Thread(){
@Override
public void run() {
while(true){
q.put(new Random().nextInt(10000));;
}
}
}.start();
}
}
}
/**
*读时可以有读,读时不能有写
*写时不能有读,也不能有写。
*/
class Queue {
private Object data = null; //共享数据,只能有一个线程能写该数据,但可以有多个线程同时读该数据
private ReadWriteLock rw1 = new ReentrantReadWriteLock(); //创建一个读写锁
public void get() {
rw1.readLock().lock(); //从读写锁中获得读锁
try {
System.out.println(Thread.currentThread().getName() + "开始读数据");
Thread.sleep((long)(Math.random()*1000));
System.out.println(Thread.currentThread().getName() + "已读完数据" + data);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rw1.readLock().unlock(); //释放读锁
}
}
public void put(Object data) {
rw1.writeLock().lock(); //获得写锁
try {
System.out.println(Thread.currentThread().getName() + "开始写数据");
Thread.sleep((long)(Math.random()*1000));
this.data = data;
System.out.println(Thread.currentThread().getName() + "已写数据" + data);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rw1.writeLock().unlock(); //释放写锁
}
}
}
利用读写锁来模拟缓存案例。(结合到单例模式下)
/**
* 模拟缓存
* 读和写间、写和写间互斥,又可以并发的读,性能高
*/
public class CacheDemo {
private Map<String, Object> cache = new HashMap<String, Object>();
private ReadWriteLock rw1 = new ReentrantReadWriteLock(); //读写锁
public Object getData(String key){
rw1.readLock().lock(); //读1,多个读时应该要可以并发,不会造成数据的破坏
Object value = null;
try {
value = cache.get(key);
if(value == null){ //如果缓存中没数据,要重新赋值,要先写锁
rw1.readLock().unlock(); //先释放读1
rw1.writeLock().lock(); //写2,如果有多个线程到这一步,也只有一个能获得写锁
try{
if(value==null){ //赋值前再判断一次
value = "aaa"; //模拟取数据库取数据
}
} finally {
rw1.writeLock().unlock(); //写2
}
rw1.readLock().lock(); //读3
}
} finally {
rw1.readLock().unlock(); //读3
}
return value;
}
}
二、Condition代替wait/notify实现同步通信
wait、notify必须和synchronized一起用,并且synchronized和wait、notify要用同一个锁对象。Condition可以用来代替wait、notify实现同步通信,Condition是在Lock对象的基础上使用的。
使用Lock、Condition代替wait、notify、synchronized,案例代码如下。
public class ConditionTest {
public static void main(String[] args) throws InterruptedException {
final Business business = new Business();
new Thread(new Runnable() {
@Override
public void run() {
for(int i=1;i<=50;i++){ //循环50轮
try {
business.forSub(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
for(int i=1;i<=50;i++){ //循环50轮
business.forMain(i);
}
}
}
class Business {
Lock lock = new ReentrantLock(); //用Lock代替synchronized
private boolean subGo = true;
Condition condition = lock.newCondition(); //Condition基于锁之上
public void forSub(int i) throws InterruptedException{
lock.lock();
try{
while(!subGo){ //防止虚假唤醒
//this.wait();
condition.await(); //别写成condition.wait();,是有区别的
}
for(int j=1;j<=10;j++){
System.out.println("subThread of, 第"+i+"轮, 序列为:"+j);
}
subGo = false;
condition.signal(); //this.notify();
}finally{
lock.unlock();
}
}
public void forMain(int i) throws InterruptedException{
lock.lock();
try{
while(subGo){
condition.await(); //this.wait();
}
for(int j=1;j<=100;j++){
System.out.println("mainThread of, 第"+i+"轮, 序列为:"+j);
}
subGo = true;
condition.signal(); //this.notify();
}finally{
lock.unlock();
}
}
}
一个锁内部可以有多个Condition,可实现多路等待和唤醒。
只用一个Condition的不足:假如有5个线程用来存馒头,1个线程用来取馒头,当笼屉馒头满的时候,5个存的线程等待,当1个取的线程取了一个馒头后,唤醒了5个存线程中的一个继续存,存完后,本来应该要唤醒取的线程,但如果这里只有1个Condition,就不能进行区分,可能会唤醒另外4个存线程中的一个。
当有三个线程要按顺序交替进行,代码如下。
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreeConditionTest {
public static void main(String[] args) throws InterruptedException {
final ConditionBusiness business = new ConditionBusiness();
new Thread(new Runnable() {
@Override
public void run() {
for(int i=1;i<=50;i++){ //循环50轮
try {
business.sub2(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for(int i=1;i<=50;i++){ //循环50轮
try {
business.sub3(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
for(int i=1;i<=50;i++){ //循环50轮
business.forMain(i);
}
}
}
//用于三个线程的同步通信,主线程-sub2-sub3的顺序交替进行
class ConditionBusiness {
Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
private int flag = 1; //设定1先走
public void forMain(int i) throws InterruptedException{
lock.lock();
try{
while(flag != 1){
condition1.await();
}
for(int j=1;j<=10;j++){
System.out.println("main, 第"+i+"轮, 序列为:"+j);
}
flag = 2;
condition2.signal(); //线程1走完唤醒线程2
}finally{
lock.unlock();
}
}
public void sub2(int i) throws InterruptedException{
lock.lock();
try{
while(flag != 2){
condition2.await();
}
for(int j=1;j<=10;j++){
System.out.println("sub2, 第"+i+"轮, 序列为:"+j);
}
flag = 3;
condition3.signal(); //线程2走完唤醒线程3
}finally{
lock.unlock();
}
}
public void sub3(int i) throws InterruptedException{
lock.lock();
try{
while(flag != 3){
condition3.await();
}
for(int j=1;j<=10;j++){
System.out.println("sub3, 第"+i+"轮, 序列为:"+j);
}
flag = 1;
condition1.signal(); //线程3走完唤醒线程1
}finally{
lock.unlock();
}
}
}
来源:https://www.cnblogs.com/zjxiang/p/9432809.html