线程的锁机制
锁机制的介绍
针对于临界资源安全隐患问题的解决方式。引入锁机制。 1. 锁机制的作用:将异步的代码块变成同步的代码块。 2. 语法: synchronized(锁对象的地址){ //需要同步的代码块( 如果不同步,就会出现安全隐患问题) } 3. 任何的java对象都可以作为锁。 一个要求: 所有的线程看到的都是同一个对象。 4. 同步的代码块在可能的情况下,尽量缩小范围,提高其他代码的并发效率。 5. 运行逻辑: 当一个线程A执行到{}里,就表示该线程获取了锁对象,其他线程都必须等待,直到 线程A执行完了同步代码块,会自动释放锁对象。其他线程才有机会获取锁对象,谁获取到锁 对象,谁就执行同步代码块。
package CSDN.day02;public class _13 {public static void main(String[] args) {}
}class Desk1 implements Runnable {private static int beanCount = 10;public void take() {beanCount--;}public void run() {while (true) {try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (this) {if (beanCount > 0) {take();}}System.out.println(Thread.currentThread().getName() + "拿走了一颗豆子,剩余:" + beanCount);if (beanCount <= 0) {break;}}}
}
synchronized的作用域
1.如果使用了synchronized(锁对象){ } ,那么{ }中就是同步块。
2.synchronized可以作用在非静态方法,此时锁对象是this。
3.synchronized可以作用在静态方法上,此时锁对象是类名.class。
package CSDN.day0821;public class _01 {public static void main(String[] args) {Blackboard b1 = new Blackboard();Thread xm = new Thread(b1,"小明");Thread xl = new Thread(b1,"小莉");xm.start();xl.start();}
}
class Blackboard implements Runnable{private static int apple = 10;public void run() {while(true){take();System.out.println(Thread.currentThread().getName()+"拿走了一颗苹果,还剩"+apple+"个苹果");if(apple <= 0){break;}}}public synchronized void take(){if(apple > 0){apple--;}}
}
4.synchronized可以锁住方法里面的部分代码即锁的范围就是同步块的范围此时,锁对象可以是java的任何引用类型的对象。 5.非静态方法上锁: 就是在方法上添加修饰词synchronized -1. 当一个线程访问了该方法时,就是获取了锁对象,其他线程想要访问该方法,处于等待状态。 -2. 有个前提:多个线程访问的对象实例必须是同一个。 比如下面案例的桌子 必须是同一张桌子。 -3. 当一个线程正在访问一个实例的某一个同步方法时,this这个锁对象即被他所占用,其他线程想要执行该实例的其他的同步方法时,也需要等待,因为this锁已经被占用了。
package CSDN.day0821;public class _02 {public static void main(String[] args) {Desk desk1 = new Desk();Desk desk2 = new Desk();Thread t1 = new Thread(desk1,"xm");Thread t2 = new Thread(desk1,"qq");t1.start();t2.start();}
}
class Desk implements Runnable{private int BeanCount = 10;public synchronized void take(){System.out.println("开始取豆子");if(BeanCount>0){BeanCount--;}if(BeanCount==0){System.out.println("没豆子了");}
// System.out.println("豆-1");}public void run() {while(true){take();if (BeanCount<=0){break;}System.out.println(Thread.currentThread().getName()+"取豆,还剩:"+BeanCount);}}}
单例设计懒汉模式的锁
package CSDN.day0821;import java.util.LinkedList;
import java.util.List;public class _03 {public static void main(String[] args) {List<Thread> pool = new LinkedList<>();for (int i = 0; i < 10; i++) {Thread t = new Thread(){public void run(){Boss b = Boss.getInstance();}};pool.add(t);}for (Thread t : pool) {t.start();}}
}class Boss {private static Boss instance;private Boss() {}public synchronized static Boss getInstance() {//synchronized(Boss.class)if (instance == null) {instance = new Boss();}return instance;}
}
死锁
产生原因:线程1 先获取锁A,然后再想要获取锁B
线程2 先获取锁B,然后再想要获取锁A
两个线程都占用了对方想要的锁,而对方还占用并不释放。因此都出现了等待现象,无法继续向下执行。 这就是死锁。
例如:
package CSDN.day0821;public class _04 {public static void main(String[] args) {Thread t1 = new Thread("小明"){public void run(){synchronized ("A"){for(int i=1;i<=10;i++){System.out.println(getName()+":"+i);}}synchronized ("B"){for(int i=1;i<=10;i++){System.out.println(getName()+":"+i);}}}};Thread t2 = new Thread("小l"){public void run(){synchronized ("B"){for(int i=1;i<=10;i++){System.out.println(getName()+":"+i);}}synchronized ("A"){for(int i=1;i<=10;i++){System.out.println(getName()+":"+i);}}}};}
}
避免死锁方法
1. 就是按照顺序加锁2. 设置超时等待。 设置了一定时间限制,如果在这个时间范围内没有获取到锁,那就不执行锁里的内容
package CSDN.day0821;public class _05 {public static void main(String[] args) {Thread t1 = new Thread("小明"){public void run() {synchronized ("A"){for (int i = 0; i < 50; i++) {System.out.println(getName()+":"+i);}synchronized ("B"){for (int i = 50; i < 100; i++) {System.out.println(getName()+":"+i);}}}}};Thread t2 = new Thread("小红"){public void run() {synchronized ("B"){for (int i = 0; i < 50; i++) {System.out.println(getName()+":"+i);}synchronized ("A"){for (int i = 50; i < 100; i++) {System.out.println(getName()+":"+i);}try {"B".wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}}}};t1.start();t2.start();}
}
与锁有关的方法
1、wait():释放自己占有的锁对象,进入等待队列中,不参与锁的争抢,不参与时间片段的争抢。 也就是阻塞了。知道被notify/notifyAll.注意,是锁调用该方法
wait(long timeout):等待一个指定时间,如果超过这个时间,则自动唤醒。
wait(long timeout ,intnaous) 指定的等待时间更精确一些。
2.notify():通知,唤醒等待队列中的某一个线程,是随机的。被唤醒的那个线程进入锁池状态,开始争抢锁对象。 3. notifyAll():通知,唤醒等待队列中的所有线程。被唤醒的所有线程进入锁池状态,开始争抢锁对象。
package CSDN.day0822;public class _01 {public static void main(String[] args) {Object obj = new Object();Thread down = new Thread(() -> {System.out.println("开始加载图片");for (int i = 1; i <= 100; i++) {System.out.println(Thread.currentThread().getName() + ": " + i + "%");try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace( );}}System.out.println("图片加载完成");synchronized (obj) {obj.notify();}}, "下载图片");Thread show = new Thread(() -> {try {synchronized (obj) {obj.wait(5000);}} catch (InterruptedException e) {e.printStackTrace();}System.out.println("开始显示图片");for (int i = 1; i <= 100; i++) {System.out.println(Thread.currentThread().getName() + ": " + i + "%");try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("图片显示完成");}}, "显示图片");down.start();show.start();}}
可重入锁
标准写法: 释放锁操作在finally模块中
package CSDN.day0822;import java.util.concurrent.locks.ReentrantLock;public class _02 {public static void main(String[] args) {MyCounter1 counter = new MyCounter1("秒表");Thread thread = new Thread(counter,"壮壮");Thread thread1 = new Thread(counter,"十八");thread.start();thread1.start();}
}
class MyCounter1 implements Runnable{private int count = 0;private String name;ReentrantLock rl = new ReentrantLock(true);public MyCounter1(String name) {this.name = name;}public void run() {rl.lock();for (int i = 0; i < 50; i++) {System.out.println(Thread.currentThread().getName()+"计数:"+i);try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}try {String str = null;System.out.println(str.length());} catch (Exception e) {e.printStackTrace();} finally {rl.unlock();}}}
是一个能让线程多次获取锁对象的类型,里面内置了一个计数器,用于记录当前线程获取该锁的次数 能避免死锁情况 该类提供了两个子类型: 非公平锁 :多个线程获取锁的方式不是按照线程的请求顺序,而是可能发生”插队“现象,这种锁性能高,但是可能增加线程解现象 公平锁:按照请求顺序获取,谁都获取到锁,减少了线程接线箱,但是系统的吞吐量可能不高 (性能高的线程也需要派对才能获取锁) 如何使用: 构造器 构造器中传入true,表示传入公平锁,传入false或者不指定参数,使用非公平锁 该类比synchronized更加灵活,但是需要手动上锁和解锁 lock() 上锁方法:锁对象没有被其他线程占用是,会成功,苟泽当前的线程处于阻塞状态 unlock() 必须在占有锁的时候才能进行解锁,否则报异常 trylock() 尝试获取锁,如果获取不到,不阻塞,而是执行其他代码 获取不到锁返回false 获取到返回true trylock(long time,TimeUnit unit) 可以指定一定时间内获取锁对象,如果超市还没获取,返回false 获取返回true
package CSDN.day0822;import java.util.concurrent.locks.ReentrantLock;public class _03 {public static void main(String[] args) {MyCounter counter = new MyCounter("秒表");Thread t1 = new Thread(counter,"小明");Thread t2 = new Thread(counter,"小美");t1.start();t2.start();}
}
class MyCounter implements Runnable{private int count = 0;private String name;ReentrantLock rl = new ReentrantLock(true);public MyCounter(String name) {this.name = name;}public void run() {rl.lock();for (int i = 0; i < 10; i++) {count++;System.out.println(Thread.currentThread().getName()+ ": " + count + " times");try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}}rl.unlock();}
}
