/**
- Created by TanJiaJun on 2020/8/30.
*/
class TanJiaJunFutureTaskTest {
private static class TanJiaJunCallable implements Callable {
@Override
public String call() {
return “谭嘉俊”;
}
}
public static void main(String[] args) {
// 创建FutureTask对象
FutureTask futureTask = new FutureTask<>(new TanJiaJunCallable());
// 创建线程
Thread thread = new Thread(futureTask);
thread.start();
try {
// 等待任务执行完毕,并且得到返回值
String result = futureTask.get();
System.out.println(result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
前面两种方式都没有返回值,FutureTask可以有返回值。
wait和notify
wait()方法、wait(long timeoutMillis)方法、wait(long timeoutMillis, int nanos)方法、notify()方法和notifyAll()方法都是Object类的方法。
wait系列方法
当一个线程调用共享变量的wait系列方法时,这个线程进入等待状态,直到使用下面两种方式才会被唤醒:
- 其他线程调用该共享变量的notify系列方法(notify()方法或者notifyAll()方法)。
- 其他线程调用该共享变量所在的线程的interrupt()方法后,该线程抛出InterruptedException异常返回。
要注意的是,需要获取到该共享变量的监视器锁才能调用wait方法,否则会抛出IllegalMonitorStateException异常,可以使用以下两种方式获得对象的监视器锁:
调用被关键字synchronized修饰的方法,代码如下所示:
Object object = new Object();
private synchronized void test() {
try {
// 调用变量object的wait()方法
object.wait();
} catch(InterruptedException e) {
e.printStackTrace();
}
}
执行同步代码块,代码如下所示:
Object object = new Object();
synchronized(object) {
try {
// 调用变量object的wait()方法
object.wait();
} catch(InterruptedException e) {
e.printStackTrace();
}
}
wait()
源码如下所示:
// Object.java
public final void wait() throws InterruptedException {
wait(0L);
}
这个方法实际上调用了wait(long timeoutMillis)方法,参数timeoutMillis的值是0L。它的行为和调用wait(0L, 0)方法是一致的。
wait(long timeoutMillis)
源码如下所示:
// Object.java
public final native void wait(long timeoutMillis) throws InterruptedException;
参数timeoutMillis是等待的最大时间,也就是超时时间,单位是毫秒。它的行为和调用wait(timeoutMillis, 0)方法是一致的。
要注意的是,如果传入了负数的timeoutMillis,就会抛出IllegalArgumentException异常。
wait(long timeoutMillis, int nanos)
源码如下所示:
// Object.java
public final void wait(long timeoutMillis, int nanos) throws InterruptedException {
if (timeoutMillis < 0) {
throw new IllegalArgumentException(“timeoutMillis value is negative”);
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
“nanosecond timeout value out of range”);
}
if (nanos > 0 && timeoutMillis < Long.MAX_VALUE) {
timeoutMillis++;
}
wait(timeoutMillis);
}
这个方法实际上调用了wait(long timeoutMillis)方法;参数timeoutMillis是等待的最大时间,也就是超时时间,单位是毫秒;参数nanos是额外的时间,单位是纳秒,范围是0~999999(包括999999)。
只有在参数nanos大于0的时候,参数timeoutMillis才会自增。
notify系列方法
在一个线程上调用共享变量的notify方法后,会唤醒这个共享变量上调用wait系列方法后进入等待状态的线程。要注意的是,一个共享变量可能有多个线程在等待,具体唤醒哪个等待的线程是随机的。
被唤醒的线程不能立即从wait系列方法返回后继续执行,它需要获取到该共享变量的监视器锁才能返回,也就是说,唤醒它的线程释放了该共享变量的监视器锁,被唤醒的线程不一定能获取到该共享变量的监视器锁,因为该线程还需要和其他线程去竞争这个监视器锁,只有竞争到这个监视器锁后才能继续执行。
只有当前线程获取到该共享变量的监视器锁后,才能调用该共享变量的notify系列方法,否则会抛出IllegalMonitorStateException异常。
notify()方法
源码如下所示:
// Object.java
@HotSpotIntrinsicCandidate
public final native void notify();
notifyAll()方法
源码如下所示:
// Object.java
@HotSpotIntrinsicCandidate
public final native void notifyAll();
notifyAll()方法可以唤醒所有在该共享变量上因为调用wait系列方法而进入等待状态的线程。
sleep()方法–让线程睡眠
sleep()方法是Thread类的一个静态方法。当一个正在执行的线程调用了这个方法后,调用线程会暂时让出指定睡眠时间的执行权,不参与CPU的调度,但是不会让出该线程所拥有的监视器锁。指定的睡眠时间到了后,sleep()方法会正常返回,线程处于就绪状态,然后参与CPU调度,获取到CPU资源后继续运行。
要注意的是,如果在睡眠期间其他线程调用了该线程的interrupt()方法中断了该线程,就会在调用sleep方法的地方抛出InterruptedException异常而返回。
源码如下所示:
// Thread.java
public static native void sleep(long millis) throws InterruptedException;
下面来看一个生产者消费者问题(Producer-Consumer Problem)的例子:
Repository类是一个存储库,存放产品,代码如下所示:
package producerconsumerproblem;
import java.util.LinkedList;
import java.util.Queue;
/**
- Created by TanJiaJun on 2020/8/30.
*/
class Repository {
// 队列的最大容量是10
private static final int MAX_SIZE = 10;
// 创建队列
private final Queue queue = new LinkedList<>();
void produce() {
synchronized (queue) {
while (queue.size() == MAX_SIZE) {
try {
System.out.println(“生产者(线程名字:” + Thread.currentThread().getName() + “):存储库已满”);
// 当队列满了后,调用变量queue的wait()方法,生产者线程,并且释放Queue对象的监视器锁
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 如果队列还没满,就创建新的Object对象,并且在队尾入列
queue.offer(new Object());
System.out.println(“生产者(线程名字:” + Thread.currentThread().getName() + “):生产了一个产品”);
// 通知其他生产者线程和消费者线程
queue.notifyAll();
}
}
void consume() {
synchronized (queue) {
while (queue.size() == 0) {
try {
System.out.println(“消费者(线程名字:” + Thread.currentThread().getName() + “):存储库是空”);
// 当队列空了后,调用变量queue的wait()方法,消费者线程进入等待状态,并且释放Queue对象的监视器锁
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 如果队列存在元素,就将队头元素出列
queue.poll();
System.out.println(“消费者(线程名字:” + Thread.currentThread().getName() + “):消费了一个产品”);
// 通知其他消费者线程和生产者线程
queue.notifyAll();
}
}
}
以下是测试代码,我先把生产者所在的线程睡眠(sleep)一秒,把消费者所在的线程睡眠三秒,这样就可以制造出生产速度大于消费速度的场景,代码如下所示:
package producerconsumerproblem;
/**
- Created by TanJiaJun on 2020/8/30.
*/
class ProducerConsumerProblemTest {
// 生产者线程
private static class Producer implements Runnable {
private Repository repository;
Producer(Repository repository) {
this.repository = repository;
}
@Override
public void run() {
// 循环执行
while (true) {
try {
// 让生产者线程睡眠一秒
Thread.sleep(1000);
// 调用存储库的produce()方法,生产者生产产品
repository.produce();
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
}
}
// 消费者线程
private static class Consumer implements Runnable {
private Repository repository;
Consumer(Repository repository) {
this.repository = repository;
}
@Override
public void run() {
// 循环执行
while (true) {
try {
// 让消费者线程睡眠三秒
Thread.sleep(3000);
// 调用存储库的consume()方法,消费者消费产品
repository.consume();
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
}
}
public static void main(String[] args) {
// 创建存储库
Repository repository = new Repository();
// 创建三个生产者线程,并且让它们运行
for (int i = 0; i < 3; i++) {
new Thread(new Producer(repository)).start();
}
// 创建三个消费者线程,并且让它们运行
for (int i = 0; i < 3; i++) {
new Thread(new Consumer(repository)).start();
}
}
}
运行上面的代码,大约十秒后手动结束进程,结果如下所示:
/Library/Java/JavaVirtualMachines/jdk-13.jdk/Contents/Home/bin/java “-javaagent:/Applications/IntelliJ IDEA CE.app/Contents/lib/idea_rt.jar=63240:/Applications/IntelliJ IDEA CE.app/Contents/bin” -Dfile.encoding=UTF-8 -classpath /Users/tanjiajun/IdeaProjects/ThreadDemo/out/production/ThreadDemo producerconsumerproblem.ProducerConsumerProblemTest
生产者(线程名字:Thread-2):生产了一个产品
生产者(线程名字:Thread-0):生产了一个产品
生产者(线程名字:Thread-1):生产了一个产品
生产者(线程名字:Thread-2):生产了一个产品
生产者(线程名字:Thread-0):生产了一个产品
生产者(线程名字:Thread-1):生产了一个产品
消费者(线程名字:Thread-3):消费了一个产品
消费者(线程名字:Thread-5):消费了一个产品
消费者(线程名字:Thread-4):消费了一个产品
生产者(线程名字:Thread-0):生产了一个产品
生产者(线程名字:Thread-2):生产了一个产品
生产者(线程名字:Thread-1):生产了一个产品
生产者(线程名字:Thread-0):生产了一个产品
生产者(线程名字:Thread-2):生产了一个产品
生产者(线程名字:Thread-1):生产了一个产品
生产者(线程名字:Thread-0):生产了一个产品
生产者(线程名字:Thread-2):存储库已满
生产者(线程名字:Thread-1):存储库已满
消费者(线程名字:Thread-3):消费了一个产品
生产者(线程名字:Thread-2):生产了一个产品
生产者(线程名字:Thread-1):存储库已满
消费者(线程名字:Thread-5):消费了一个产品
消费者(线程名字:Thread-4):消费了一个产品
生产者(线程名字:Thread-1):生产了一个产品
生产者(线程名字:Thread-0):生产了一个产品
生产者(线程名字:Thread-2):存储库已满
生产者(线程名字:Thread-1):存储库已满
生产者(线程名字:Thread-0):存储库已满
消费者(线程名字:Thread-3):消费了一个产品
生产者(线程名字:Thread-2):生产了一个产品
生产者(线程名字:Thread-0):存储库已满
生产者(线程名字:Thread-1):存储库已满
消费者(线程名字:Thread-4):消费了一个产品
消费者(线程名字:Thread-5):消费了一个产品
生产者(线程名字:Thread-1):生产了一个产品
生产者(线程名字:Thread-0):生产了一个产品
生产者(线程名字:Thread-2):存储库已满
生产者(线程名字:Thread-0):存储库已满
生产者(线程名字:Thread-1):存储库已满
Process finished with exit code 130 (interrupted by signal 2: SIGINT)
然后我把生产者所在的线程睡眠三秒,把消费者所在的线程睡眠一秒,这样就可以制造出生产速度小于消费速度的场景,代码如下所示:
package producerconsumerproblem;
/**
- Created by TanJiaJun on 2020/8/30.
*/
class ProducerConsumerProblemTest {
// 生产者线程
private static class Producer implements Runnable {
private Repository repository;
Producer(Repository repository) {
this.repository = repository;
}
@Override
public void run() {
// 循环执行
while (true) {
try {
// 让生产者线程睡眠三秒
Thread.sleep(3000);
// 调用存储库的produce()方法,生产者生产产品
repository.produce();
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
}
}
// 消费者线程
private static class Consumer implements Runnable {
private Repository repository;
Consumer(Repository repository) {
this.repository = repository;
}
@Override
public void run() {
// 循环执行
while (true) {
try {
// 让消费者线程睡眠一秒
Thread.sleep(1000);
// 调用存储库的consume()方法,消费者消费产品
repository.consume();
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
}
}
public static void main(String[] args) {
// 创建存储库
Repository repository = new Repository();
// 创建三个生产者线程,并且让它们运行
for (int i = 0; i < 3; i++) {
new Thread(new Producer(repository)).start();
}
// 创建三个消费者线程,并且让它们运行
for (int i = 0; i < 3; i++) {
new Thread(new Consumer(repository)).start();
}
}
}
运行上面的代码,大约十秒后手动结束进程,结果如下所示:
/Library/Java/JavaVirtualMachines/jdk-13.jdk/Contents/Home/bin/java “-javaagent:/Applications/IntelliJ IDEA CE.app/Contents/lib/idea_rt.jar=63256:/Applications/IntelliJ IDEA CE.app/Contents/bin” -Dfile.encoding=UTF-8 -classpath /Users/tanjiajun/IdeaProjects/ThreadDemo/out/production/ThreadDemo producerconsumerproblem.ProducerConsumerProblemTest
消费者(线程名字:Thread-3):存储库是空
消费者(线程名字:Thread-4):存储库是空
消费者(线程名字:Thread-5):存储库是空
生产者(线程名字:Thread-0):生产了一个产品
消费者(线程名字:Thread-3):消费了一个产品
消费者(线程名字:Thread-5):存储库是空
生产者(线程名字:Thread-1):生产了一个产品
消费者(线程名字:Thread-4):消费了一个产品
生产者(线程名字:Thread-2):生产了一个产品
消费者(线程名字:Thread-5):消费了一个产品
消费者(线程名字:Thread-3):存储库是空
消费者(线程名字:Thread-4):存储库是空
消费者(线程名字:Thread-5):存储库是空
生产者(线程名字:Thread-0):生产了一个产品
消费者(线程名字:Thread-3):消费了一个产品
消费者(线程名字:Thread-5):存储库是空
消费者(线程名字:Thread-4):存储库是空
生产者(线程名字:Thread-1):生产了一个产品
消费者(线程名字:Thread-5):消费了一个产品
生产者(线程名字:Thread-2):生产了一个产品
消费者(线程名字:Thread-4):消费了一个产品
消费者(线程名字:Thread-3):存储库是空
消费者(线程名字:Thread-4):存储库是空
消费者(线程名字:Thread-5):存储库是空
生产者(线程名字:Thread-0):生产了一个产品
消费者(线程名字:Thread-3):消费了一个产品
消费者(线程名字:Thread-5):存储库是空
消费者(线程名字:Thread-4):存储库是空
生产者(线程名字:Thread-2):生产了一个产品
生产者(线程名字:Thread-1):生产了一个产品
消费者(线程名字:Thread-4):消费了一个产品
消费者(线程名字:Thread-5):消费了一个产品
消费者(线程名字:Thread-3):存储库是空
消费者(线程名字:Thread-4):存储库是空
消费者(线程名字:Thread-5):存储库是空
生产者(线程名字:Thread-0):生产了一个产品
消费者(线程名字:Thread-3):消费了一个产品
消费者(线程名字:Thread-5):存储库是空
消费者(线程名字:Thread-4):存储库是空
生产者(线程名字:Thread-2):生产了一个产品
生产者(线程名字:Thread-1):生产了一个产品
消费者(线程名字:Thread-4):消费了一个产品
消费者(线程名字:Thread-5):消费了一个产品
Process finished with exit code 130 (interrupted by signal 2: SIGINT)
上面的结果都符合预期,我解释一下,当发现队列满了后,就会调用变量queue的wait()方法,该生产者线程就会被进入等待状态,并且释放Queue对象的监视器锁,让其他生产者线程和消费者线程去竞争这个监视器锁,打破了死锁产生的四个条件中的请求并持有条件,避免发生死锁,同样的,当发现队列空了后,也会调用变量queue的wait()方法,该消费者线程会进入等待状态,并且释放Queue对象的监视器锁,让其他消费者线程和生产者线程去竞争这个监视器锁,打破了死锁的四个条件中的请求并持有条件,避免发生死锁。
join系列方法–等待线程执行终止
join系列方法是Thread类的一个普通方法。它可以处理一些需要等待某几个任务完成后才能继续往下执行的场景。
join()方法
源码如下所示:
// Thread.java
public final void join() throws InterruptedException {
join(0);
}
这个方法实际上调用了join(final long millis)方法,参数millis的值是0。
join(final long millis)方法
源码如下所示:
// Thread.java
public final synchronized void join(final long millis)
throws InterruptedException {
if (millis > 0) {
if (isAlive()) {
final long startTime = System.nanoTime();
long delay = millis;
do {
wait(delay);
} while (isAlive() && (delay = millis -
TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)) > 0);
}
} else if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
throw new IllegalArgumentException(“timeout value is negative”);
}
}
参数millis是等待时间,单位是毫秒。
要注意的是,如果传入了负数的millis,就会抛出IllegalArgumentException异常。
join(long millis, int nanos)方法
源码如下所示:
// Thread.java
public final synchronized void join(long millis, int nanos)
throws InterruptedException {
if (millis < 0) {
throw new IllegalArgumentException(“timeout value is negative”);
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
“nanosecond timeout value out of range”);
}
if (nanos > 0 && millis < Long.MAX_VALUE) {
millis++;
}
join(millis);
}
这个方法实际上调用了join(final long millis)方法;参数millis是等待时间,单位是毫秒;参数nanos是额外的时间,单位是纳秒,范围是0~999999(包括999999)。
只有在参数nanos大于0的时候,参数millis才会自增。
我在写深入了解volatile关键字这篇文章的时候,其中一个例子使用到了这个方法,代码如下所示:
/**
- Created by TanJiaJun on 2020-08-16.
*/
class VolatileDemo {
private static final int THREADS_COUNT = 10;
private static volatile int value = 0;
private static void increase() {
// 对value变量进行自增操作
value++;
}
public static void main(String[] args) {
// 创建10个线程
Thread[] threads = new Thread[THREADS_COUNT];
for (int i = 0; i < THREADS_COUNT; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++)
// 每个线程对value变量进行1000次自增操作
increase();
});
threads[i].start();
}
// 主线程等待子线程运行结束
for (Thread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(“value的值:” + value);
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
Android开发除了flutter还有什么是必须掌握的吗?
相信大多数从事Android开发的朋友们越来越发现,找工作越来越难了,面试的要求越来越高了
除了基础扎实的java知识,数据结构算法,设计模式还要求会底层源码,NDK技术,性能调优,还有会些小程序和跨平台,比如说flutter,以思维脑图的方式展示在下图;
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
315107)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
Android开发除了flutter还有什么是必须掌握的吗?
相信大多数从事Android开发的朋友们越来越发现,找工作越来越难了,面试的要求越来越高了
除了基础扎实的java知识,数据结构算法,设计模式还要求会底层源码,NDK技术,性能调优,还有会些小程序和跨平台,比如说flutter,以思维脑图的方式展示在下图;
[外链图片转存中…(img-96Shddu8-1713606315109)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!