当前位置: 首页 > java >正文

Java基础(多线程2)

五、线程同步(5.1两种实现 5.2一种实现)

5.1、 synchronized对象及方法

有的时候需要几行代码作为整体一起执行,实现线程同步,需要用到synchronized代码块

相关源码及其注释:

package day25;public class Window implements Runnable {private Integer no = 100;//表示票的编号 1~100@Overridepublic void run() {while(true) {//this即Window对象,唯一的对象也被称之为锁,也可以写no,必须保证同一时刻只有一个线程拿到它/* synchronized (this) {if(no <= 0) {break;}//必须让下面两条语句一块运行才能同步System.out.println(Thread.currentThread().getName() + "销售第" + no + "张票");no--;}*/if(no <= 0){break;}sale();try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}}}public synchronized void sale(){if(no <= 0) {return;}System.out.println(Thread.currentThread().getName() + "销售第" + no + "张票");no--;}
}
package day25;/*
* 1、三个窗口销售同一堆票
* 2、每个编号的票只能被销售一次
* 3、票的编号的范围是1~100
*
* 线程同步机制:
* 1、synonronized代码块
*   synchronized(对象) {
*
*   }
*   1) synchronized代码块()中的对象必须是唯一的对象
*   2)唯一的对象 —— 锁,同一时刻只能有一个线程占据这个唯一的对象
*   3)哪个线程占据了这个对象,哪个线程就可以执行synchronized代码块中的代码,
*      此时其他线程就不能执行synchronized代码块中的代码,陷入阻塞状态
*   4)占据这个唯一的对象的线程执行完synchronized代码块后就会释放这个锁
* 2、synonronized方法 即Window里的sale方法
*
*
* */
public class MyTest {public static void main(String[] args) {Window w = new Window();Thread window1 = new Thread(w);Thread window2 = new Thread(w);Thread window3 = new Thread(w);window1.setName("窗口1");window2.setName("窗口2");window3.setName("窗口3");window1.start();window2.start();window3.start();}
}

5.2、Lock锁

从JDK5.0开始,Java提供了更强大的线程同步机制——通过显示定义同步锁对象来实现同步。同步锁使用Lock对象充当。

Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。

常用ReentrantLock来加锁实现锁

package day25;import java.util.concurrent.locks.ReentrantLock;public class Window implements Runnable {private Integer no = 100;//表示票的编号 1~100private ReentrantLock lock = new ReentrantLock();@Overridepublic void run() {while(true) {//this即Window对象,唯一的对象也被称之为锁,也可以写no,必须保证同一时刻只有一个线程拿到它/* synchronized (this) {if(no <= 0) {break;}//必须让下面两条语句一块运行才能同步System.out.println(Thread.currentThread().getName() + "销售第" + no + "张票");no--;}*//* if(no <= 0){break;}sale();*/try{//加锁lock.lock();if(no <= 0) {break;}System.out.println(Thread.currentThread().getName() + "销售第" + no + "张票");no--;}finally {lock.unlock();}try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}}}public synchronized void sale(){if(no <= 0) {return;}System.out.println(Thread.currentThread().getName() + "销售第" + no + "张票");no--;}
}

5.3、线程安全的单例模式——懒汉式

package day25;//懒汉式
public class SingleObject {private static SingleObject obj;private SingleObject() {}//加了synchronized关键字就是唯一和排他的了,整体运行完,这样就只会创建一次对象,单例synchronized public static SingleObject getObject() {if (obj == null) {obj = new SingleObject();}return obj;}
}
package day25;public class MyTest2 {private static SingleObject obj1;private static SingleObject obj2;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {obj1 = SingleObject.getObject();}});Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {obj2 = SingleObject.getObject();}});t1.start();t2.start();Thread.sleep(1000);System.out.println(obj1 == obj2);}
}

5.4、死锁

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。

出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。

相关源码现象展示,运行时会卡住:

