程序(program) 是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。下面谈一下[程序的两种执行方式]。
为了描述和控制进程的运行,系统为每个进程定义了一个数据结构 —— 进程控制块PCB。PCB描述进程状态以及控制所需信息,系统根据PCB感知进程的存在,并对它进行控制,因此,PCB是进程存在的唯一标志。由于PCB要被系统频繁的访问,它必须常驻内存。
进程可进一步细化为线程,是一个程序内部的一条执行路径。
以前所编写的程序,都是单线程的(main);每个程序都有一个入口,一个出口以及一个顺序执行的序列,在程序执行过程中的任何指定时刻,都只有一个单独的执行点。
所谓的【多线程】,就是一个程序运行时有多条不同的执行路径。
线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小。
一个进程中的多个线程共享相同的内存单元/内存地址空间,它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患。
单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。如果是多核的话,才能更好的发挥多线程的效率。(现在的服务器都是多核的)
一个 Java 应用程序 java.exe,其实至少有 3 个线程:main() 主线程、gc() 垃圾回收线程 和 异常处理线程。// 如果发生异常,会影响主线程
Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread 类来体现
Thread 类的特性:
// 遍历1000以内的所有整数
public class ThreadDemo {
// 主线程
public static void main(String[] args) {
// 3. 创建子类对象(主线程造的)
MyThread_1 t1 = new MyThread_1();
// 4. 通过子类对象调用start()
// 作用:① 启动当前线程 ② JVM 调用当前线程的run()
t1.start();
// t1.run(); // 直接调用run(), 就是纯粹的方法调用, 不会启动新的线程
// Quiz:想要再启动一个线程,打印1~1000,怎么做?
/*
t1.start(); // 错误方式!
Exception in thread "main" java.lang.IllegalThreadStateException
-------------------------
start() 部分源码:
if (threadStatus != 0) throw new IllegalThreadStateException();
*/
// 解决方法:重新创建一个线程的对象
MyThread_1 t2 = new MyThread_1();
t2.start();
for (int i = 0; i < 1000; i++)
System.out.println("主线程:"+i);
}
}
// 1. 创建继承Thread的子类
class MyThread_1 extends Thread {
// 2. 重写run()
@Override
public void run() {
for (int i = 0; i < 1000; i++)
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
// 创建两个分线程,其中一个遍历100以内的偶数,另一个遍历100以内的奇数
public class ThreadTest {
public static void main(String[] args) {
// new对象和调用start 是 main线程做的
// 创建 Thread 类的匿名子类
new Thread() {
@Override
public void run() {
for (int i = 0; i < 100; i++)
if(i % 2 != 0)
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}.start();
new Thread() {
@Override
public void run() {
for (int i = 0; i < 100; i++)
if(i % 2 == 0)
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}.start();
}
}
IllegalThreadStateException
public static Thread currentThread()
返回对当前正在执行的线程对象的引用public final void setName(String name)
设置当前线程的名字public final String getName()
返回当前线程的名字public final boolean isAlive()
判断线程是否处于活动状态。如果线程已经启动且尚未终止,则为活动状态public static void sleep(long millis) throws InterruptedException
在指定的毫秒数内让当前正在执行的线程休眠public final void join(long millis) throws InterruptedException
在Thread-A的线程体中调用Thread-B的join(),将 Thread-A 和 Thread-B 进行"合并",等待 Thread-B 结束,再恢复 Thread-A 的运行 // 暂停的不是Thread-B,而是调用 b.join() 的线程,即Thread-Apublic static void yield()
让出CPU使用权,当前线程进入就绪队列等待调度public final void stop()
已过时。 强迫线程停止执行。应使用 interrupt()
来中断该线程public final void wait() throws InterruptedException
在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待 // 当前线程进入对象的wait poolpublic final void notify()
唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待 // 唤醒对象的wait pool 中的一个/所有等待线程public final void notifyAll()
唤醒在此对象监视器上等待的所有线程。线程通过调用其中一个 wait 方法,在对象的监视器上等待注:wait()
/ notify()
/ notifyAll()
声明在 Object
中
Java提供一个线程调度器来监控程序中 [启动后进入就绪状态的] 所有线程。线程调度器按照线程的优先级决定应调用哪个线程来执行。
现成的优先级用数字表示,范围从 1 到 10,一个线程的缺省优先级是 5:
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
使用下述方法获得/设置线程对象的优先级:
public final int getPriority()
public final void setPriority(int newPriority)
通常高优先级的线程将先于优先级低的线程执行,但并不是说要等高优先级线程执行完之后才会执行低优先级线程,只是从概率上讲,高优先级线程抢占到 CPU 使用权的概率会更大,仅此而已。
public class ThreadDemo1 {
public static void main(String[] args) {
MyThread_2 r = new MyThread_2();
Thread t1 = new Thread(r);
// 1. 启动线程 2. 调用当前线程的run() → 调用target的run()
t1.start();
// 再启动一个线程(共享同一个Runnable)
Thread t2 = new Thread(r);
t2.start();
}
}
class MyThread_2 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0)
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
public class Thread implements Runnable {
// What will be run
private Runnable target;
public Thread(Runnable target) {
...
this.target = target;
...
}
...
@Override
public void run() {
if (target != null) {
target.run();
}
}
}
public class Thread implements Runnable {...}
Java中的线程分为两类:一种是守护线程,一种是用户线程
start()
前调用 thread.setDaemon(true)
可以把一个 [用户线程] 变成一个 [守护线程]JDK中用Thread.State类来定义线程状态:public static enum Thread.State extends Enum<Thread.State>
要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的 5 种状态:
start()
后,,此线程进入就绪状态sleep()
结束,其他线程 join()
,等待用户输入完毕,某个线程拿到对象锁,这些线程也将进入就绪状态yield()
,当前线程进入就绪状态run()
定义了线程的操作和功能wait()
,该线程会释放占用的所有资源,JVM 会把该线程放入 [等待池] 中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用 notify()
或 notifyAll()
才能被唤醒sleep()
或 join()
,或者发出了 I/O 请求时,JVM 会把该线程置为阻塞状态。当 sleep()
状态超时、join()
等待线程终止或者超时、I/O 处理完毕时,线程重新转入就绪状态在给定时间点上,一个线程只能处于一种状态。这些状态是虚拟机状态,它们并没有反映所有操作系统线程状态。
补充:线程的挂起(suspend) 和恢复(resume)
// 存在线程安全问题,待解决
public class SellTicket {
public static void main(String[] args) {
Window w1 = new Window();
w1.setName("window-1");
Window w2 = new Window();
w2.setName("window-2");
Window w3 = new Window();
w3.setName("window-3");
w1.start();
w2.start();
w3.start();
}
}
/*
控制台:
window-2销售出第100张票
window-3销售出第100张票
window-1销售出第100张票
window-3销售出第97张票
window-2销售出第97张票
window-1销售出第95张票
window-3销售出第94张票
...
window-3销售出第1张票
window-1销售出第0张票
window-2销售出第0张票
*/
class Window extends Thread {
private static int tickets = 100;
@Override
public void run() {
while(tickets > 0) {
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + "销售出第" + tickets + "张票");
tickets--;
}
}
}
public class SellTicket1 {
public static void main(String[] args) {
Window1 w = new Window1();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.start();
t2.start();
t3.start();
}
}
class Window1 implements Runnable {
// 无须 static
private int tickets = 100;
@Override
public void run() {
while(tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "销售出第" + tickets + "张票");
tickets--;
}
}
}
this
或 类名.class
synchronized(同步监视器) {
// 操作 [共享数据] 的代码,即为需要被同步的代码
// 共享数据:多个线程共同操作的数据(变量),比如例子中的tickets
}
@Override
public void run() {
while(true) {
synchronized(this) {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "销售出第" + tickets + "张票");
tickets--;
} else break;
}
}
}
@Override
public void run() {
while(true) { // Class clazz = Window.class; 且是唯一的
synchronized(Window.class) {
if(tickets > 0) {
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + "销售出第"
+ tickets + "张票");
tickets--;
} else break;
}
}
}
synchronized
还可以放在方法声明中,表示整个方法为同步方法。同步方法仍然涉及到同步监视器,只是不需要显式声明。
this
private synchronized void show() {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "销售出第" + tickets + "张票");
tickets--;
}
}
类名.class
private static synchronized void show() {
if(tickets > 0) {
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "销售出第" + tickets + "张票");
tickets--;
}
}
若类中有多个非静态同步方法,则这些同步方法共有一把锁;所以,当其中某个方法被调用,即对象的锁被锁住,则不会有其他线程可以调用同一个类的这个或任何其他的同步方法。注意,是只有使用同一个锁对象的线程才会受到这个影响,如果是多个锁对象,则不受影响,各是各的。但如果是静态同步方法,也就是"类锁",那这就一定是所有线程共享的锁。
class Bank {
private Bank() {}
private static Bank instance;
// public static synchronized Bank getInstance() {
public static Bank getInstance() {
/*
// 效率低
synchronized (Bank.class) {
if(instance == null)
instance = new Bank();
return instance;
}
*/
if(instance == null) {
synchronized (Bank.class) {
if(instance == null)
instance = new Bank();
}
}
return instance;
}
}
// 代码演示死锁
class A {
public synchronized void foo(B b) { //同步监视器:A类的对象a
System.out.println("当前线程名: " + Thread.currentThread().getName()
+ " 进入了A实例的foo方法"); // ①
try {
Thread.sleep(200);
} catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前线程名: " + Thread.currentThread().getName()
+ " 企图调用B实例的last方法"); // ③
b.last();
}
public synchronized void last() {//同步监视器:A类的对象a
System.out.println("进入了A类的last方法内部");
}
}
class B {
public synchronized void bar(A a) { //同步监视器:b
System.out.println("当前线程名: " + Thread.currentThread().getName()
+ " 进入了B实例的bar方法"); // ②
try {
Thread.sleep(200);
} catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前线程名: " + Thread.currentThread().getName()
+ " 企图调用A实例的last方法"); // ④
a.last();
}
public synchronized void last() { //同步监视器:b
System.out.println("进入了B类的last方法内部");
}
}
public class DeadLock implements Runnable {
A a = new A();
B b = new B();
public void init() {
Thread.currentThread().setName("主线程");
// 调用a对象的foo方法
a.foo(b);
System.out.println("进入了主线程之后");
}
public void run() {
Thread.currentThread().setName("副线程");
// 调用b对象的bar方法
b.bar(a);
System.out.println("进入了副线程之后");
}
public static void main(String[] args) {
DeadLock dl = new DeadLock();
new Thread(dl).start();
dl.init();
}
}
从JDK 5.0开始,Java提供了更强大的线程同步机制 —— 通过显式定义同步锁对象来实现同步。同步锁使用 Lock
对象充当。
java.util.concurrent.locks.Lock
接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得 Lock
对象。
ReentrantLock
类实现了 Lock
,它拥有与 synchronized
相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是 ReentrantLock
,可以显式加锁、释放锁。
java.util.concurrent.locks.Lock<I>
void lock()
获取这个锁;如果锁同时被另一个线程拥有则发生阻塞void unlock()
释放这个锁public class ReentrantLock extends Object implements Lock, Serializable
ReentrantLock()
构建一个可以被用来保护临界区的可重入锁ReentrantLock(boolean fair)
构建一个带有公平策略的锁。一个公平锁偏爱等待时间最长的线程。但是,这一公平的保证将大大降低性能。所以,默认情况下,锁没有被强制为公平的听起来公平锁更合理一些,但是使用公平锁比使用常规所要慢很多。只有当你确实了解自己要做什么并且对于你要解决的问题有一个特定的里有必须使用公平锁的时候,才可以使用公平锁。即使使用公平锁,也无法确保线程调度器是公平的。如果线程调度器选择忽略一个线程,而该线程为了这个锁已经等待了很长时间,那么就没有机会公平地处理这个锁了。
用 ReentrantLock
保护代码块的基本结构如下:
myLock.lock(); // a ReentrantLock object
try {
// critical section
} finally {
myLock.unlock(); // 确保即使发生异常,也会释放锁
}
这个结构确保任何时刻只有一个线程进入临界区。一旦一个线程封锁了锁对象,其他任何线程都无法通过 lock
语句。当其他线程调用 lock()
时,他们被阻塞,直到第一个线程释放锁对象。
注意!把解锁操作括在 finally 子句之内是至关重要的。如果在临界区的代码抛出异常,锁必须被释放。否则,其他线程将永远阻塞。
使用一个锁来保护Bank类的transfer方法:
public class Bank {
private Lock bankLock = new ReentrantLock(); // ReentrantLock implements the Lock<I>
...
public void transfer(int from, int to, int amount) {
bankLock.lock();
try {
System.out.println(Thread.currentThread().getName());
accounts[from] -= amount;
System.out.println(" %10.2f from %d to %d", amount, from, to);
accounts[to] += amount;
System.out.println(" Total Balance: " + getTotalBalance());
} finally {
bankLock.unlock();
}
}
}
transfer()
,在执行结束前辈剥夺了运行权。假定第二个线程也调用了 transfer()
,由于第二个线程不能获得锁,将在调用 lock()
时被阻塞。它必须等待第一个线程完成 transfer()
方法的执行之后才能再度被激活。当第一个线程释放锁时,那么第二个线程才能开始运行。Bank
对象都有自己的 ReentrantLock
对象。如果两个线程试图访问同一个 Bank
对象,那么锁将以串行方式提供服务。但是,如果两个线程访问不同的 ReentrantLock
对象,每一个线程得到不同的锁对象,两个线程都不会发生阻塞。本该如此,因为线程在操作不同的 Bank
实例的时候,线程之间不会相互影响。lock()
的嵌套调用。线程在每一个调用 lock()
都要调用 unlock()
来释放锁。由于这一特性,被一个锁保护的代码可以调用另一个使用使用相同的锁的方法Bank
的 transfer()
中还会调用 getTotalBalance()
,这也会封锁 bankLock
对象,此时,bankLock
对象的持有计数为 2。当 getTotalBanlance()
退出的时候,持有计数变回 1。当 transfer()
退出的时候,持有计数变为 0。线程释放锁。要留心临界区中的代码,不要因为异常的抛出而跳出了临界区。如果在临界区代码结束之前抛出了异常,finally子句将释放锁,但会是对象可能处于一种受损状态。
通常,线程进入临界区,却发现在某一条件满足之后它才能执行。要使用一个条件对象来管理那些已经获得了一个锁但是却不能做有用工作的线程。本节将介绍Java库中条件对象的实现(条件对象经常被称为条件变量)
// 下面直接截图吧。。。 我太累了
java.util.concurrent.locks.Lock
Condition newCondition()
返回一个与该锁相关的条件对象java.util.concurrent.locks.Condition
void await()
将该线程放到条件的等待集中void signalAll()
解除该条件的等待集中的所有线程的阻塞状态void signal()
从该条件的等待集中随机地选择一个线程,解除其阻塞状态wait()
添加一个线程到等待集中,notifyAll()
/ notify()
解除等待线程的阻塞状态。换句话说,调用 wait()
~ condition.await()
,notifyAll()
~ condition.signalAll()
银行有一个账户。 有两个储户分别向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额。问题:该程序是否有安全问题,如果有,如何解决?
public class AccountTest {
public static void main(String[] args) {
Account acct = new Account();
Customer c1 = new Customer(acct);
Customer c2 = new Customer(acct);
c1.setName("甲");
c2.setName("乙");
c1.start();
c2.start();
}
}
/*
打印控制台(存在线程安全问题):
乙存入1000.0, 当前余额:2000.0
甲存入1000.0, 当前余额:2000.0
乙存入1000.0, 当前余额:4000.0
甲存入1000.0, 当前余额:4000.0
乙存入1000.0, 当前余额:6000.0
甲存入1000.0, 当前余额:6000.0
---------------------------------
加入同步机制后:
甲存入1000.0, 当前余额:1000.0
甲存入1000.0, 当前余额:2000.0
甲存入1000.0, 当前余额:3000.0
乙存入1000.0, 当前余额:4000.0
乙存入1000.0, 当前余额:5000.0
乙存入1000.0, 当前余额:6000.0
*/
class Account {
private double balance;
public Account() {}
public Account(double balance) {
this.balance = balance;
}
// public void deposit(double amt) {
public synchronized void deposit(double amt) {
// 前面说过,在使用继承Thread类来创建多线程的方式时,慎用this
// 充当同步监视器,考虑使用‘当前类.class‘来充当同步监视器
// 这个慎用不是不通用,而是要具体问题具体分析,比如这里就可以用
// 此时的this,不是Customer对象,而是其共用的Account,它是唯一的
if(amt > 0) {
balance += amt;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "存入" + amt + ", 当前余额:" + balance);
}
}
}
class Customer extends Thread {
private Account acct;
public Customer(Account acct) {
this.acct = acct;
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
acct.deposit(1000);
}
}
}
break
、return
终止了该代码块、 该方法的继续执行Error
或 Exception
,导致异常结束wait()
,当前线程暂停,并释放锁Thread.sleep()
、Thread.yield()
暂停当前线程的执行suspend()
将该线程挂起,该线程不会释放锁(同步监视器)// 应尽量避免使用 suspend()
和 resume()
来控制线程wait()
: 一旦执行此方法,当前线程就会进入阻塞状态;释放同步监视器,然后进入等待notify()
: 唤醒在此同步监视器上等待的所有线程;若有多个,则会选择唤醒其中一个线程notifyAll()
: 唤醒在此同步监视器上等待的所有线程public class Demo {
public static void main(String[] args) {
Number n = new Number();
Thread t1 = new Thread(n);
Thread t2 = new Thread(n);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
class Number implements Runnable {
private int number = 1;
// private Object obj = new Object();
@Override
public void run() {
while(true) {
// synchronized (obj) {
synchronized (this) {
// obj.notify();
notify();
if(number <= 10) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
try {
System.out.println(Thread.currentThread().getName()+"将被阻塞");
wait(); // 使得调用该方法的线程进入阻塞状态;且线程会释放锁
// obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 在当前线程被notify后,要重新获得监控权,然后从断点处继续代码的执行
System.out.println(Thread.currentThread().getName()
+ "被唤醒后,继续从这里执行");
} else break;
}
}
}
}
synchronized (obj) {notify(); ... }
会抛异常:IllegalMonitorStateException
;要求这 3 个方法的调用者必须是同步监视器sleep()
和 wait()
的异同?
生产者(Productor) 将产品交给店员(Clerk),而消费者(Customer) 从店员处取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如 果店中有产品了再通知消费者来取走产品。
/*
1. 是否是多线程问题?是,生产者线程,消费者线程
2. 是否有线程安全问题?是,共享数据为 [商品数目]
3. 如何解决线程安全问题?同步机制
4. 是否涉及到线程的通信?是,wait()/notify()
*/
class Clerk {
private int productCount;
private final int MAX_COUNT = 20;
// 如下两个同步方法共用一个对象监视器, 即 Clerk.this
public synchronized void produceProduct() {
if(productCount < MAX_COUNT) {
productCount++;
System.out.println(Thread.currentThread().getName()
+ "生产第" + productCount + "个产品");
notifyAll();
} 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 Consumer extends Thread {
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
while(true) {
System.out.println(getName() + "消费中...");
try {
Thread.sleep(130);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.consumeProduct();
}
}
}
class Producer extends Thread {
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
while(true) {
System.out.println(getName() + "生产中...");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.produceProduct();
}
}
}
public class ProductTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer p = new Producer(clerk);
Consumer c1 = new Consumer(clerk);
Consumer c2 = new Consumer(clerk);
p.setName("生产者");
c1.setName("1号消费者");
c2.setName("2号消费者");
p.start();
c1.start();
c2.start();
}
}
public interface Callable<V>
返回结果并且可能抛出异常的任务。实现者定义了一个不带任何参数的叫做 call 的方法public interface Future<V>
Future 表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。计算完成后只能使用 get 方法来获取结果public interface RunnableFuture<V> extends Runnable, Future<V>
作为 Runnable 的 Future。成功执行 run 方法可以完成 Future 并允许访问其结果public class FutureTask<V> extends Object implements RunnableFuture<V>
可取消的异步计算。利用开始和取消计算的方法、查询计算是否完成的方法和获取计算结果的方法,此类提供了对 Future 的基本实现。仅在计算完成时才能获取结果;如果计算尚未完成,则阻塞 get 方法。一旦计算完成,就不能再重新开始或取消计算Callable<I>
的实现类,实现 call()
,将此线程需要执行的操作声明在 call()
中Callable<I>
实现类对象public FutureTask(Callable<V> callable)
,创建 FutureTask
对象FutureTask
对象作为参数传递到 Thread
构造器(FutureTask 实现的接口继承了 Runnable) 中,创建 Thread
对象Thread
对象的 start()
启动线程FutureTask
对象的 get()
获取 call()
的返回值class NewThread implements Callable {
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 0; i < 100; i++) {
if(i % 2 == 0) {
System.out.println(i);
sum += i;
}
}
return sum;
}
}
public class CallableDemo {
public static void main(String[] args) {
NewThread nt = new NewThread();
FutureTask ft = new FutureTask(nt);
new Thread(ft).start();
try {
Object sum = ft.get();
System.out.println("总和为:" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
经常创建和销毁、使用量特别大的资源,比如并发情况下的线程, 对性能影响很大。提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。
使用线程池的好处:① 提高响应速度(减少了创建新线程的时间) ② 降低资源消耗(重复利用线程池中线程,不需要每次都创建) ③ 便于线程管理(核心池的大小、最大线程数、线程没有任务时最多保持多长时间后会终止 ...)
Executors.newCachedThreadPool()
:必要时创建新线程;空闲线程会被保留60秒Executors.newFixedThreadPool(n)
:该池包含固定数量的线程;空闲线程会一直被保留Executors.newSingleThreadExecutor()
:只有一个线程的"池",该线程顺序执行每一个提交的任务Executors.newScheduledThreadPool(n)
:创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行void execute(Runnable command)
:执行任务/命令,没有返回值,一般用来执行 Runnable<T> Future<T> submit(Callable<T> task)
:执行任务,有返回值,一般用来执行 Callablevoid shutdown()
:关闭连接池void setCorePoolSize()
:设置核心线程数int getPoolSize()
返回池中的当前线程数void setKeepAliveTime(long time, TimeUnit unit)
设置线程在终止前可以保持空闲的时间限制原文:https://www.cnblogs.com/liujiaqi1101/p/13283192.html