Java-多线程-锁
synchronized 内置锁
类锁
// 使用类锁的线程
private static class SynClass extends Thread {
@Override
public void run() {
System.out.println("TestClass is running...");
synClass();
}
}
// 类锁,实际是锁类的class对象
// synchronized == 类锁 == SynClzAndInst.class的对象锁
private static synchronized void synClass() {
SleepTools.second(1);
System.out.println("synClass going...");
SleepTools.second(1);
System.out.println("synClass end");
}
// 特殊的类锁 obj对象类锁
private static Object obj = new Object();
private void synStaticObject() {
synchronized (obj) {//类似于类锁
SleepTools.second(1);
System.out.println("synClass going...");
SleepTools.second(1);
System.out.println("synClass end");
}
}
对象锁
- 锁不能是匿名对象。因为匿名对象不是同一个对象,也就不是同一把锁
// 使用对象锁
private static class SynObject implements Runnable {
private SynClzAndInst synClzAndInst;
public SynObject(SynClzAndInst synClzAndInst) {
this.synClzAndInst = synClzAndInst;
}
@Override
public void run() {
System.out.println("TestInstance is running..." + synClzAndInst);
synClzAndInst.instance();
}
}
// 使用对象锁
private static class SynObject2 implements Runnable {
private SynClzAndInst synClzAndInst;
public SynObject2(SynClzAndInst synClzAndInst) {
this.synClzAndInst = synClzAndInst;
}
@Override
public void run() {
System.out.println("TestInstance2 is running..." + synClzAndInst);
synClzAndInst.instance2();
}
}
// 锁对象
private synchronized void instance() {
SleepTools.second(3);
System.out.println("synInstance is going..." + this.toString());
SleepTools.second(3);
System.out.println("synInstance ended " + this.toString());
}
// 锁对象
private synchronized void instance2() {
SleepTools.second(3);
System.out.println("synInstance2 is going..." + this.toString());
SleepTools.second(3);
System.out.println("synInstance2 ended " + this.toString());
}
- SleepTools
import java.util.concurrent.TimeUnit;
/**
*
* 类说明:线程休眠辅助工具类
*/
public class SleepTools {
/**
* 按秒休眠
* @param seconds 秒数
*/
public static final void second(int seconds) {
try {
TimeUnit.SECONDS.sleep(seconds);
} catch (InterruptedException e) {
}
}
/**
* 按毫秒数休眠
* @param seconds 毫秒数
*/
public static final void ms(int seconds) {
try {
TimeUnit.MILLISECONDS.sleep(seconds);
} catch (InterruptedException e) {
}
}
}
缺点:
- synchronized是不能中断的
- synchronized没法尝试等10秒后 再去拿下锁
可重入锁
synchronized
- synchronized就是可重入锁
- 可重入锁就是可以递归调用自己,锁可以释放出来
- synchronized如果没有实现可重入,会自己把自己锁死
// 如果是非重入锁🔒 ,就会自己把自己锁死
public synchronized void incr2(){
count++;
incr2();
}
ReentrantLock
// 声明一个显示锁之可重入锁 new 可重入锁
// 非公平锁
private Lock lock = new ReentrantLock();
public void incr(){
// 使用 显示锁 的规范
lock.lock();
try{
count++;
} finally { // 打死都要执行 最后一定会执行
lock.unlock();
}
}
- 公平锁
new ReentrantLock(true);
死锁
package demo2;
/**
* 定义死锁任务
*/
class DieLockThread extends Thread {
/**
* 此变量已经不是共享数据了,因为:
* DieLockThread extends Thread
* new DieLockThread().start();
* new DieLockThread().start();
*
* 所以:Thread-0有自己的flag Thread-1也有自己的flag
*/
private boolean flag;
public DieLockThread(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
int i = 0;
int j = 0;
if (flag) {
while (true) {
// 第一步:CPU随机性切换到:Thread-1 Thread-1正常往下走......
// 第六步:CPU随机性切换到:Thread-1 ,然后判断同步锁 注意:此时的同步锁🔒Lock == 第二把锁🔒, 第二把锁🔒是被Thread-1锁定的,就造成了线程之间不和谐:死锁
synchronized (Lock.LOCK1) // 使用第一把锁🔒
{
synchronized (Lock.LOCK2) // 使用第二把锁🔒
{
System.out.println("一一一一一一一一一一一一" + i++);
// 第二步:CPU随机性切换到:Thread-1 就在此时,CPU执行权没有了, CPU去执行其他线程了
}
}
// 第四步: CPU随机性切换到:Thread-1 Thread-1正常往下走完,并解锁🔓
}
} else {
while(true) {
// 第三步:CPU随机切换到:Thread-0,然后判断同步锁 注意:此时的同步锁🔒Lock == 第二把锁🔒, 第二把锁🔒是被Thread-1锁定的,就造成了线程之间不和谐:死锁
synchronized (Lock.LOCK2) // 使用第二把锁🔒
{
synchronized (Lock.LOCK1) // 使用第一把锁🔒
{
// 第五步:CPU随机性切换到:Thread-0 就在此时,CPU执行权没有了, CPU去执行其他线程了
System.out.println("二二二二二二二二二二二二" + j++);
}
}
}
}
}
}
/**
* 定义两把锁🔒
*/
class Lock {
public final static Object LOCK1 = new Object();
public final static Object LOCK2 = new Object();
}
public class DieLockDemo {
public static void main(String[] args) {
// 多线程
new DieLockThread(true).start();
new DieLockThread(false).start();
}
}
生产者消费者
- 生产一个,消费一个
/**
* 描述资源
*/
class Res3 {
/**
* name 是共享数据,被Thread-0 Thread-1公用使用
*/
private String name;
/**
* id 是共享数据,被Thread-0 Thread-1公用使用
*/
private int id;
/**
* flag 是共享数据,被Thread-0 Thread-1公用使用
*/
private boolean flag; // 定义标记 默认第一次为false
/**
* 对操作共享数据的地方加入同步锁的方式来解决安全问题
* public synchronized(this) void put(String name) {
*/
public synchronized void put(String name) {
/**
* 生产之前判断标记
*/
if (!flag) {
// 开始生产
id += 1;
// this.name = name + " 商品编号:" + id;
System.out.println(Thread.currentThread().getName() + "生产者 生产了:" + this.id);
// 生产完毕
/**
* 修改标记
*/
flag = true;
/**
* 唤醒 wait(); 冻结的线程,如果没有就是空唤醒,Java是支持的
* 生产好了,消费者 快来买 ,唤醒
*/
notify(); // 注意:⚠️ wait(); notify(); 这些必须要有同步锁包裹着
/**
* 当前自己线程 冻结,释放CPU执行资格,释放CPU执行权,CPU就会去执行其他线程了
*/
try {
// 生产好一个,我就去睡觉了
wait(); // 注意:⚠️ wait(); notify(); 这些必须要有同步锁包裹着
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 对操作共享数据的地方加入同步锁的方式来解决安全问题
* public synchronized(this) void put(String name) {
*/
public synchronized void out() {
/**
* 消费之前判断标记
*/
if (flag) {
// 开始消费
System.out.println(Thread.currentThread().getName() + ">>>>>>>>>>>>>>>>>>>>>>>>>>>>> 消费者 消费了:" + this.id);
// 消费完毕
/**
* 修改标记
*/
flag = false;
/**
* 唤醒 wait(); 冻结的线程,如果没有就是空唤醒,Java是支持的
* 唤醒生产者,你可以再生产一个面包了
*/
notify(); // 注意:⚠️ wait(); notify(); 这些必须要有同步锁包裹着
/**
* 当前自己线程 冻结,释放CPU执行资格,释放CPU执行权,CPU就会去执行其他线程了
*/
try {
// 顾客又睡觉了
wait(); // 注意:⚠️ wait(); notify(); 这些必须要有同步锁包裹着
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 描述生产者任务
*/
class ProduceRunnable3 implements Runnable {
/**
* 此变量已经不是共享数据了,因为:
* new Thread(produceRunnable).start();
* new Thread(consumeRunnable).start();
*
* 所以:Thread-0有自己的res Thread-1也有自己的res
*/
private Res3 res;
ProduceRunnable3(Res3 res) {
this.res = res;
}
/**
* 执行线程任务
*/
@Override
public void run() {
for (int i = 0; i < 5; i++) {
res.put("面包🍞");
}
}
}
/**
* 描述消费者任务
*/
class ConsumeRunnable3 implements Runnable {
/**
* 此变量已经不是共享数据了,因为:
* new Thread(produceRunnable).start();
* new Thread(consumeRunnable).start();
*
* 所以:Thread-0有自己的res Thread-1也有自己的res
*/
private Res3 res;
ConsumeRunnable3(Res3 res) {
this.res = res;
}
/**
* 执行线程任务
*/
@Override
public void run() {
for (int i = 0; i < 5; i++) {
res.out();
}
}
}
/**
* wait(); 等待/冻结 :可以将线程冻结,释放CPU执行资格,释放CPU执行权,并把此线程临时存储到线程池
* notify(); 唤醒线程池里面 任意一个线程,没有顺序;
* notifyAll(); 唤醒线程池里面,全部的线程;
* 注意:⚠️ wait(); notify(); 这些必须要有同步锁包裹着
*
*/
public class ThreadCommunicationDemo3 {
public static void main(String[] args) {
// 创建资源对象
Res3 res = new Res3();
// 创建生产者任务
ProduceRunnable3 produceRunnable = new ProduceRunnable3(res);
// 创建消费者任务
ConsumeRunnable3 consumeRunnable = new ConsumeRunnable3(res);
// 启动生产者任务
new Thread(produceRunnable).start();
// 启动消费者任务
new Thread(consumeRunnable).start();
}
}
打印日志:
Thread-0生产者 生产了:1
Thread-1>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 消费者 消费了:1
Thread-0生产者 生产了:2
Thread-1>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 消费者 消费了:2
Thread-0生产者 生产了:3
Thread-1>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 消费者 消费了:3
Thread-0生产者 生产了:4
Thread-1>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 消费者 消费了:4
Thread-0生产者 生产了:5
Thread-1>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 消费者 消费了:5
等待唤醒机制:
wait();
等待/冻结:可以将线程冻结,释放CPU执行资格,释放CPU执行权,并把此线程临时存储到线程池notify();
唤醒线程池里面 任意一个线程,没有顺序;notifyAll();
唤醒线程池里面,全部的线程;
wait
或者 notify
为什么需要 syn
包裹,否则报错?
首先 wait 和 notify 需要的是同一把锁
wait
内部操作:
1.获取对象锁
2.检测条件等等内部逻辑
wait
在syn(this)
已经被锁住了,notify
就无法获取锁了。wait()
后,持有锁会被释放
notify
内部操作:
1.获取对象锁
2。然后
syn(this)
notify
或者 notifyAll
使用等待唤醒注意事项:
- 1.使用来
wait();
冻结,就必须要被其他方notify();
,否则一直wait()
冻结,所以等待与唤醒是配合一起使用的; - 2.
wait(); notify(); notifyAll();
等方法必须被synchronized(锁) {包裹起来},意思就是:wait(); notify(); notifyAll();
必须要有同步锁,否则毫无意义; - 3.
wait(); notify(); notifyAll();
等方法必须持有同一把锁,因为:lockA.wait();
只能使用 lockA.notify(); /lockA.notifyAll();
, 它们是使用同一把锁的;
读写锁
- 读写锁比sync效率更高。sync锁住后其他线程是无法进入的。但是读取操作是不会引发安全问题的,读写锁即使锁住了,还是允许线程进入读取数据,所以时间更短
/**
* 类说明:商品的实体类
*/
public class GoodsInfo {
private final String name;
private double totalMoney;//总销售额
private int storeNumber;//库存数
public GoodsInfo(String name, int totalMoney, int storeNumber) {
this.name = name;
this.totalMoney = totalMoney;
this.storeNumber = storeNumber;
}
public double getTotalMoney() {
return totalMoney;
}
public int getStoreNumber() {
return storeNumber;
}
public void changeNumber(int sellNumber){
this.totalMoney += sellNumber*25;
this.storeNumber -= sellNumber;
}
}
/**
* 类说明:用内置锁来实现商品服务接口
*/
public class UseSyn implements GoodsService {
private GoodsInfo goodsInfo;
public UseSyn(GoodsInfo goodsInfo) {
this.goodsInfo = goodsInfo;
}
// 读取
@Override
public synchronized GoodsInfo getNum() {
SleepTools.ms(5);
return this.goodsInfo;
}
// 读取
@Override
public synchronized void setNum(int number) {
SleepTools.ms(5);
goodsInfo.changeNumber(number);
}
}
/**
* 类说明: 这里采用 【读写锁】
*/
public class UseRwLock implements GoodsService{
private GoodsInfo goodsInfo;
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock getLock = lock.readLock(); // 读锁
private final Lock setLock = lock.writeLock(); // 写锁
public UseRwLock(GoodsInfo goodsInfo) {
this.goodsInfo = goodsInfo;
}
// 读取
@Override
public GoodsInfo getNum() {
getLock.lock();
try {
SleepTools.ms(5);
return this.goodsInfo;
}finally {
getLock.unlock();
}
}
// 写入
@Override
public void setNum(int number) {
setLock.lock();
try {
SleepTools.ms(5);
goodsInfo.changeNumber(number);
}finally {
setLock.unlock();
}
}
}
ThreadLocal
- 给每个线程进行了隔离
没有ThreadLocal的情况演示:
/**
* 类说明:没有ThreadLocal的情况演示
*
* 数字会乱套 例如:341 134 124 等等
*/
public class NoThreadLocal {
// 静态的
static Integer count = new Integer(1);
/**
* 运行3个线程
*/
public void StartThreadArray() {
Thread[] runs = new Thread[3];
for (int i = 0; i < runs.length; i++) {
runs[i] = new Thread(new TestTask(i));
}
for (int i = 0; i < runs.length; i++) {
runs[i].start(); // 启动三个线程
}
}
/**
* 类说明:
*/
public static class TestTask implements Runnable {
int id;
public TestTask(int id) {
this.id = id;
}
public void run() {
System.out.println(Thread.currentThread().getName() + ":start");
count = count + id; // 123 231
System.out.println(Thread.currentThread().getName() + ":" + count);
}
}
public static void main(String[] args) {
NoThreadLocal test = new NoThreadLocal();
test.StartThreadArray();
// 每个线程没法独有自己的一份数据
}
}
演示ThreadLocal的使用:
/**
* 说明:演示ThreadLocal的使用
*
* 数字不会乱套 例如:123 132 123 213 等等 始终在123范围中
*/
public class UseThreadLocal {
static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 1;
}
};
/**
* 运行3个线程
*/
public void StartThreadArray() {
Thread[] runs = new Thread[3];
for (int i = 0; i < runs.length; i++) {
runs[i] = new Thread(new TestThread(i));
}
for (int i = 0; i < runs.length; i++) {
runs[i].start();
}
}
/**
* 类说明:测试线程,线程的工作是将ThreadLocal变量的值变化,并写回,
* 看看线程之间是否会互相影响
*/
public static class TestThread implements Runnable {
int id;
public TestThread(int id) {
this.id = id;
}
public void run() {
System.out.println(Thread.currentThread().getName() + ":start");
// 如果使用了 ThreadLocal 会单独Copy一份 到 当前线程 例如 Thread-0
Integer s = threadLocal.get();
s = s + id;
threadLocal.set(s); // 这里所修改的内容 只对 Thread-0 有效果, 和 Thread-1 没有半毛钱关系
System.out.println(Thread.currentThread().getName() + " :"
+ threadLocal.get());
//threadLocal.remove();
}
}
public static void main(String[] args) {
UseThreadLocal test = new UseThreadLocal();
test.StartThreadArray();
}
}