4.1 传统方式(同步方法)
传统的 synchronized
package cn.guardwhy.test01;
// 1.并发:多线程操作同一个资源类,把线程拿进线程中
public class SaleTicketDemo01 {
public static void main(String[] args) {
Ticket ticket = new Ticket();
// 函数式接口
new Thread(()->{
for(int i = 1; i < 30; i++){
ticket.saleTicket();
}
}, "kobe").start();
new Thread(()->{
for(int i = 1; i < 30; i++){
ticket.saleTicket();
}
}, "Curry").start();
new Thread(()->{
for(int i = 1; i < 30; i++){
ticket.saleTicket();
}
}, "James").start();
}
}
// oop资源类
class Ticket{
private int number = 30;
// synchronized 本质: 队列, 锁
public synchronized void saleTicket(){
// 条件判断
if(number > 0){
System.out.println(Thread.currentThread().getName() + "卖出第" + (number--)
+ "票, 还剩下:" + number + "张票");
}
}
}
4.2 Lock锁
使用 juc.locks 包下的类操作 Lock 锁
4.2.1接口实现类
4.2.2 公平锁和非公平锁
公平锁: 十分公平, 可以先来后到。
非公平锁: 十分不公平,可以插队(默认是非公平锁)。
代码示例
package cn.guardwhy.test02;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SaleTicketDemo01 {
public static void main(String[] args) {
Ticket ticket = new Ticket();
// 函数式接口
new Thread(()->{
for(int i = 1; i < 30; i++) ticket.saleTicket();}, "kobe").start();
new Thread(()->{
for(int i = 1; i < 30; i++) ticket.saleTicket();}, "Curry").start();
new Thread(()->{
for(int i = 1; i < 30; i++) ticket.saleTicket(); }, "James").start();
}
}
/*
* lock锁使用:
* 1.new ReentrantLock();
* 2.Lock.lock(); // 加锁
* 3. finally=> lock.unlock(); // 解锁
*/
class Ticket{
private int number = 30;
// 锁对象
Lock lock = new ReentrantLock();
public void saleTicket(){
// 加锁操作
lock.lock();
try {
// 条件判断
if(number > 0){
System.out.println(Thread.currentThread().getName() + "卖出第" + (number--)
+ "票, 还剩下:" + number + "张票");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放锁
lock.unlock();
}
}
}
4.2.3 synchronized和lock 区别
1、原始构成
- synchronized是java内置关键字,属于JVM层面。
- lock是具体类(java.util.concurrent.locks.Lock)是API层面的锁。
2、使用方法
- synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁。
- synchronized不需要用户去手动释放锁,当synchronized代码执行完后系统会自动的让线程释放对锁的占用。
- ReentrantLock则需要用户去手动释放锁,若用户没有主动释放锁,就可能出现死锁现象。需要lock( )和unlock()方法配合try/finally语句块来完成。
3、等待是否中断
- synchronized不可中断,除非抛出异常或者正常运行完成。
- ReentrantLock可中断。
1.设置超时方法: tryLock(long timeout, TimeUnit unit);
2.lockInterruptibly()放入代码块中,调用interrupt()方法可中断。
4、加锁是否公平
- synchronized的锁可重入、不可中断、非公平。
- ReentrantLock两者都可以,默认非公平锁,构造方法可以传入boolean值,true为公平锁,false为非公平锁。
- Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。
5、实现等待通知
- 关键字synchronized通过与wait( )和notify( )/notifyAll( )方法相结合可以实现等待/通知
- ReentrantLock实现等待/通知,需要借助于Condition对象。用来精确唤醒线程,而不是像synchronized那样要么随机唤醒一个,要么就是全部唤醒。
4.3 生产者和消费者
4.3.1 synchroinzed实现
代码示例
package cn.guardwhy.test02;
/*
* 线程之间的通信问题: 生产者和消费者问题, 等待唤醒,通知唤醒
* 线程交替执行,两个线程操作同一个变量 number=0
*/
public class ThreadDemo02 {
public static void main(String[] args) {
// 创建线程对象
Data data = new Data();
new Thread(()->{
for (int i=1; i<=8; i++){
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "kobe").start();
new Thread(()->{
for (int i=1; i<=8; i++){
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "curry").start();
}
}
// 资源类
class Data{
// 判断等待,业务, 通知
private int number = 0;
public synchronized void increment() throws InterruptedException {
// 判断是否该线程执行操作
if(number != 0){
// 等待操作
this.wait();
}
// 执行操作+1
number++;
System.out.println(Thread.currentThread().getName() + "===>" + number);
// 通知其他线程,num+1执行完毕
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
// 判断是否该线程执行操作
if(number == 0){
// 等待操作
this.wait();
}
// 执行操作+1
number--;
System.out.println(Thread.currentThread().getName() + "===>" + number);
// 通知其他线程,num-1执行完毕
this.notifyAll();
}
}
4.3.2 防止虚假唤醒
if 和while 的区别
多个线程同时操作,存在线程虚假唤醒
代码示例
package cn.guardwhy.test02;
public class ThreadDemo03 {
public static void main(String[] args) {
// 创建线程对象
Account accout = new Account();
new Thread(()->{
for (int i=1; i<=8; i++){
try {
accout.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "kobe").start();
new Thread(()->{
for (int i=1; i<=8; i++){
try {
accout.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "curry").start();
new Thread(()->{
for (int i=1; i<=8; i++){
try {
accout.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "james").start();
new Thread(()->{
for (int i=1; i<=8; i++){
try {
accout.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "rondo").start();
}
}
// 资源类
class Account {
// 判断等待,业务, 通知
private int number = 0;
public synchronized void increment() throws InterruptedException {
// 判断是否该线程执行操作
while (number != 0){
// 等待操作
this.wait();
}
// 执行操作+1
number++;
System.out.println(Thread.currentThread().getName() + "===>" + number);
// 通知其他线程,num+1执行完毕
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
// 判断是否该线程执行操作
while(number == 0){
// 等待操作
this.wait();
}
// 执行操作+1
number--;
System.out.println(Thread.currentThread().getName() + "===>" + number);
// 通知其他线程,num-1执行完毕
this.notifyAll();
}
}
4.3.3 Lock锁实现
精准通知,唤醒线程。
Lock接口
Condition对象
常用方法
两者方式区别
代码示例
package cn.guardwhy.test03;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
* 精确通知顺序访问
*/
public class LockDemo01 {
public static void main(String[] args) {
// 1.创建数据资源对象
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.print1();
}
}, "curry").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.print2();
}
}, "kobe").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.print3();
}
}, "james").start();
}
}
// 资源类
class Data{
// 创建锁对象
private Lock lock = new ReentrantLock();
// 创建Condition对象
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
// 定义计数器
private int number = 1;
// 打印方法
public void print1(){
// 调用锁对象
lock.lock();
try {
// 业务,判断->执行-> 通知
while (number != 1){
// 等待
condition1.await();
}
System.out.println(Thread.currentThread().getName() + "===>curry");
// 指定唤醒的线程
number = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void print2(){
// 调用锁对象
lock.lock();
try {
// 业务,判断->执行-> 通知
while (number != 2){
condition2.await();
}
System.out.println(Thread.currentThread().getName() + "===>kobe");
// 唤醒指定的线程
number = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void print3(){
// 调用锁对象
lock.lock();
try {
// 业务,判断->执行-> 通知
while (number != 3){
condition3.await();
}
System.out.println(Thread.currentThread().getName() + "===>james");
// 唤醒指定的线程
number = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
执行结果
4.4 ReadWriteLock(读写锁)
4.4.1 基本概念
4.5.2 代码示例
package cn.guardwhy.juc01;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/*
* 独占锁(写锁):一次只能被一个线程占有
* 共享锁(读锁):多个线程可以同时占有
* ReadWriteLock
* 读-读 可以共存!!!
* 读-写 不能共存!!!
* 写-写 不能共存!!!
*/
public class ReadWriteLockDemo01 {
public static void main(String[] args) {
// 创建MyCache对象
MyCacheLock myCache = new MyCacheLock();
// 写入操作
for (int i = 1; i <=3 ; i++) {
int temp = i;
new Thread(()->{
myCache.put(temp + "", temp+ "");
}, String.valueOf(i)).start();
}
// 读取操作
for (int i = 1; i <=3 ; i++) {
int temp = i;
new Thread(()->{
myCache.get(temp + "");
}, String.valueOf(i)).start();
}
}
}
/*
加锁自定义缓存
*/
class MyCacheLock{
// volatile能保证顺序性和可见性,但是不能保证原子性,避免机读重新排序。
private volatile Map<String, Object> map = new HashMap<>();
// 读写锁: 更加细粒度的控制
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// 存,写入操作的时候,只希望同时只有一个线程写入
public void put(String key, Object value){
// 加锁操作
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "写入" + key);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "写入成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
// 解锁
readWriteLock.writeLock().unlock();
}
}
// 取,读操作,所有人都可以读取
public void get(String key){
// 加锁
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "读取" + key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}
执行结果