package day27;
//发生嵌套同步了
public class A extends Thread{@Overridepublic void run() {while(true){synchronized (Lock.m){synchronized (Lock.n){System.out.println("A............");}}}}
}
package day27;public class B extends Thread{@Overridepublic void run() {while(true){synchronized (Lock.n){synchronized (Lock.m){System.out.println("B............");}}}}
}
package day27;public class Lock {public static Object m = new Object();public static Object n = new Object();
}
package day27;public class MyTest {public static void main(String[] args) {A a = new A();B b = new B();a.start();b.start();}
}

解决方法:

专门的算法、原则

尽量减少同步资源的定义

尽量避免嵌套同步

六、线程通信

相关方法:

wait():令当前线程挂起并放弃CPU,同步资源并等待,使别的线程可访问并修改共享资源,而当前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行。

notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待;

notifyAll():唤醒正在排队等待资源的所有线程结束等待。

以上三个方法只有在synchronized方法或代码块中才能使用。

6.1、打印数字:

package day28;//1~20
/*
* wait()
* notify()
* notifyAll()
*
* */
public class PrintNum implements Runnable {private Integer i = 1;@Overridepublic void run() {while (true) {synchronized (Object.class){if(i > 20){break;}Object.class.notifyAll();//唤醒其他线程,通知别人我要睡了,wait方法System.out.println(Thread.currentThread().getName() + ": " + i);i++;try {Object.class.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}try {Thread.sleep(5);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
package day28;public class MyTest {public static void main(String[] args) {PrintNum printNum = new PrintNum();Thread th1 = new Thread(printNum);Thread th2 = new Thread(printNum);th1.setName("线程1");th2.setName("线程2");th1.start();th2.start();}
}

6.2、生产者消费者问题

此问题也称有限缓冲问题,是一个多线程同步问题的经典案例。

这里需要让生产者在缓冲区满时休眠(要么干脆放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,在唤醒消费者。

见源码及其注释:

package day28;import java.util.ArrayList;
import java.util.Objects;public class Store {public static ArrayList<Object> list= new ArrayList<>(100);public static final Integer MAX_NUM = 100;}
package day28;/*
* 生产者
* */
public class Producer extends Thread {@Overridepublic void run() {while (true) {synchronized (Store.list) {if (Store.list.size() < Store.MAX_NUM){Store.list.notifyAll();//生产Object item = new Object();Store.list.add(item);System.out.println(Thread.currentThread().getName() + " - 生产商品,仓库目前数量:" + Store.list.size());}else{//停止生产System.out.println(Thread.currentThread().getName() + " - 仓库已满,停止生产" );try {Store.list.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}}}}
}
package day28;/*
* 消费者
* */
public class Consumer extends Thread {@Overridepublic void run() {while (true) {synchronized (Store.list) {if (Store.list.size() > 0){Store.list.notifyAll();//消费Store.list.remove(0);System.out.println(Thread.currentThread().getName() + " - 消费商品,仓库目前数量:" + Store.list.size());}else{//停止消费System.out.println(Thread.currentThread().getName() + " - 仓库已空,停止消费" );try {Store.list.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}}}}
}
package day28;public class MyTest2 {public static void main(String[] args) {Producer p = new Producer();Consumer c = new Consumer();p.start();c.start();}
}

6.3、wait和sleep的区别

共同点:

wait(),wait(long)和sleep(long)的效果都是让当前线程暂时放弃CPU的使用权,进入阻塞状态。

不同点:

方法归属不同:

sleep(long)是Thread的静态方法

而wait(),wait(long)都是Object的成员方法,每个对象都有

醒来时机不同:

执行sleep(long)和wait(long)的线程都会在等待相应毫秒后醒来

wait(long)和wait()还可以被notify唤醒,wait()如果不唤醒就一直等下去。

锁特性不同(重点)

wait方法的调用必须先获取wait对象的锁,而sleep则无此限制

wait方法执行后会释放对象锁,允许其他线程获得该对象锁(我放弃CPU,但你们还可以用)

而sleep方法如果在synchronized代码块中执行,并不会释放对象锁(我放弃CPU,但你们也不能用)

七、JDK5.0新增线程创建方式

7.1、实现Callable接口

与Runnable相比,Callable功能更强大些:

相比run()方法,可以有返回值;

方法可以抛出异常;

支持泛型的返回值;

需要借助FutureTask类,比如获取返回结果。

package day29;import java.util.concurrent.Callable;/*
* 实现Callable接口实现线程
* 1、实现Callable接口 - 泛型是返回值的类型
* 2、重写call方法
* */
public class SumThread implements Callable<Integer> {@Overridepublic Integer call() throws Exception {Integer sum = 0;for (int i = 0; i <=100 ; i++) {sum+=i;}return sum;}
}
package day29;import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;public class MyTest {public static void main(String[] args)  {SumThread sumThread = new SumThread();FutureTask<Integer> futureTask = new FutureTask<Integer>(sumThread);Thread thread = new Thread(futureTask);thread.start();try {Integer sum = futureTask.get();System.out.println(sum);} catch (InterruptedException e) {throw new RuntimeException(e);} catch (ExecutionException e) {throw new RuntimeException(e);}}
}

7.2、面试题:

Runnable和Callable的区别?

1)Runnable接口run方法没有返回值,Callable接口call方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果;

2)Callable接口支持返回执行结果,有返回值,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞;

3)Callable接口的call()方法允许抛出异常,而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛。

7.2、线程池_创建线程,使用过程

池化技术:

线程池

连接池 - 网络连接

内存池

目的:减少对性能消耗比较大的操作(创建),提升系统的工作效率

如何解决:提前创建好,直接拿来用

相关源码:

package day30;public class Num1Thread implements Runnable{@Overridepublic void run() {for (int i = 0; i <= 100; i++) {if (i % 2 == 0) {System.out.println(Thread.currentThread().getName() + " " + i);}}}
}
package day30;public class Num2Thread implements Runnable{@Overridepublic void run() {for (int i = 0; i <= 100; i++) {if (i % 2 == 0) {System.out.println(Thread.currentThread().getName() + " " + i);}}}
}
package day30;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class MyTest1 {public static void main(String[] args) {//创建自定义线程类的对象Num1Thread num1 = new Num1Thread();Num2Thread num2 = new Num2Thread();//创建有固定数量线程的线程池ExecutorService pool = Executors.newFixedThreadPool(100);//通过线程池执行线程pool.execute(num1);pool.execute(num2);//关闭线程池pool.shutdown();}
}

7.3、Collections_创建线程安全的集合

http://www.xdnf.cn/news/6354.html

相关文章:

  • 构建全栈 Web 应用的新选择:NextPy 技术详解与实战指南
  • 降本增效双突破:Profinet转Modbus TCP助力包布机产能与稳定性双提升
  • 初识Linux · IP分片
  • Bismark
  • 第三章:系统命令
  • 【技术原理】Linux 文件时间属性详解:Access、Modify、Change 的区别与联系
  • 论文学习_Directed Greybox Fuzzing
  • 练习小项目2:今日幸运颜色生成器
  • Storyboarder - 快速绘制可视化故事工具
  • nginx负载均衡及keepalive高可用
  • Python60日基础学习打卡D26
  • 学习ai课程大纲
  • 5.19 BGP实验
  • digitalworld.local: DEVELOPMENT靶场
  • 使用GmSSL v3.1.1实现SM2证书认证
  • Uniapp 安卓实现讯飞语音听写(复制即用)
  • WEB安全--Java安全--LazyMap_CC1利用链
  • RPC框架源码分析学习(二)
  • Spring+LangChain4j小智医疗项目
  • ultalytics代码中模型接收多层输入的处理
  • [训练和优化] 3. 模型优化
  • 学习小组实验1
  • 和为target问题汇总
  • Spark SQL 之 Analyzer
  • Ubuntu Linux bash的相关默认配置文件内容 .profile .bashrc, /etc/profile, /etc/bash.bashrc等
  • 2025年5月-信息系统项目管理师高级-软考高项-成本计算题
  • 为什么doris是实时的?
  • 一个基于 Spring Boot 的实现,用于代理百度 AI 的 OCR 接口
  • 06Spring—AOP @Around环绕通知的进阶思考
  • 【愚公系列】《Manus极简入门》040-科技与组织升级顾问:“项目掌舵人”