*/
//1.创建一个继承Thread类的子类
class MyThread extends Thread {
//2.重写run方法
@Override
public void run() {
for (int i = 0; i < 101; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + “:” + i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//3.创建Thread类的子类的对象
MyThread t1 = new MyThread();
//4.通过此对象调用start()
t1.start();//导致此线程开始执行; Java虚拟机调用此线程的run方法。
//问题一:我们不能通过调用run的方法启动线程
// myThread.run(); //不会开启新的线程
//问题二:再启动一个线程,遍历100以内的偶数,不可以让已经start()的线程去执行,会报illegalThread异常
//我们需要重新创建一个线程的对象,也就是说重新创建一个Thread子类的对象
MyThread t2 = new MyThread();
t2.start();
//如下操作仍是在main线程中执行
for (int i = 0; i < 101; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + “” + “**hello”);
}
}
}
}
实现流传如下:
注意点
1.如果自己手动调用run()方法, 那么就只是普通方法,没有启动多线程模式。
2.run()方法 由JVM调,什么时候调用,执行的过程控制都有操作系统的CPU调度决定。
3.想要启动多线程,必须调用start方法。
4.一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出以上的异常“lllgalThreadStateException”
方式二:实现Runnable接口
1)定义Runnable接口的实现类
2)重现接口的run方法
3)创建子类的对象
4)将子类对象传入Thread类的构造器中
5)通过Thread调用start方法开启线程,调用Runnable实现类中的run方法
package com.example.www.d4;
/**
-
@ClassName ThreadTest1
-
@Description 创建多线程的方式二:实现Runnable接口
-
1.创建一个实现了Runnable接口的类
-
2.实现类去实现Runnable中的抽象方法:run()
-
3.创建实现类的对象
-
4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
-
5.调用start()启动当前线程,调用当前线程的run()
-
@Author Jack
-
@Date 2021/11/22 7:27
-
@Version 1.0
*/
//1.创建实现类
class MyThread3 implements Runnable{
//2.实现Runnable中的抽象方法
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+“:”+i);
}
}
}
public class ThreadTest1 {
public static void main(String[] args) {
//3.创建实现类的对象
MyThread3 t3 = new MyThread3();
//4.将此对象作为参数传递到Thread的构造方法中
Thread t = new Thread(t3);
//5.通过Thread类的对象调用start 启动线程,调用当前线程的run–>调用了Runnable类型的target
t.start();
}
}
public class Thread extends Object implements Runnable
区别
➢继承Thread:线程代码存放Thread子类run方法中。
➢实现Runnable:线程代码存在接口的实现类的run方法。
实现方式的好处
➢避免了单继承的局限性
➢多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。
void start():启动线程,并执行对象的run()方法
run():线程在被调度时执行的操作
String getName():返回线程的名称
void setName(String name):设置该线程名称
static Thread currentThread():返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable实现类
static void yield(): 线程让步
暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
若队列中没有同优先级的线程,忽略此方法
**join()😗*当某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到join()方法加入的join线程执行完为止
static void sleep(long millis): (指定时间:毫秒)
令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。
抛出InterruptedException异常
stop():强制线程生命期结束,不推荐使用
boolean isAlive():返回boolean,判断线程是否还活着
Java的调度方法
同优先级线程组成先进先出队列(先到先服务),使用时间片策略
对高优先级,使用优先调度的抢占式策略
线程的优先级等级
MAX_ PRIORITY: 10
MIN_ PRIORITY: 1
NORM_ PRIORITY: 5
涉及的方法
getPriority() :返回线程优先值
setPriority(int newPriority) :改变线程的优先级
说明
线程创建时继承父线程的优先级
低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用
package com.example.www.d4;
/**
-
@ClassName ThreadMethodTest
-
@Description Thread的常用方法
-
1.start():启动当前线程,调用当前线程的run()
-
2.run():通常需要重写Thread类中的此方法,将创建线程要执行的操作写在里面
-
3.currentThread:静态方法,返回当前代码的线程
-
4.getName()获取当前线程的名词
-
5.setName设置当前线程的名字
-
6.yield():释放当前cpu执行权
-
7.join():在线程a中调用线程b的join,此时线程a进入阻塞状态,直到线程b执行完毕,线程a才结束阻塞状态
-
8.stop()已过时,当执行此方法时,强制结束当前线程
-
9.sleep(long milltime):让当前线程睡眠指定的millitime毫秒,在指定时间内线程属于阻塞状态
-
10.isAlive()判断线程是否存活
-
-
-
线程的优先级
-
-
MAX_PRIORITY 10
-
MIN_PRIORITY 1
-
NORM_PRIORITRY 5
-
2.如何获取和设置当前线程的优先级
-
getPriority():获取线程的优先级
-
setPriority():设置线程的优先级
-
@Author Jack
-
@Date 2021/11/21 22:14
-
@Version 1.0
*/
public class ThreadMethodTest {
public static void main(String[] args) {
MyThread2 t1 = new MyThread2();
t1.setName(“线程1”);
//设置分线程的优先级
t1.setPriority(Thread.MAX_PRIORITY);
t1.start();
Thread.currentThread().setName(“主线程”);
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
for (int i = 1; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + “:” + Thread.currentThread().getPriority() + “:” + i);
// if (i % 20 ==0){
// try {
// t1.join();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
}
System.out.println(t1.isAlive());
}
}
class MyThread2 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
// try {
// sleep(20);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println(Thread.currentThread().getName() + “:” + getPriority() + “:” + i);
}
// if (i == 20) {
// yield();//释放当前cpu的执行权
//线程1:80
//主线程:75
//…当线程1释放当前cpu执行权的时候,其他线程会进行抢夺
// }
}
}
}
Java中的线程分为两类:一种是守护线程,一种是用户线程。
它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。
守护线程是用来服务用户线程的,通过在start()方法前调用thread.setDaemon(true)可以把一个用户线程变成一个守护线程。
Java垃圾回收就是一个典型的守护线程。
若JVM中都是守护线程,当前JVM将退出。
形象理解:兔死狗烹,鸟尽弓藏
飞鸟没有了,弓也就藏起来不用了;兔子捕杀完了,猎狗无用也就烹煮吃了。
NEW
Runnable
Blocked
Waiting
Timed_waiting
Terminated
要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:
➢新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建
状态
➢就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已
具备了运行的条件,只是没分配到CPU资源
➢运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线
程的操作和功能
➢阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中
止自己的执行,进入阻塞状态
➢死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
======================================================================
多个线程共同操作共享数据时,会造成操作的不完整性,会破坏数据
当多个线程在操作共享数据时,一个线程只执行了一部分语句,另一个线程也参与进来。会导致共享数据的错误
代码如下:
package com.example.www.d4;
/**
-
@ClassName WindowTest
-
@Description 卖票
-
例子:创建三个窗口卖票,总票数为100张
-
现在存在线程的安全问题
-
@Author Jack
-
@Date 2021/11/21 23:33
-
@Version 1.0
*/
public class WindowTest {
public static void main(String[] args) {
Window w1 = new Window();
Window w2 = new Window();
Window w3 = new Window();
w1.setName(“窗口一”);
w2.setName(“窗口二”);
w3.setName(“窗口三”);
w1.start();
w2.start();
w3.start();
}
}
class Window extends Thread {
private static int ticket = 100;
// private static Object obj =new Object();
@Override
public void run() {
while (true) {
// synchronized (Window.class){
if (ticket > 0) {
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + “:卖票,票号为” + ticket);
ticket–;
}else {
break;
}
// }
}
}
}
输出结果为:
…
…
窗口三:卖票,票号为13
窗口二:卖票,票号为13
窗口一:卖票,票号为13
窗口一:卖票,票号为10
窗口三:卖票,票号为10
窗口二:卖票,票号为8
窗口三:卖票,票号为7
窗口一:卖票,票号为6
窗口二:卖票,票号为5
窗口三:卖票,票号为4
窗口二:卖票,票号为4
窗口一:卖票,票号为4
窗口三:卖票,票号为1
窗口二:卖票,票号为0
窗口一:卖票,票号为-1
解决方法:
当一个线程操作共享数据时,等它操作完了其他线程才能继续操作
Java对这个问题提供了专业的解决方法:同步机制
理解同步和异步
同步和异步关注的是消息通信机制
同步,就是调用某个东西是,调用方得等待这个调用返回结果才能继续往后执行。异步,和同步相反 调用方不会等待结果,而是在调用发出后调用者可用继续执行后续操作,哪个调用先执行完先处理哪个
同步异步 , 举个例子来说,一家餐厅来了5个客人,同步的意思就是说,来第一个点菜,点了个鱼,好, 厨师去捉鱼杀鱼,过了半小时鱼好了给第一位客人,开始下位一位客人,就这样一个一个来,按顺序来
相同, 异步呢,异步的意思就是来第一位客人,点什么,点鱼,给它一个牌子,让他去一边等吧,下一位客人接着点菜,点完接着点让厨师做去吧,哪个的菜先好就先端出来,
同步的优点是:同步是按照顺序一个一个来,不会乱掉,更不会出现上面代码没有执行完就执行下面的代码, 缺点:是解析的速度没有异步的快;
异步的优点是:异步是接取一个任务,直接给后台,在接下一个任务,一直一直这样,谁的先读取完先执行谁的, 缺点:没有顺序 ,谁先读取完先执行谁的 ,会出现上面的代码还没出来下面的就已经出来了,会报错;
1.同步代码块:
synchronized (对象){
//需要被同步的代码;
2.synchronized还可 以放在方法声明中,表示整个方法为同步方法。
例如:
public synchronized void show (String name){
}
实现如下
package com.example.www.d4;
/**
-
@ClassName WindowTest
-
@Description 卖票
-
例子:创建三个窗口卖票,总票数为100张
-
现在存在线程的安全问题
-
@Author Jack
-
@Date 2021/11/21 23:33
-
@Version 1.0
*/
public class WindowTest {
public static void main(String[] args) {
Window w1 = new Window();
Window w2 = new Window();
Window w3 = new Window();
w1.setName(“窗口一”);
w2.setName(“窗口二”);
w3.setName(“窗口三”);
w1.start();
w2.start();
w3.start();
}
}
class Window extends Thread {
private static int ticket = 100;
// private static Object obj =new Object();
@Override
public void run() {
while (true) {
synchronized (Window.class){
if (ticket > 0) {
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + “:卖票,票号为” + ticket);
ticket–;
}else {
break;
}
}
}
}
}
输出结果如下:
…
…
窗口三:卖票,票号为20
窗口三:卖票,票号为19
窗口二:卖票,票号为18
窗口二:卖票,票号为17
窗口三:卖票,票号为16
窗口三:卖票,票号为15
窗口三:卖票,票号为14
窗口三:卖票,票号为13
窗口三:卖票,票号为12
窗口三:卖票,票号为11
窗口三:卖票,票号为10
窗口三:卖票,票号为9
窗口一:卖票,票号为8
窗口三:卖票,票号为7
窗口三:卖票,票号为6
窗口三:卖票,票号为5
窗口三:卖票,票号为4
窗口三:卖票,票号为3
窗口二:卖票,票号为2
窗口三:卖票,票号为1
在《Thinking in Java》 中,是这么说的:对于并发工作,你需要某种方式来防止两个任务访问相同的资源(其实就是共享资源竞争)。防止这种冲突的方法就是当资源被一个任务使用时,在其上加锁。第一个访问某项资源的任务必须锁定这项资源,使其他任务在其被解锁之前,就无法访问它了,而在其被解锁之时,另一个任务就可以锁定并使用它了。
synchronized的锁是什么 ?
任意对象都可以作为同步锁。所有对象都自动含有单一的锁(监视器)。
同步方法的锁:静态方法(类名.class) 、非静态方法(this )
同步代码块:自己指定,很多时候也是指定为this或类名.class
注意
必须确保使用同一个资源的多个线程共用一把锁,这个非常重要,否则就无法保证共享资源的安全
一个线程类中的所有静态方法共用同一把锁(类名.class) ,所有非静态方法共用同一把锁(this) ,同步代码块(指定需谨慎)
同步的范围
1、如何找问题,即代码是否存在线程安全? (非常 重要)
(1)明确哪些代码是多线程运行的代码
(2)明确多个线程是否有共享数据
(3)明确多线程运行代码中是否有多条语句操作共享数据
2、如何解决呢?
(非常重要)
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。
即所有操作共享数据的这些语句都要放在同步范围中
3、切记
范围太小:没锁住所有有安全问题的代码
范围太大:没发挥多线程的功能。
释放锁的操作
当前线程的同步方法、同步代码块执行结束。
当前线程在同步代码块、同步方法中遇到break、return终 止了该代码块、
该方法的继续执行。
当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导
致异常结束。
当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线
程暂停,并释放锁。(后面会提到wait方法)
不会释放锁的操作
线程执行同步代码块或同步方法时,程序调用Thread. sleep()、Thread.yield()方法暂停当前线程的执行
线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)。
应尽量避免使用suspend()和resume()来控制线程
线程的死锁问题
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
如下
package com.caq.www;
import static java.lang.Thread.sleep;
/**
-
@ClassName ThreadTest
-
@Description 演示线程的死锁问题
-
1.死锁的理解:
-
不同的线程分别占用对方需要的同步资源不放弃,
-
都在等待对方放弃自己需要的公共资源,这样就形成了线程的死锁
-
2.说明:
-
1)出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
-
2)我们使用同步时,要避免出现死锁
-
@Author Jack
-
@Date 2021/11/23 11:13
-
@Version 1.0
*/
public class ThreadTest {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
//通过匿名内部类继承Thread类的方式开启一个线程
new Thread(new Runnable() {
@Override
public void run() {
synchronized(s1){
s1.append(“a”);
s2.append(“1”);
//线程拿着s1锁,等待s2锁
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2){
s1.append(“b”);
s2.append(“2”);
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
//通过实现Runnable接口的方式开启一个新的线程
new Thread(new Runnable() {
@Override
public void run() {
synchronized(s2){
s1.append(“c”);
s2.append(“3”);
//线程拿着s2锁,等待s1锁
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1){
s1.append(“d”);
s2.append(“4”);
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
解决方法
专门的算法、原则
尽量减少同步资源的定义
尽量避免嵌套同步
从JDK 5.0开始,Java提供了更强大的线程同步机制一通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
改进后的实例
package com.caq.www;
import java.util.concurrent.locks.ReentrantLock;
/**
-
@ClassName LockTest
-
@Description 解决线程安全问题方式三:Lock锁 —JDK5.0新增
-
1.面试题:synchronized 与Lock的异同
-
同:二者都可以解决线程安全问题
-
不同:synchronized机制在执行完相应的同步代码以后,自动释放同步监视器
-
lock需要手动的启动同步(lock()),同时结束同步也需要手动执行(unlock())
-
2.面试题:如何创建多线程?
-
1.通过继承Thread类重写run方法。生成子类的对象并调用start方法
-
2.通过实现Runnable接口并把实现类作为参数传递到Thread类的构造器中,创建Thread对象调用start()启动线程
-
3.面试题:如何解决线程安全问题?有几种方式?
-
1.通过synchronized的方式{同步代码块,同步方法}
-
2.通过Lock锁的方式,手动开启同步锁对线程进行同步,并手动释放同步锁结束
-
@Author Jack
-
@Date 2021/11/23 15:57
-
@Version 1.0
*/
class Window implements Runnable{
private int ticket = 100;
//实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock(true);//为true的话是公平的先进先出,默认是false抢占形
@Override
public void run() {
while (true){
try {
//2.调用锁定方法lock
lock.lock();
if (ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+“:售票,票号为:”+ticket);
ticket–;
}else {
break;
}
}finally {
//3.调用解锁方法
lock.unlock();
}
}
}
}
public class LockTest {
public static void main(String[] args) {
Window w = new Window();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName(“窗口一”);
t2.setName(“窗口二”);
t3.setName(“窗口三”);
t1.start();
t2.start();
t3.start();
}
}
注意:如果同步代码有异常,要将unlock()写 入finally语句块
synchronized与Lock的对比
-
Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域自动释放
-
Lock只有代码块锁,synchronized有代码块锁和方法锁
-
使用Lock锁, JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
-
优先使用顺序:
Lock -> 同步代码块(已经进入了方法体,分配了相应资源) -> 同步方法(在方法体之外)
======================================================================
可以理解为交替
wait()与notify()和notifyAll()
wait():令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当前线程排队等候其他线程调用notify()或ntifyll()方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行。
notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待
notifyAll():唤醒正在排队等待资源的所有线程结束等待
这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报java.langllegalMonitorStateException异常。
因为这三个方法必须有锁对象调用,而任意对象都可以作为synchronized的同步锁,因此这三个方法只能在Object类中声明。
package com.caq.java2;
/**
-
@ClassName CommunicatrionTEst
-
@Description 线程通信的例子;使用两个线程打印1-100.线程1,线程2,交替打印
-
涉及到的三个方法:
-
wait():一旦执行此方法,当前线程进入阻塞状态,并释放同步监视器
-
notify():一旦执行此方法,就会唤醒一个wait的线程,如果有多个线程被wait有限释放优先级高的
-
nitifyAll():一旦执行此方法,就会唤醒所有的wait的线程
-
说明:
-
1.wait, notify(),nitifyAll()三个方法必须使用在同步代码块或同步方法中
-
2.wait, notify(),nitifyAll()三个方法调用者必须是同步块或同步方法中的同步监视器
-
3.wait, notify(),nitifyAll()三个方法都是java.lang.Object类中方法
-
否则会出现IllegalMonitorStateException异常
-
面试题:sleep()和wait()方法的异同
-
相同点:一旦执行方法都会使线程进入阻塞状态
-
不同点:1)两方法声明的位置不一致,一个是Thread一个是Object
-
2)调用的要求不一致,sleep()方法可以在任何需要场景下调用,wait()必须使用在同步代码块中或同步方法中
-
3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁
-
@Author Jack
-
@Date 2021/11/23 22:52
-
@Version 1.0
*/
class Number implements Runnable{
private int number = 1;
@Override
public void run() {
while (true){
synchronized (this) {
notify();//当线程1wait后,释放锁。线程2进来调用notify唤醒线程1
if (number < 100){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+“:”+number);
number++;
try {
//使得调用如下wait()方法的线程进入阻塞状态
//进入wait状态会释放锁
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
break;
}
}
}
}
}
public class CommunicationTest {
public static void main(String[] args) {
Number number = new Number();
Thread t1 = new Thread(number);
Thread t2 = new Thread(number);
t1.setName(“线程1”);
t2.setName(“线程2”);
t1.start();
t2.start();
}
}
package com.caq.java2;
/**
-
@ClassName ProductTest
-
@Description 线程通信的应用:经典例题:生产者 消费者问题
-
生产者(Productor)将产品交给店员(Clerk),而消费者(Customer) 从店员处取走产品,
-
店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员
-
会叫生产者停一下, 如果店中有空位放产品了再通知生产者继续生产:如果店中没有产品
-
了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。
-
-
分析:
-
1.是否是多线程问题? 是,生产者线程,消费者线程
-
2.是否有共享数据? 是,店员(或产品)
-
3.如何解决线程的安全问题?同步机制,有三种方法
-
4.是否涉及线程的通信? 是
-
@Author Jack
-
@Date 2021/11/23 23:21
-
@Version 1.0
*/
class Clerk {
private int productCount = 0;
//生产产品
public synchronized void produceProduct() {
if (productCount < 20){
productCount++;
System.out.println(Thread.currentThread().getName()+“:开始生产第”+productCount+“个产品”);
notify();
}else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
//等待
}
}
//消费产品
public synchronized void consumeProduct() {
if (productCount > 0){
System.out.println(Thread.currentThread().getName()+“:开始消费第”+productCount+“个产品”);
productCount–;
notify();
}else {
//等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Producer extends Thread {//生产者
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(getName() + “:开始生产产品…”);
while (true) {
try {
sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.produceProduct();
}
}
}
class Consumer extends Thread {
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

本次面试答案,以及收集到的大厂必问面试题分享:
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
().getName()+“:开始生产第”+productCount+“个产品”);
notify();
}else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
//等待
}
}
//消费产品
public synchronized void consumeProduct() {
if (productCount > 0){
System.out.println(Thread.currentThread().getName()+“:开始消费第”+productCount+“个产品”);
productCount–;
notify();
}else {
//等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Producer extends Thread {//生产者
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(getName() + “:开始生产产品…”);
while (true) {
try {
sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.produceProduct();
}
}
}
class Consumer extends Thread {
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。[外链图片转存中…(img-2drTkGTU-1713678873377)]
[外链图片转存中…(img-VjcL0E7G-1713678873378)]
[外链图片转存中…(img-R6dFAnIB-1713678873378)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

本次面试答案,以及收集到的大厂必问面试题分享:
[外链图片转存中…(img-z5pmR9kU-1713678873378)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!