欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 维修 > Java-多线程-锁

Java-多线程-锁

2025/2/25 1:28:37 来源:https://blog.csdn.net/weixin_37165769/article/details/142553034  浏览:    关键词:Java-多线程-锁

synchronized 内置锁

类锁

// 使用类锁的线程
private static class SynClass extends Thread {@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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;}/*** 执行线程任务*/@Overridepublic 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;}/*** 执行线程任务*/@Overridepublic 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.检测条件等等内部逻辑
waitsyn(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;}// 读取@Overridepublic synchronized GoodsInfo getNum() {SleepTools.ms(5);return this.goodsInfo;}// 读取@Overridepublic 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;}// 读取@Overridepublic GoodsInfo getNum() {getLock.lock();try {SleepTools.ms(5);return this.goodsInfo;}finally {getLock.unlock();}}// 写入@Overridepublic 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  231System.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>() {@Overrideprotected 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-0Integer 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();}
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词