一、程序、进程、线程
(一)相关概念
- 程序 Program:完成特定任务,用某种语言编写的一组指令的集合,指一段静态的代码,静态对象。
- 进程 Process:一个程序的执行过程,或是正在运行的一个程序,是一个动态的过程,有它自身的产生、存在和消亡的过程(生命周期)
- 进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
- 线程 Thread:进程可以进一步细化为线程,是一个程序内部的一条执行路径
- 若一个进程同一时间并行执行多个线程,就是支持多线程的
- 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器,线程切换的开销小
- 一个进程中的多个线程共享相同的内存单元/内存地址空间 -> 他们从同一个堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更加简便、高效。但多个线程操作共享的系统资源可能就会带来安全隐患
- 虚拟机栈&程序计数器每个线程中拥有一套
方法区&堆一个进程拥有一套,多个线程共享一个进程中的方法区和堆
- 线程可以细化为多个进程
每个线程拥有自己独立的虚拟机栈和程序计数器
多个线程,共享同一个进程中的堆和方法区
(二)单核CPU和多核CPU
- 单核CPU:其实是一种假的多线程
- 多核CPU:同时执行, 发挥多线程的效率,现在的服务器都是多线程的
- 一个java应用程序java.exe至少有三个线程:main()主线程、gc()l垃圾回收线程、异常处理线程
如果发生异常,会影响主线程
(三)并行与并发
- 并行:多个CPU同时执行多个任务。Eg. 多个人同时做不同的事
- 并发:一个CPU“同时”(快速切换)执行多个任务。Eg. 秒杀
(四)使用多线程的优点
- 提高应用程序的相应
- 提高计算机系统CPU的利用率
- 改变程序结构,将长且复杂的进程分为多个线程,独立运行,利于理解和修改
(五)何时需要多线程
- 程序徐奥同时执行两个或多个任务
- 程序需要实现一些需要等待的任务时,Eg. 用户输入、文件读写、网络操作…
- 需要一些后台运行的程序时
二、线程的创建和使用
(一)线程的创建
方式一:继承Thread类 or 创建Thread类的匿名子类
- 继承Thread类
- 重写Thread类中的run()方法,将此线程执行的操作声明在run()中
- 创建Thread类子类的对象
- 通过此对象调用start()
- 不可以让已经执行start()的线程再去执行start(), 会报异常java.lang.IllegalThreadStateException,若需开启一个新线程,需要重新new一个对象
- 启动一个线程, 必须调用start()方法, 无法直接通过调用run()方法启动一个新线程
继承Thread类
// Step1:继承Thread类
class MyThread extends Thread {
// Step2:重写Thread类中的run() --> 将此线程执行的操作声明在run()中
@Override
public void run() {
super.run();
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + i);
}
}
}
}
public class ThreadTest1 {
public static void main(String[] args) {
// Step3:创建Thread类的子类对象
MyThread t1 = new MyThread();
// Step4:通过此对象调用start()
t1.start();
t1.start();// 不可以让已经执行start()的线程再去执行start(), 会报异常java.lang.IllegalThreadStateException
// 若需要开启一个新线程,需要重新new一个对象
t1.run();// 无法直接通过调用run()方法启动一个新线程
}
}
创建Thread类的匿名子类
public class ThreadTest {
public static void main(String[] args) {
new Thread() {
@Override
public void run() {
super.run();
System.out.println("innerrun()");
}
}.start();
}
}
方式二:实现Runnable接口
- 创建一个实现了Runnable接口的类
- 在实现类中实现Runnable接口的run()方法
- 创建实现类对象
- 创建Thread类的对象, 将实现类对象作为参数传入Thread类的构造器中
- 通过Thread类的对象调用start()方法
public class ThreadTest1 {
public static void main(String[] args) {
//Step3: 创建实现类的对象
MyThread1 myThread = new MyThread1();
// Step4: 将此对象作为参数传入Thread类的构造器中, 创建Thread类的对象
Thread thread1 = new Thread(myThread);
// Step5: 通过Thread类的对象调用start()
thread1.start();
// 创建多个线程
Thread thread2 = new Thread(myThread);
thread2.start();
Thread thread3 = new Thread(myThread);
thread3.start();
}
}
// Step1: 创建一个实现了Runnable接口的类
class MyThread1 implements Runnable {
// Step2: 实现类实现Runnable中的run()方法
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
- 对比两种创建方式: 开发中优先选择实现Runnable接口的方式
- 继承Thread类方法创建线程, 该类无法再此继承其他类, 具有局限性
- 实现Runbbale接口的方式更适合处理多个线程共享数据的情况
- 相同点:
- 两种方式都需要重写run(), 将线程需要执行的逻辑声明在run()中
- 两种方式都需要调用Thread类中的start()方法启动线程
-
public class Thread implements Runnable
(二)Thread类的相关方法
- void start(): 启动当前线程, 并执行对象的run()方法
- run(): 线程在被调度时执行的操作写在此方法中, 通常需要重写Thread类中的此方法
- String getName(): 返回线程的名字
- void setName(String name) :设置当前线程的名字
- static Thread currentThread(): 返回执行当前代码的线程
public class ThreadMethodTest {
public static void main(String[] args) {
//1. 通过构造器给当前对象调用线程命名
MethodTest1 test1 = new MethodTest1("ThreadNewName");
//2. 给当前对象调用线程命名
test1.setName("Thread1-");
test1.start();
//给主线程命名
Thread.currentThread().setName("MainThread-");
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + i);
}
}
}
}
class MethodTest1 extends Thread {
public MethodTest1(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + i);
}
}
}
}
- static void yield(): 线程让步,释放当前cpu执行权
–> 暂停当前执行的线程, 把执行机会让给优先级相同或更高的线程
–> 若队列中没有同优先级的线程, 忽略此方法
class MethodTest6 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + i);
}
if (i % 20 == 0) {
yield(); //this.yiele():
}
}
}
}
- join(): 在线程A中调用线程B的join()方法时, 线程A将被阻塞, 直到调线程B完全执行完以后,线程A才结束阻塞状态,继续执行
–> 低优先级的线程也可以获得执行
public class ThreadMethodTest {
public static void main(String[] args) {
MethodTest1 test1 = new MethodTest1();
test1.start();
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + i);
}
if (i == 20) {
try {
test1.join(); // 在主线程中调用另一个线程对象的join()方法
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class MethodTest1 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + i);
}
}
}
}
- stop(): 强制线程生命结束, 不推荐使用 -> Deprecated 已过时
- static void sleep(long millis): (指定时间: ms) 让当前线程“睡眠”指定millitime毫秒,在指定的millitime毫秒内,当前线程时阻塞状态,阻塞时间结束后重新排队
–> 抛出InterruptedException异常
class MethodTest1 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
try {
sleep(1000);
System.out.println("-------sleeping");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + i);
}
}
}
}
- boolean isAlive(): 返回boolean, 判断线程是否还活着
public class ThreadMethodTest {
public static void main(String[] args) {
MethodTest1 test1 = new MethodTest1();
test1.start();
for (int i = 0; i < 100; i++) {
try {
test1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(test1.isAlive()); // 判断test1对象线程是否仍然存活 --> fa;se0
}
}
(三)线程的调度
- 调度策略
- 时间片
- 抢占式:高优先级的线程抢占CPU
- 调度方法
- 同优先级线程组成,先进先出,使用时间片策略
- 对高优先级,使用优先调度的抢占式策略
- 线程的优先级等级
- MAX_PRIORITY: 10
- MIN_PRIORITY: 1
- NORM_PRIORITY: 5 --> 默认优先级
- 涉及的方法
- getPriority() : 返回线程的优先级
- setPriority() : 改变线程的优先级
- 说明
- 线程创建时,继承父线程的优先级
- 低优先级只是获得调度的概率低,并非一定在高优先级线程之后才被调用
(四)线程的分类
- 用户线程: 主线程就是用户线程, 用户线程执行结束时, 守护线程就会执行结束
- 守护线程: 服务用户线程, 通过在start()方法前调用thread.setDaemon(true) 可以把一个用户线程变成一个守护线程
- Java垃圾回收就是守护线程
- 若JVM中都是守护线程,当前JVM将退出
三、线程的生命周期
- 新建: 当Thread类或其子类的对象被声明并创建时, 新生的线程对象处于新建状态
- 就绪: 处于新建状态的线程被start()后, 将进入线程队列等待CPU时间片, 此时他已经具备了运行的条件, 只是没有分配到CPU资源
- 运行: 当就绪的线程被调度并获得CPU资源时, 便进入运行状态, run()方法定义了线程的操作和功能
- 阻塞: 在某种特殊情况下, 被人为挂起或执行输入输出操作时, 让出CPU并临时终止自己的执行, 进入阻塞状态
- 死亡: 线程完成了它的全部工作, 或线程被提前强制终止, 或出现异常导致结束
四、线程的安全问题
- Java中通过同步机制解决线程安全问题
- 同步代码块
- 同步方法
(一)同步代码块
- 操作共享数据的代码,即需被同步的代码
- 共享数据:多个线程共同操作的变量
- 同步监视器:( synchronized ) 任何一个类的对象,都可被称为锁
- 多个线程必须共用同一把锁
- 使用同步代码块,解决了线程的安全问题,操作同步代码时,只有一个线程参与,其他线程等待,相当于是一个单线程 --> 效率低
synchronized (同步监视器) {
// 需要被同步的代码
}
1. extends Thread --> public static Object obj = new Object();
public class ThreadTest4 {
public static void main(String[] args) {
MyThread4 thread1 = new MyThread4();
MyThread4 thread2 = new MyThread4();
MyThread4 thread3 = new MyThread4();
thread1.setName("Thread1- ");
thread2.setName("Thread2- ");
thread3.setName("Thread3- ");
thread1.start();
thread2.start();
thread3.start();
}
}
class MyThread4 extends Thread {
public static int ticket = 100;
public static Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized (obj) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ticket);
ticket--;
} else
break;
}
}
}
}
2. implements Runnable --> synchronized (MyThread3.class)
public class ThreadTest3 {
public static void main(String[] args) {
MyThread3 myThread = new MyThread3();
Thread thread1 = new Thread(myThread);
Thread thread2 = new Thread(myThread);
Thread thread3 = new Thread(myThread);
thread1.setName("Thread1- ");
thread2.setName("Thread2- ");
thread3.setName("Thread3- ");
thread1.start();
thread2.start();
thread3.start();
}
}
class MyThread3 implements Runnable {
public int ticket = 100;
@Override
public void run() {
while (true) {
synchronized (MyThread3.class) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ticket);
ticket--;
} else
break;
}
}
}
}
3. implements Runnable --> synchronized (this)
class MyThread3 implements Runnable {
public int ticket = 100;
@Override
public void run() {
while (true) {
synchronized (this) { // 唯一的Runnable对象
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ticket);
ticket--;
} else
break;
}
}
}
}
(二)同步方法
- 当操作共享数据的代码完成的声明在一个方法中,我们可以将这个方法声明为同步
- 同步方法仍然涉及同步监视器,只是不需要显示声明
- 当同步方法是非static时,同步监视器为this
- 当同步方法是static时,同步监视器是本身(类)
1. implements Runnable
public class ThreadTest5 {
public static void main(String[] args) {
MyThread5 myThread = new MyThread5();
Thread thread1 = new Thread(myThread);
Thread thread2 = new Thread(myThread);
Thread thread3 = new Thread(myThread);
thread1.setName("Thread1- ");
thread2.setName("Thread2- ");
thread3.setName("Thread3- ");
thread1.start();
thread2.start();
thread3.start();
}
}
class MyThread5 implements Runnable {
public int ticket = 100;
@Override
public void run() {
while (true) {
show();
}
}
private synchronized void show() {
// 同步监视器 this
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ticket);
ticket--;
}
}
}
2. extends Thread
public class ThreadTest6 {
public static void main(String[] args) {
MyThread6 thread1 = new MyThread6();
MyThread6 thread2 = new MyThread6();
MyThread6 thread3 = new MyThread6();
thread1.setName("Thread1- ");
thread2.setName("Thread2- ");
thread3.setName("Thread3- ");
thread1.start();
thread2.start();
thread3.start();
}
}
class MyThread6 extends Thread {
public static int ticket = 100;
@Override
public void run() {
while (true) {
show();
}
}
private static synchronized void show() {
// 没有static则是创建三个不同对象
// 同步监视器 MyThread6.class
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ticket);
ticket--;
}
}
}
五、线程安全的单例模式之 懒汉式
class Bank {
private Bank() {}
private static Bank instance = null;
public static Bank getInstance() {
if (instance == null) {
synchronized (Bank.class) {
if (instance == null) {
instance = new Bank();
}
}
}
return instance;
}
}
六、线程的死锁问题
- 死锁:不同的线程分别占用对方需要的同步资源不释放,都在等待对方释放自己需要的同步资源,就形成了线程的死锁
- 出现死锁后,不会出现异常,不会出现提示,只是所有的线程处于阻塞状态,无法继续
public class ThreadTest7 {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s1) {
s1.append("a");
s2.append("1");
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2) {
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2) {
s1.append("c");
s2.append("3");
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1) {
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}).start();
}
}
ab
12
abcd
1234
public class DeadLockTest2 implements Runnable {
A a = new A();
B b = new B();
public void init() {
Thread.currentThread().setName("MainThread");
a.foo(b);
System.out.println("after MainThread...");
}
@Override
public void run() {
Thread.currentThread().setName("SubThread");
b.bar(a);
System.out.println("in SubThread...");
}
public static void main(String[] args) {
DeadLockTest2 d2 = new DeadLockTest2();
new Thread(d2).start();
d2.init();
}
}
class A {
public synchronized void foo(B b) {
System.out.println("当前线程:" + Thread.currentThread().getName() + ",进入了A类的foo方法");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前线程:" + Thread.currentThread().getName() + ",准备调用B类的last方法");
b.last();
}
public synchronized void last() {
System.out.println("in A last");
}
}
class B {
public synchronized void bar(A a) {
System.out.println("当前线程:" + Thread.currentThread().getName() + ",进入了B类的bar方法");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前线程:" + Thread.currentThread().getName() + ",准备调用B类的foo方法");
}
public synchronized void last() {
System.out.println("in B last");
}
}
七、Lock锁
- JDK5.0起, Java通过显式定义同步锁来实现对象同步
- java.util.concurrent.locks.Lock接口, 是控制多个线程对共享资源进行访问的工具, 锁提供了对共享资源的独占访问, 每次只能有一个线程对Lock对象加锁, 线程开始访问共享资源之前应该先获得Lock对象
- ReentrantLock类实现了Lock, 可以显示加锁,释放锁
- Lock锁和Synchronized的区别
- Synchroinzed机制在执行完相应的同步代码以后, 自动释放同步监视器 <隐式锁>
- Lock需要手动启动同步( lock() ), 同时结束同步也需要手动实现( unlock() ) <显式锁>
- Lock只有代码块锁, Synchronized有代码块锁和方法锁
- 使用Lock锁, JVM将话费更少的时间调度线程, 性能更好, 并且具有更好的扩展性(提供更多子类)
- 使用顺序
Lock -> 同步代码块(已经进入了方法体, 分配了相应资源) -> 同步方法(在方法体之外) - 具体使用:
-1. 实例化ReentrantLock
-2. 在try{}中调用锁定lock()方法
-3. 在finally{}中调用解锁unlock()方法
public class LockTest1 {
public static void main(String[] args) {
Window window = new Window();
Thread thread1 = new Thread(window);
Thread thread2 = new Thread(window);
Thread thread3 = new Thread(window);
thread1.setName("thread1 - ");
thread2.setName("thread2 - ");
thread3.setName("thread3 - ");
thread1.start();
thread2.start();
thread3.start();
}
}
class Window implements Runnable { // 如果是extends Thread
private int ticket = 100;
// Step1 实例化ReentrantLock
// 如果是extends Thread, 需要将lock声明为static的
private ReentrantLock lock = new ReentrantLock(false); //boolean fair: 谁先来的谁先执行
@Override
public void run() {
while (true) {
try {
// Step2 调用锁定lock()方法
lock.lock();
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "sales :" + ticket);
ticket--;
} else
break;
} finally {
// Step3 调用解锁unlock()方法
lock.unlock();
}
}
}
}
练习 – 存钱
public class LockTest2 {
public static void main(String[] args) {
Account acct = new Account(0);
Customer c1 = new Customer(acct);
Customer c2 = new Customer(acct);
c1.setName("甲");
c2.setName("乙");
c1.start();
c2.start();
}
}
class Account {
private double balance;
public Account(double balance) {
this.balance = balance;
}
public synchronized void deposit(double amount) {
if (amount > 0) {
balance += amount;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +
"存钱成功! 余额为:" + 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);
}
}
}
八、线程通信
- 线程通信:两个线程交替打印100以内的数
- wait(): 一旦进入此方法,当前线程进入阻塞状态,并释放锁(同步监视器)
- notify(): 一旦进入此方法,则唤醒被wait()的一个线程,如果有多个线程被wait(),则唤醒优先级高的
- notifyAll(): 一旦进入此方法,则唤醒被wait()的所有线程
- 注意:
- wait()、notify()、notifyAll() 必须使用在同步代码块或同步方法中
- 三个方法前省略的是this.
- 三个方法的调用者必须是同步代码块或同步方法中的同步监视器,否则会出现异常IllegalMonitorStateException
public static void main(String[] args) {
Number number = new Number();
Thread thread1 = new Thread(number);
Thread thread2 = new Thread(number);
thread1.setName("Thread1");
thread2.setName("Thread2");
thread1.start();
thread2.start();
}
}
class Number implements Runnable {
private int num = 1;
@Override
public void run() {
while (true) {
synchronized (this) {
notify(); // notify()只唤醒一个线程,notifyAll()唤醒所有wait()的线程
if (num <= 100) {
System.out.println(Thread.currentThread().getName() + ":" + num);
num++;
try {
// 使得调用如下wati()的线程进入阻塞状态,并释放锁
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else
break;
}
}
}
}
- 注意:
- wait()、notify()、notifyAll() 必须使用在同步代码块或同步方法中
- 三个方法前省略的是this.
- 三个方法的调用者必须是同步代码块或同步方法中的同步监视器,否则会出现异常IllegalMonitorStateException
- 三个方法定义在java.lang.Object中
class Number implements Runnable {
private int num = 1;
Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized (obj) {
notify(); //同步监视器变为obj,调用方法应为obj.notify()
if (num <= 100) {
System.out.println(Thread.currentThread().getName() + ":" + num);
num++;
try {
wait(); //同步监视器变为obj,调用方法应为obj.wait()
} catch (InterruptedException e) {
e.printStackTrace();
}
} else
break;
}
}
}
}
- sleep() 和wait()方法的异同:
- 一旦进入方法,都可以使当前方法进入阻塞状态
- 声明位置不同:Thread类中声明sleep(),Object类中声明wait()
- 调用要求不同:sleep() 可以在任何需要的场景下调用,wait() 必须使用在同步代码块或同步方法中
- 关于是否释放同步监视器:如果两个方法都使用在同步代码块、同步方法中,sleep() 不会释放同步监视器,wait()会释放同步监视器
练习 – 生产者消费者
public class ProductTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer producer = new Producer(clerk);
producer.setName("Producer1");
Customer customer = new Customer(clerk);
customer.setName("Customer1");
producer.start();
customer.start();
}
}
class Clerk {
private int product = 0;
public synchronized void produce() {
if (product < 20) {
product++;
System.out.println(Thread.currentThread().getName() + ":" + "生产第" + product + "件产品");
notify();
} else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void consume() {
if (product > 0) {
System.out.println(Thread.currentThread().getName() + ":" + "消费第" + product + "件产品");
product--;
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("生产者开始生产...");
while (true) {
clerk.produce();
}
}
}
class Customer extends Thread {
private Clerk clerk;
public Customer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println("消费者开始消费...");
while (true) {
clerk.consume();
}
}
}
九、JDK5.0新增创建方式
新增方式一:实现Callable接口
- 与Runnable相比,Callable功能更强大
- 相比run() 方法,call() 方法可以有返回值
- call() 方法可以抛出异常
- 支持泛型的返回值
- 需要借助FutureTest类,比如获取返回结果
- Future接口
- 可以对具体Runnable、Callable的执行结果进行取消、查询是否完成、获取结果等
- FutureTask是Futurei接口的唯一实现类
- FutureTask同时实现了Runnable、Future接口,它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
- 具体步骤
- Step1 创建一个实现Callable接口的实现类
- Step2 实现call()方法,并将此线程需要执行的操作声明在call()中,call()方法可以有返回值
- Step3 创建Callable接口实现类的对象
- Step4 将此Callable接口实现类的对象作为参数传入FutureTest的构造器中,创建FutureTest对象
- Step5 将FutureTest的对象作为参数传入Thread类的构造器中,创建Thread的匿名对象,并调用start()启动线程
- Step6 获取Callable中call()方法的返回值
public class CallableTest {
public static void main(String[] args) {
// Step3 创建Callable接口实现类的对象
NumberThread numThread = new NumberThread();
// Step4 将此Callable接口实现类的对象作为参数传入FutureTest的构造器中,创建FutureTest对象
FutureTask futureTask = new FutureTask(numThread);
// Step5 将FutureTest的对象作为参数传入Thread类的构造器中,创建Thread的匿名对象,并调用start()启动线程
new Thread(futureTask).start(); // FutureTest实现了Runnable接口
try {
// Step6 获取Callable中call()方法的返回值
// get()方法返回值即为FutureTask构造器参数Callable实现类重写的call()方法的返回值
System.out.println("总和为:" + futureTask.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
// Step1 创建一个实现Callable接口的实现类
class NumberThread implements Callable {
// Step2 实现call()方法,并将此线程需要执行的操作声明在call()中,call()方法可以有返回值
@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;
}
}
新增方式二:线程池
- 背景:经常创建和销毁,使用量特别大的资源,比如开发情况下的线程,对性能影响很大
- 思路:提前创建好多个线程,放入线程池中,使用时可以直接获取,使用完放回池中,可以避免频繁的调用销毁,实现重复利用。
- 优点
- 提高相应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
- 便于线程管理
- corePoolSize:核心池的大小
- maximumPoolSize:最大线程数
- KeepAliveTime:线程没有任务时,最多保持多长时间后终止
- 相关API
- ExecutorService 接口:真正的线程池接口
- ThreadPoolExecutor类
- void execute (Runnable command): 执行任务/命令,没有返回值,一般执行Runnable
- < T> Future < T> submit (Callable task): 执行任务,有返回值,一般用来执行Callable
- void shuldown(): 关闭连接池
- Executors 工具类:线程池的工厂类,用来创建并返回不同类型的线程池
- Executors newCachedThreadPool(): 创建一个可根据需要创建新线程的线程池
- Executors newFixedThreadPool(): 创建一个可复用固定线程的线程池
- Executors newSingleThreadPool(): 创建一个只有一个线程的线程池
- Executors newScheduledThreadPool(): 创建一个线程池,它可安排在给定延迟后运行命令或者定期的执行
- ExecutorService 接口:真正的线程池接口
public class ThreadPoolTest {
public static void main(String[] args) {
// Step1 提供指定线程数的线程池
ExecutorService service = Executors.newFixedThreadPool(2);
System.out.println(service.getClass()); // java.util.concurrent.ThreadPoolExecutor
// ThreadPoolExecutor extends AbstractExecutorService -> AbstractExecutorService implements ExecutorService
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
service1.set...
// Step2 执行指定的线程的操作,需要提供Runnable接口或Callable接口的实现类对象
service.execute(new NumThread());// 适合使用于Runnable
service.execute(new NumThread1());// 适合使用于Runnable
// service.submit(task); // 适合使用于Callable
// Step3 关闭连接池
service.shutdown();
}
}
class NumThread implements Runnable {
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
class NumThread1 implements Runnable {
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}