一个并发的问题
现在有100张票,很多人来抢,可能就会出现超卖的问题
public static int cnt = 100;
public static void main(String[] args) throws InterruptedException {
List<Thread> list = new ArrayList<>();
for (int i=0;i<1000;i++){
Thread thread = new Thread(){
@Override
public void run() {
if(cnt>0){
String name = Thread.currentThread().getName();
System.out.println(name+"抢到了"+cnt+"张票");
cnt--;
}
}
};
list.add(thread);
}
//启动
for (int i=0;i<list.size();i++){
list.get(i).start();
}
for (int i=0;i<list.size();i++){
list.get(i).join();
}
System.out.println("剩下的票:"+cnt);
}
第一个问题:cnt>0作为条件,会导致并发的问题
- 第二个问题:cnt在—的时候,也可能导致并发的问题
使用锁进行解决
public static int cnt = 100;
//创建一把锁
static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
List<Thread> list = new ArrayList<>();
for (int i=0;i<1000;i++){
Thread thread = new Thread(){
@Override
public void run() {
//使用锁将可能出现并发的代码锁住
synchronized (lock){
if(cnt>0){
String name = Thread.currentThread().getName();
System.out.println(name+"抢到了"+cnt+"张票");
cnt--;
}
}
}
};
list.add(thread);
}
//启动
for (int i=0;i<list.size();i++){
list.get(i).start();
}
for (int i=0;i<list.size();i++){
list.get(i).join();
}
System.out.println("剩下的票:"+cnt);
}
synchronized
基本使用,锁对象
- 所有的线程必须要使用同一把锁对象才可以有效果
以下代码是无法锁住线程方法的, 如果是多线程,下面的代码,每个线程对象都会使用自己的独立锁 ```java public class TicketThread extends Thread{ public static int cnt = 100; Object lock = new Lock();
@Override public void run() { //使用锁将可能出现并发的代码锁住 synchronized (lock){
if(cnt>0){
String name = Thread.currentThread().getName();
System.out.println(name+"抢到了"+cnt+"张票");
cnt--;
}
} } }
- 可以将Object lock使用static修饰,或者从外部传入同一把锁
- 修饰this对象
- 注意必须是多个线程使用一个对象的时候才有效果,一般用于单例模式
- 创建一个实现了Runnable的任务
```java
public class TickTask implements Runnable {
public int cnt = 100;
@Override
public void run() {
//使用锁将可能出现并发的代码锁住
synchronized (this){
if(cnt>0){
String name = Thread.currentThread().getName();
System.out.println(name+"抢到了"+cnt+"张票");
cnt--;
}
}
}
}
- 通过线程池,让多个线程调用同一个task
public static void main(String[] args) {
//线程池+runnable组合
ExecutorService service = Executors.newFixedThreadPool(10);
TickTask task = new TickTask();
for(int i=0;i<1000;i++){
service.execute(task);
}
}
- 修饰方法
- 一般情况需要需要通过static配合使用才有效果,因为锁住是类的方法,而不是对象的方法
- 如果是对象的方法,必须要保证是同一个对象才有效果
- 一般情况,最好不要锁方法
修饰类的类对象
- 每个类被jvm加载之后,都会生产一个类的对象,而且是唯一的
- 大多数的情况直接锁类对象,都是有效果的,因为类对象是唯一的
synchronized(TicketThread.class){
if(cnt>0){
String name = Thread.currentThread().getName();
System.out.println(name+"抢到了"+cnt+"张票");
cnt--;
}
}
synchronized基本原理
synchronized是自动锁,什么时候加锁,什么时候释放锁,都是系统完成的,如果在使用不当,那么会出现死锁
创建一个男孩线程去找女孩约会
public class Boy extends Thread {
@Override
public void run() {
try {
date();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void date() throws InterruptedException {
//男孩只能去找一个女孩约会
synchronized (Boy.class){
System.out.println("男去找女孩约会了");
Thread.sleep(10);
//加上锁,防止女孩同和两个男孩约会
synchronized(Girl.class){
System.out.println("女孩在约会中...");
}
System.out.println("在一起约会了...");
}
}
}
创建一个女孩去找男孩约会
public class Girl extends Thread{
@Override
public void run() {
try {
date();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void date() throws InterruptedException {
//女孩只能去找一个男孩约会
synchronized (Girl.class){
System.out.println("女孩去找男孩约会了");
//防止男孩和多个女孩约会
synchronized (Boy.class){
System.out.println("男孩在约会了");
}
System.out.println("在一起约会了");
}
}
}
开始约会
public static void main(String[] args) throws InterruptedException {
new Boy().start();
new Girl().start();
}
Lock是JDK提供的锁对象,可以手动控制锁的加载和释放
lock是一个接口
public interface Lock {
void lock(); //主动锁住代码,会阻塞代码
void lockInterruptibly() throws InterruptedException;//可以中断的锁
boolean tryLock();//尝试获取锁,非阻塞锁
boolean tryLock(long time, TimeUnit unit) throws InterruptedException; //尝试获取锁,指定一个等待时间
void unlock();//释放锁
Condition newCondition();//条件锁,可以分为读锁和写锁
}
ReentrantLock的基本使用
public class TestLock {
static int cnt =10;
public static void main(String[] args) {
Lock lock = new ReentrantLock();
//创建一个任务
Runnable runnable = ()->{
try{
lock.lock();//当前线程主动获取锁,如果其他线程已经获取了锁,那么会阻塞
if(cnt>0){
System.out.println("抢到资源"+cnt);
cnt--;
if(cnt==5){
throw new RuntimeException();
}
}
}finally { //无论是否抛出异常,都释放锁
lock.unlock();// 释放锁
}
};
//创建线程池执行任务
ExecutorService service = Executors.newFixedThreadPool(5);
for (int i=0;i<100;i++){
service.execute(runnable);
}
service.shutdown();
}
}
tryLock的使用
尝试获取锁,如果成功则返回true,如果失败则返回false
Lock lock = new ReentrantLock();
//创建一个任务
Runnable runnable = ()->{
if(lock.tryLock()){//尝试获取锁,不会阻塞线程
try{
if(cnt>0){
System.out.println("抢到资源"+cnt);
cnt--;
}
} finally { //无论是否抛出异常,都释放锁
lock.unlock();// 释放锁
}
}else{
System.out.println("没有抢到资源");
}
};
可以带时间参数的,在获取锁的时候,可以设置等待的时间
//创建一个任务
Runnable runnable = () -> {
try {
//第一个参数是等待多长时间
//第二个参数是等待时间的单位
if (lock.tryLock(1,TimeUnit.MICROSECONDS)) {
try {
if (cnt > 0) {
System.out.println("抢到资源" + cnt);
cnt--;
}
}finally {
lock.unlock();
}
} else {
System.out.println("没有抢到资源");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
};
lockInterruptibly:可以中断锁
public static void main(String[] args) throws InterruptedException {
Lock lock = new ReentrantLock();
Runnable task = new Runnable() {
@Override
public void run() {
try {
lock.lockInterruptibly();//可以中断
try {
String name = Thread.currentThread().getName();
System.out.println(name + "执行任务");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
} catch (InterruptedException e) {
// e.printStackTrace();
System.out.println("不想再等待了");
}
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
Thread.sleep(1000);
t2.interrupt(); //中断线程2的等待
}
锁的分类
可重载锁:一个已经获得锁的线程,可以重复再加载锁
案例
class MyClass {
public synchronized void method1() {
method2();
}
public synchronized void method2() {
}
}
method1 和 methods都所有锁,一个线程加载了method1的锁之后,还可以继续加载method2的锁,如果不是可重载锁,则不能加载
- 可中断锁:A线程获取了锁,B线程在等待锁的过程中,可以直接中断等待
- 公平锁:使得多个线程在获取锁的机会上是比较公平
Lock lock = new ReentrantLock(true);//true是公平,默认false是不公平 读写锁:可以让数据的读取和修改的锁分开
- 当一个读取数据的线程获取锁的时候,另一个线程如果是读取数据,不需要锁
- 当一个读取数据的线程获取锁的时候,另一个线程如果是写如数据,需要锁
- 当一个写入数据的线程获取锁的时候,另一个线程无论读取还是写,都需要锁
- 基本规则
- 写-写:互斥,阻塞。读-写:互斥,读阻塞写、写阻塞读。读-读:不互斥、不阻塞。
读写分类
public class MyData2 {
int value = 10;
//创建读写锁
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
//分为写锁
ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
//读锁
ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
public int getValue() {
readLock.lock();
try{
Thread.sleep(1000);
return value;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
readLock.unlock();
}
return 0;
}
public void setValue(int value) {
writeLock.lock();
try{
Thread.sleep(1000);
this.value = value;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
writeLock.unlock();
}
}
}
线程通信
在使用多线程的开发的时候,会发现线程运行顺序是无序的,无法对线程进行精确的控制,但是有些场景是需要对线程的顺序进行控制的,例如:生产者-消费-商品的问题
创建一个生产者-消费-商品的场景
创建商品
public class Product {
int id;
String brand;//标签,例如北京,长沙
String name;//名称,例如烤鸭,臭豆腐
}
创建生产者线程
//生产者线程
public class ProductThread extends Thread{
Product product;//操作的商品
public ProductThread(Product product) {
this.product = product;
}
@Override
public void run() {
for (int i=1;i<=10;i++){
product.setId(i);
if(i%2==0){
product.setBrand("长沙");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
product.setName("臭豆腐");
}else{
product.setBrand("北京");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
product.setName("烤鸭");
}
System.out.println("厨师加工了"+product.getId()+"-"+product.getBrand()+product.getName());
}
}
}
创建一个消费者线程
public class ConsumerThread extends Thread{
Product product;//操作的商品
public ConsumerThread(Product product) {
this.product = product;
}
@Override
public void run() {
for (int i=1;i<=10;i++){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("顾客吃了:"+product.getId()+"-"+product.getBrand()+product.getName());
}
}
}
通过synchronized + notify/wait来控制线程的顺序
给生产者添加 ```java public class ProductThread extends Thread{ Product product;//操作的商品
public ProductThread(Product product) { this.product = product; }
@Override public void run() { for (int i=1;i<=10;i++){
synchronized (product){
product.setId(i);
if(i%2==0){
product.setBrand("长沙");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
product.setName("臭豆腐");
}else{
product.setBrand("北京");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
product.setName("烤鸭");
}
System.out.println("厨师加工了"+product.getId()+"-"+product.getBrand()+product.getName());
product.notify();
try {
//放弃锁,进入到等待的状态
product.wait(); //阻塞状态
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} } }
- 给消费者添加
```java
Product product;//操作的商品
public ConsumerThread(Product product) {
this.product = product;
}
@Override
public void run() {
for (int i=1;i<=10;i++){
synchronized (product){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("顾客吃了:"+product.getId()+"-"+product.getBrand()+product.getName());
product.notify();//告诉厨师可以继续开始做了
try {
product.wait();//客户进入等待状态
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}