标签:data- 简单的 重点 syn system 非公平锁 同步 解锁 OLE
真正的多线程开发,公司中的开发,需要降低耦合度
线程是一个单独的资源,没有任何附属的操作!
单独的资源包含属性、方法
第一种:高耦合写法,Ticket
线程类还有附属操作,不推荐使用
public class SaleTicketDemo01 { public static void main(String[] args) { Ticket ticket = new Ticket(); new Thread(ticket).start(); } } class Ticket implements Runnable{ @Override public void run() { } }
第二种:降低耦合度
//举例买票的栗子 /** * 真正的多线程开发,公司中的开发,降低耦合性 * 线程就是一个单独的资源类,没有任何附属的操作! * 1、 属性、方法 */ public class SaleTicketDemo01 { public static void main(String[] args) { // 并发,就是多个线程操作同一份资源,使用时直接丢入线程 Ticket ticket = new Ticket(); // 3个人同时卖票 // Runnable接口标注着 @FunctionalInterface 表示函数式接口,在jdk1.8,可以使用lambda表达式 ()->{} new Thread(() -> { for (int i = 0; i < 40; i++) { //卖40张肯定会卖完 ticket.sale(); //调用买票方法,就是操作资源 } }).start(); new Thread(() -> { for (int i = 0; i < 40; i++) { ticket.sale(); } }).start(); new Thread(() -> { for (int i = 0; i < 40; i++) { ticket.sale(); } }).start(); } } class Ticket{ //剩余的票 private int number = 30; // 售票方法 // synchronized同步方法,本质:相当于队列,锁 // 比如:食堂学生打饭,不排队学生就会一拥而至;排队的情况下,相当于一个人打饭会有一个锁,打完饭后释放锁; public synchronized void sale(){ if(number > 0){ System.out.println("已售出第" + number-- + "张票,剩余:" + number + "张"); } } }
多线程下产生并发问题,需要加上synchronized,同步方法;
1)实现类
2)使用方法
3) 可重入锁:ReentrantLock类
有两种构造方法,可以构造非公平锁和公平锁,默认是公平锁!
公平锁:顾名思义非常的公平
非公平锁:顾名思义它是不公平的
package com.zxh.demo01; //举例买票的栗子 import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class SaleTicketDemo02 { public static void main(String[] args) { // 并发,就是多个线程操作同一份资源,使用时直接丢入线程 Ticket ticket = new Ticket(); // 3个人同时卖票 new Thread(() -> { for (int i = 0; i < 40; i++) ticket.sale(); }, "A").start(); new Thread(() -> { for (int i = 0; i < 40; i++) ticket.sale(); }, "B").start(); new Thread(() -> { for (int i = 0; i < 40; i++) ticket.sale(); }, "C").start(); } } // Lock三部曲 // 1、new ReentrantLock(); 创建锁 // 2、lock.lock(); // 加锁 // 3、lock.unlock(); // 解锁 class Ticket2 { //剩余的票 private int number = 30; Lock lock = new ReentrantLock(); // 售票方法 public void sale(){ lock.lock(); // 加锁 // lock.tryLock(); //尝试获取锁,只有在调用时它不被另一个线程占用才能获取锁,获取成功返回true,否则返回false try { // 业务代码 if(number > 0){ System.out.println(Thread.currentThread().getName() + "已售出第" + number-- + "张票,剩余:" + number + "张"); } } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); // 解锁 } } }
广义上的可重入锁:
可重入锁就是可以重复可递归调用的锁,就是在外层使用锁后,内层依旧可以使用该锁,并且不会发生死锁(可重入锁:前提锁的是同一个对象或者class,如果锁的东西不同,那就不是可重入锁了)。
可重入锁是以线程为单位的,比如:当一个线程获取对象加锁后,该线程可以再次获取该对象的锁,但是其他线程不行,需要等待该线程释放锁。
synchronized
和 ReentrantLock
都是可重入锁。
解释了这么多,可能还是不太明白,接下来通过栗子进行解释什么是可重入锁。
public class MyTest { public static void main(String[] args) { Data data = new Data(); /* synchronized:锁的是方法的调用者,就是对象 data 现在创建两条线程,各自去调用10次get方法,或者更多 你会发现,一个线程获取了两次锁,并没有发生死锁,并且哪个线程谁先拿到锁,其他的线程只能等待 (运行结果可能存在,B线程在A线程之间调用了get和set方法, 那是因为A线程释放了锁,并且CPU刚好去执行B线程了,尽管如此你还是会发现,get()set()方法都是连在一起执行的) 所以:这就是可重入锁,该锁可以被重复递归的调用 */ // 这里为了截图方便调用3次get()方法 new Thread(() -> { for (int i = 0; i < 3; i++) data.get(); }, "A").start(); new Thread(() -> { for (int i = 0; i < 3; i++) data.get(); }, "B").start(); } } class Data{ /** * 两个方法都是同步方法,作用:打印线程的名字 * get()方法调用set(),一个同步方法调用另一个同步方法 */ public synchronized void get(){ System.out.println(Thread.currentThread().getName() + "=> get()"); set(); } public synchronized void set(){ System.out.println(Thread.currentThread().getName() + "=> set()"); } }
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class MyTest { public static void main(String[] args) { Data data = new Data(); // 这里为了截图方便调用3次get()方法 new Thread(() -> { for (int i = 0; i < 3; i++) data.get(); }, "A").start(); new Thread(() -> { for (int i = 0; i < 3; i++) data.get(); }, "B").start(); } } class Data{ Lock lock = new ReentrantLock(); // 可重入锁 /** * 作用:打印线程的名字 * get()方法调用set(),一个同步方法调用另一个同步方法 */ public void get(){ lock.lock(); try { System.out.println(Thread.currentThread().getName() + "=> get()"); set(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void set(){ lock.lock(); try { System.out.println(Thread.currentThread().getName() + "=> set()"); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }
自定义不可重入锁
现在我们自己定义一个简单的锁,并且不是可重入锁,我们可以看一下会发生什么
public class MyTest { public static void main(String[] args) { Data data = new Data(); // 这里为了截图方便调用3次get()方法 new Thread(() -> { for (int i = 0; i < 3; i++) data.get(); }, "A").start(); // new Thread(() -> { for (int i = 0; i < 3; i++) data.get(); }, "B").start(); } } class Data{ MyLock lock = new MyLock(); // 可重入锁 /** * 作用:打印线程的名字 * get()方法调用set(),一个同步方法调用另一个同步方法 */ public void get(){ lock.lock(); try { System.out.println(Thread.currentThread().getName() + "=> get()"); set(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void set(){ lock.lock(); try { System.out.println(Thread.currentThread().getName() + "=> set()"); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } } // 自定义简单锁 class MyLock{ private boolean flag = false; // 使用变量控制该锁是否被占用 // 加锁 public synchronized void lock() { while (flag){ // 如果是true,那么表示该锁被占用 try { this.wait(); // 线程等待 } catch (InterruptedException e) { e.printStackTrace(); } } flag = true; // 加锁 } // 解锁 public synchronized void unlock(){ flag = false; // 释放锁 notifyAll(); // 唤醒其他线程 } }
接下来说一下可重入锁的实现原理?
实现原理:通过为每一个锁关联一个请求计数器和一个占用它的线程。当计数器为0,代表该锁没有被占用;当线程请求一个未被占用的锁,那么JVM将记录锁的占用者,并且将请求计数器置为1。
如果同一个线程再次请求该锁,那么计数器会递增+1。
每次占用线程退出同步块,请求计数器会 -1,直到计数器为0才释放锁。
修改自定义的锁为可重入锁
public class MyTest { public static void main(String[] args) { Data data = new Data(); // 这里为了截图方便调用3次get()方法 new Thread(() -> { for (int i = 0; i < 3; i++) data.get(); }, "A").start(); new Thread(() -> { for (int i = 0; i < 3; i++) data.get(); }, "B").start(); } } class Data{ MyLock lock = new MyLock(); // 可重入锁 /** * 作用:打印线程的名字 * get()方法调用set(),一个同步方法调用另一个同步方法 */ public void get(){ lock.lock(); try { System.out.println(Thread.currentThread().getName() + "=> get()"); set(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void set(){ lock.lock(); try { System.out.println(Thread.currentThread().getName() + "=> set()"); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } } // 自定义简单锁 class MyLock{ private boolean flag = false; // 使用变量控制该锁是否被占用 private int lockCount = 0; // 关联一个请求计数器 private Thread thread = null; // 关联一个占用它的线程 // 加锁 public synchronized void lock() { Thread currentThread = Thread.currentThread(); while (flag && this.thread != currentThread){ // 如果该锁被占用,并且进入的线程不是当前占用的线程 try { this.wait(); // 线程等待 } catch (InterruptedException e) { e.printStackTrace(); } } // 锁没有被占用 或者 锁被占用但是为同一个线程进入 flag = true; // 加锁 lockCount++; // 请求计数器+1 this.thread = currentThread; // 被当前进入的线程占用 } // 解锁 public synchronized void unlock(){ if (this.thread == Thread.currentThread()){ // 如果进入的线程是当前占用的线程 lockCount--; // 请求计数器 -1 if (lockCount == 0){ // 如果请求计数器为0 flag = false; // 释放锁 notifyAll(); // 唤醒其他线程 } } } }
特征区别
详细区别
标签:data- 简单的 重点 syn system 非公平锁 同步 解锁 OLE
原文地址:https://www.cnblogs.com/zxhbk/p/12943949.html