LOCK接口
synchronized关键字回顾
- 修饰一个同步语句代码块,作用范围是大括号{}括起来的代码,作用对象是调用这个代码块的对象。
- 修饰一个方法,被修饰的方法称为同步方法,其作用范围是整个方法,作用的对象是调用这个方法的对象。
- 修饰一个静态方法。
- 修饰一个类。
多线程编程步骤(上)
-
创建一个资源类,定义属性和操作方法 (高内聚低耦合思想)
-
在资源类操作方法
- 判断
- 干活
- 通知
-
创建多个线程,调用资源类的操作方法
操作实例:
package com.amyth.JavaEE;
//创建资源类
class Ticket{
private int number = 30;
//加上synchronized修饰方法
public synchronized void sale(){
if(number > 0 ) {
System.out.println(Thread.currentThread().getName() + "卖出1张票,剩余:" + (number--) + "张");
}else{
System.out.println(Thread.currentThread().getName() + "卖出0张票,剩余:0没票啦!!!");
}
}
}
public class SyncTest {
public static void main(String[] args) {
//将资源创建出来
Ticket ticket = new Ticket();
new Thread(new Runnable() {
@Override
public void run() {
for (int i=0; i<30;i++)
{
ticket.sale();
}
}
},"AA").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i=0; i<40;i++)
{
ticket.sale();
}
}
},"BB").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i=0; i<40;i++)
{
ticket.sale();
}
}
},"CC").start();
}
}
Lock和Synchronized的区别
Lock锁实现了比使用同步方法和语句可以获得更广泛的锁操作,提供了更多功能。
- Lock不是JAVA内置的,Synchronized是关键字,有内置特性,Lock是一个类,通过这个类可以实现同步访问。
- 最大的不同在于,Lock需要手动释放锁,否则可能会出现“死锁”现象,而Synchronized不需要,是自动释放。
利用可重入锁:ReentrantLock重写上述售票案例
package com.amyth.JavaEE;
import java.util.concurrent.locks.ReentrantLock;
//创建资源类
class Ticket{
private int number = 100;
//使用可重用锁ReentrantLock
private final ReentrantLock lock = new ReentrantLock();
public void sale(){
lock.lock(); //上锁
try{
if(number > 0 ) {
System.out.println(Thread.currentThread().getName() + "卖出1张票,剩余:" + (number--) + "张");
}else{
System.out.println(Thread.currentThread().getName() + "卖出0张票,剩余:0没票啦!!!");
}
}finally {
lock.unlock(); //解锁
}
}
}
public class SyncTest {
public static void main(String[] args) {
//将资源创建出来
Ticket ticket = new Ticket();
//这里使用了lambda表达式简化了代码
//匿名类实现的接口使用了java.lang.FunctionalInterface注解,且只有一个待实现的抽象接口方法
new Thread(() -> {
for (int i=0; i<30;i++)
{
ticket.sale();
}
},"AA").start();
new Thread(() -> {
for (int i=0; i<30;i++)
{
ticket.sale();
}
},"BB").start();
new Thread(() -> {
for (int i=0; i<30;i++)
{
ticket.sale();
}
},"CC").start();
}
}
- LOCK能让等待锁的线程响应中断,synchronized不行,等待的线程会一直等下去。
- 通过LOCK可以知道是否获取锁,关键字无法做到。
- LOCK可以提高多个线程进行读操作的效率。
总的来说,在资源竞争激烈的环境中,LOCK的性能非常非常好!
通过synchronized实现进程间通信的方法 number+1-1:
package com.amyth.JavaEE;
//资源类创建
class Share{
private int number = 0;
public synchronized void incr() throws InterruptedException {
//资源类中操作方法
if(number!=0){
this.wait();
}
number++;
System.out.println("Thread number = "+number);
this.notify();
}
public synchronized void decr() throws InterruptedException {
if (number==0){
this.wait();
}
number--;
System.out.println("Thread number = "+number);
this.notify();
}
}
public class ThreadDemo1 {
public static void main(String[] args) {
Share share = new Share();
new Thread(()->{
for(int i =0;i<=10;i++){
try {
share.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"ADD").start();
new Thread(()->{
for(int i =0;i<=10;i++){
try {
share.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"Decr").start();
}
}
虚假唤醒问题实例:当线程数超过2个之后,使用if判断就可能出现虚假唤醒问题
package com.amyth.JavaEE;
//资源类创建
class Share{
private int number = 0;
public synchronized void incr() throws InterruptedException {
//资源类中操作方法
if(number!=0){
this.wait();
}
number++;
System.out.println("Thread number = "+number);
this.notifyAll();
}
public synchronized void decr() throws InterruptedException {
if (number==0){
this.wait();
}
number--;
System.out.println("Thread number = "+number);
this.notifyAll();
}
}
public class ThreadDemo1 {
public static void main(String[] args) {
Share share = new Share();
new Thread(()->{
for(int i =0;i<=10;i++){
try {
share.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"ADD").start();
new Thread(()->{
for(int i =0;i<=10;i++){
try {
share.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"Decr").start();
new Thread(()->{
for(int i =0;i<=10;i++){
try {
share.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"CC").start();
new Thread(()->{
for(int i =0;i<=10;i++){
try {
share.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"DD").start();
}
}
此时结果中就出现1、0之外的数字,这就是虚假唤醒情况。
具体分析:
AA线程+1之后 num=1 唤醒其余线程
此时可能CC抢到了线程 通过if判断 num!=0 就进入了wait
…
经过一系列这种操作,一旦别的线程再次唤醒CC线程,此时就会执行wait之后的代码
因为这是基于wait方法在哪里睡着就在哪里醒来的特点,因此if判断只一次有效,第二次被唤醒就不经过if
解决办法就是使用while循环进行判断,睡着之后醒来又会在循环中再进行判断。
使用LOCK锁实现上述示例
package com.amyth.JavaEE;
import javax.naming.NameNotFoundException;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//创建资源类
class Numbers{
private int number=0;
private Lock lock= new ReentrantLock();
private Condition condition = lock.newCondition();
//+1
public void incr() throws InterruptedException {
lock.lock();
//判断
try{
while(number !=0){
condition.await();
}
//干活
number++;
System.out.println("number +1 :"+number);
//通知
condition.signalAll();
}finally {
lock.unlock();
}
}
//-1
public void decr() throws InterruptedException {
lock.lock();
//判断
try{
while(number ==0){
condition.await();
}
//干活
number--;
System.out.println("number -1 :"+number);
//通知
condition.signalAll();
}finally {
lock.unlock();
}
}
}
public class ThreadDemo2 {
public static void main(String[] args) {
Numbers numbers = new Numbers();
new Thread(() -> {
for (int i =0;i<=10;i++){
try {
numbers.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"AA").start();
new Thread(() -> {
for (int i =0;i<=10;i++){
try {
numbers.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"BB").start();
}
}