1、synchronized
1.1、Synchronized关键字回顾
synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
(1)、修饰一个代码块。
(2)、修饰方法。
备注:虽然可以使用synchronized来定义方法,但synchronized并不属于方法定义的一部分,因此,synchronized关键字不能被继承。如果在父类中的某个方法使用了synchronized关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized关键字才可以。当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步了
(3)、修改一个静态的方法
(4)、修改一个类
列子:
package com.study.demo;
class Data{
private Integer number = 40; //商品数量
public synchronized void rob(){
if( number > 0) {
System.out.println(Thread.currentThread().getName()+"---抢出:"+(number--)+"库存:----"+number);
}
}
}
如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:
1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有;
2)线程执行发生异常,此时JVM会让线程自动释放锁。
那么如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,试想一下,这多么影响程序执行效率。
因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过Lock就可以办到。
2、Lock
2.1、介绍
Lock接口
public interface Lock {
void lock(); //获取锁
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition(); //实现线程等待/通知模型
(在synchrozied 中使用wait()/notify()也可实现等待/通知模型)
}
Lock锁实现提供了比使用同步方法和语句可以获得的更广泛的锁操作。它们允许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对象。Lock提供了比synchronized更多的功能。
2.2、synchronizd与lock的区别
• Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问;
• Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
• Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
• Lock可以提高多个线程进行读操作的效率。
• 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
• 在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。
使用lock实现代码同步的列子:
package com.study.demo;
import java.util.concurrent.locks.ReentrantLock;
class Data2{
private Integer number = 40; //商品数量
/*
* public synchronized void rob(){ if( number > 0) {
*
* System.out.println(Thread.currentThread().getName()+"---抢出:"+(number--)+
* "库存:----"+number); } }
*/
//使用lock实现方法同步
private final ReentrantLock lock = new ReentrantLock();
public void rob() {
lock.lock();
try {
if( number > 0) {
System.out.println(Thread.currentThread().getName()+"---抢出:"+(number--)+"库存:----"+number);
}
}finally {
lock.unlock();//放锁
}
}
}
public class LockDemo2 {
public static void main(String[] args) {
Data2 data = new Data2();
new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0 ; i <=40; i ++) {
//System.out.println("BB线程运行次数:"+i);
data.rob();
}
}
},"BB").start();
new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0 ; i <=40; i ++) {
//System.out.println("AA线程运行次数:"+i);
data.rob();
}
}
},"AA").start();
}
}
2.2、线程间的通信
线程间通信的模型有两种:共享内存和消息传递
2.2.1、使用synchronized实现
package com.study.demo;
/**
* 线程间的通信
* 使用synchornizd实现线程之间的通信
* 实现:
* 使用多线程同时访问一个资源,一个线程判断资源对数据 为0是做+1,
* 一个线程判断资源为1是-1,是资源在0与1之间交替即为:(0,1,0,1 .....)
* @author marvin
*
*/
class Data3{
//资源类
private int number = 0;
public synchronized void incr() throws InterruptedException { //+1
//判断(等待)
while(number != 0) {//在是使用wait时,判断应该使用while,若使用if可能会造成虚假唤醒
this.wait();//如果判断为if时,虚假唤醒,问题出现在此处,如果在不为0时,此处在等待,下次此线程如果抢占到时,会从此处,往下执行,不会参与判断
}
//操作
System.out.println(Thread.currentThread().getName()+"--加数:"+(number++)+"当前数:----"+number);
//通知
this.notifyAll();
}
public synchronized void encr() throws InterruptedException { //+1
//判断(等待)
while(number != 1) {
this.wait();
}
//操作
System.out.println(Thread.currentThread().getName()+"--减数:"+(number--)+"当前数:----"+number);
//通知
this.notifyAll();
}
}
public class LockDemo3 {
public static void main(String[] args) {
Data3 date3 = new Data3();
new Thread(() ->{//执行+1,操作
try {
for(int i = 0; i <= 40; i ++) {
date3.incr();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"AA").start();
new Thread(() ->{//执行-1,操作
try {
for(int i = 0; i <= 40; i ++) {
date3.encr();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"BB").start();
new Thread(() ->{//执行+1,操作
try {
for(int i = 0; i <= 40; i ++) {
date3.encr();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"CC").start();
new Thread(() ->{//执行-1,操作
try {
for(int i = 0; i <= 40; i ++) {
date3.encr();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"DD").start();
}
}
2.2.2、使用Lock实现(ReentrantLock可重入锁)
package com.study.demo;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 线程间的通信
* 使用Lock实现线程之间的通信
* 实现:
* 使用多线程同时访问一个资源,一个线程判断资源对数据 为0是做+1,
* 一个线程判断资源为1是-1,是资源在0与1之间交替即为:(0,1,0,1 .....)
* @author marvin
*
*/
class Data4{
//资源类
private int number = 0;
//是定义锁
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void incr() throws InterruptedException { //+1
lock.lock();//获取锁
try {
//判断(等待)
while(number != 0) {//在是使用wait时,判断应该使用while,若使用if可能会造成虚假唤醒
condition.await();
//this.wait();//如果判断为if时,虚假唤醒,问题出现在此处,如果在不为0时,此处在等待,下次此线程如果抢占到时,会从此处,往下执行,不会参与判断
}
//操作
System.out.println(Thread.currentThread().getName()+"--加数:"+(number++)+"当前数:----"+number);
//通知
condition.signalAll();
//this.notifyAll();
} finally {
lock.unlock();//手动释放锁
}
}
public void encr() throws InterruptedException { //-1
lock.lock();//获取锁
try {
//判断(等待)
while(number != 1) {//在是使用wait时,判断应该使用while,若使用if可能会造成虚假唤醒
condition.await();
//this.wait();//如果判断为if时,虚假唤醒,问题出现在此处,如果在不为0时,此处在等待,下次此线程如果抢占到时,会从此处,往下执行,不会参与判断
}
//操作
System.out.println(Thread.currentThread().getName()+"--减数:"+(number--)+"当前数:----"+number);
//通知
condition.signalAll();
//this.notifyAll();
} finally {
lock.unlock();//手动释放锁
}
}
}
public class LockDemo4 {
public static void main(String[] args) {
Data4 date4 = new Data4();
new Thread(() ->{//执行+1,操作
try {
for(int i = 0; i <= 10; i ++) {
date4.incr();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"AA").start();
new Thread(() ->{//执行-1,操作
try {
for(int i = 0; i <= 10; i ++) {
date4.encr();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"BB").start();
new Thread(() ->{//执行+1,操作
try {
for(int i = 0; i <= 10; i ++) {
date4.incr();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"CC").start();
new Thread(() ->{//执行-1,操作
try {
for(int i = 0; i <= 10; i ++) {
date4.encr();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"DD").start();
}
}
2.2.3、线程的定制化通信
列子: A线程打印5次A,B线程打印10次B,C线程打印15次C,按照此顺序循环10轮
package com.study.demo;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 线程间的通信
* 使用Lock实现线程之间的通信
* 实现:
* 使用多线程同时访问一个资源,一个线程判断资源对数据 为0是做+1,
* 一个线程判断资源为1是-1,是资源在0与1之间交替即为:(0,1,0,1 .....)
* @author marvin
*
*/
class Data5{
//资源类
private int number = 0;
//线程标志位,1:AA线程 2:BB线程 3:CC线程
private int start = 1;
//是定义锁
private Lock lock = new ReentrantLock();
声明各个线程的钥匙
private Condition conditionAA = lock.newCondition();
private Condition conditionBB = lock.newCondition();
private Condition conditionCC = lock.newCondition();
/**
*
* @param i 标识轮数
* @throws InterruptedException
*/
public void DataAA(int i) throws InterruptedException {
//获取锁
lock.lock();
try {
//判断
while(start != 1) {
conditionAA.await();//等待
}
//操作
for(int j = 1;j <= 5; j++ ) {
System.out.println(Thread.currentThread().getName()+"------打印次数:"+j+"-------第"+i+"轮");
}
start = 2;//修改标志位
conditionBB.signal();//通知BB线程
} finally {
lock.unlock();//释放锁
}
}
public void DataBB(int i) throws InterruptedException {
//获取锁
lock.lock();
try {
//判断
while(start != 2) {
conditionBB.await();//等待
}
//操作
for(int j = 1;j <= 10; j++ ) {
System.out.println(Thread.currentThread().getName()+"------打印次数:"+j+"-------第"+i+"轮");
}
start = 3;//修改标志位
conditionCC.signal();//通知CC线程
} finally {
lock.unlock();//释放锁
}
}
public void DataCC(int i) throws InterruptedException {
//获取锁
lock.lock();
try {
//判断
while(start != 3) {
conditionCC.await();//等待
}
//操作
for(int j = 1;j <= 15; j++ ) {
System.out.println(Thread.currentThread().getName()+"------打印次数:"+j+"-------第"+i+"轮");
}
start = 1;//修改标志位
conditionAA.signal();//通知AA线程
} finally {
lock.unlock();//释放锁
}
}
}
public class LockDemo5 {
public static void main(String[] args) {
Data5 data5 = new Data5();
//创建AA、BB、CC线程
new Thread(()-> {
for(int i = 1; i <= 10; i++) {
try {
data5.DataAA(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"AA").start(); ;
new Thread(()-> {
for(int i = 1; i <= 10; i++) {
try {
data5.DataBB(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"BB").start() ;
new Thread(()-> {
for(int i = 1; i <= 10; i++) {
try {
data5.DataCC(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"CC").start() ;
}
}