并行性和并发性是既相似又有区别的两个概念。并行性是指两个或多个事件在同一时刻发生,并发性是指两个或多个事件在同一时间间隔内发生。
在多道程序环境下,并发性是指在一段时间内,宏观上有多个程序同时运行,但在单处理器系统中每个时刻却仅能有一道程序执行,因此微观上这些程序只能分时地交替执行。若在计算机系统中有多个处理器,则这些可以并发执行的程序便被分配到多个处理器上,实现并行执行,即利用每个处理器来处理一个可并发执行的程序。
程序(program)
是静态的,是一段具有特定功能的代码。
进程(process)
是动态的,它是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程,进程具有一定的生命周期,如创建,就绪,执行,阻塞,死亡……。进程举例:
进程是具有特定功能的程序在一个数据集合上的运行过程,是系统进行资源分配和调度的独立单位(没有线程时)。
进程一般由以下几个部分组成:
线程(thread)
由于进程是并发执行的,每次切换时都需要保存上一个进程的运行环境并恢复下一个进程的环境,这就造成了一定的开销,为了减少这种开销,提高操作系统的并发程度。因此引入了线程的概念,在拥有线程的操作系统中,进程不再是进行资源分配与调度的单位,而仅仅是拥有资源的单位,一个进程可有多个线程,每个线程都是一个可运行的实体,各线程并发执行,即操作系统进行调度的单位是线程、进行资源分配的单位是进程。
线程由线程标识符、程序计数器、一组寄存器的值和堆栈组成。由于线程只是任务调度的单位,只是运行实体,而不是资源分配的单位,因此除了上述线程运行时必不可少的资源外,线程基本不拥有资源。线程创建时不需要另行分配资源,终止线程时也不需要进行资源回收,切换线程时也大大减少了需要保存和恢复的现场信息,因此线程的创建、终止、切换均比进程迅速且开销小,从而提高了操作系统的并发程度,增大了系统吞吐量,提高了系统效率。由于一个进程所拥有的全部线程共享该进程分配的内存、资源等,因此线程间的通信也比进程更快、更方便。
在进程的运行过程中,由于系统中多个进程的并发运行及相互制约的结果,使得进程的状态不断发生变化。通常,一个运行中的进程至少可划分为5种基本状态。
package com.fanyi.fy.thread_creation;
/**
* @author FanYi
* @datetime 2021/1/24 13:30
*
* 多线程的创建之方式一:继承Thread类
* 1 创建一个Thread的子类
* 2 重写Thread类的run()方法,将线程要执行的代码写在run()方法中
* 3 创建Thread子类对象
* 4 通过此对象调用start()方法
* ① 启动当前线程
* ② 调用当前线程的run()方法
*/
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
}
}
}
}
public class ThreadCreation1 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
// 1 必须通过start()方法启动线程,不能通过run()方法。否则创建不出多线程
// myThread.run();
// 2 同一线程,其start()方法只能调用一次,不能多次调用。否则出现 java.lang.IllegalThreadStateException
// myThread.start();
MyThread myThread1 = new MyThread();
myThread1.start();
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
}
}
}
}
package com.fanyi.fy.thread_creation;
/**
* @author FanYi
* @datetime 2021/1/24 15:20
*
* 线程创建方式二:实现Runnable接口
* 1 创建一个实现Runnable接口的类
* 2 重写接口的run()方法
* 3 创建实现类的对象
* 4 将该对象作为参数传递到Thread的构造方法中
* 5 通过Thread对象调用start()方法
*/
class MyThread2 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 3 == 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
}
}
}
}
public class ThreadCreation2 {
public static void main(String[] args) {
MyThread2 myThread2 = new MyThread2();
Thread thread = new Thread(myThread2);
thread.setName("线程一");
thread.start();
}
}
实现Callable接口。— JDK 5.0新增
与使用Runnable相比,Callable功能更强大些相比run()方法强大
Future接口可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
/**
* @author FanYi
* @datetime 2021/1/25 6:13
*
* 实现Callable接口创建线程步骤:
* 1 创建要给实现Callable接口的类
* 2 实现接口中的call()方法,并将线程需要完成的任务代码写在方法内
* 3 创建Callable接口实现类的对象
* 4 将Callable接口实现类对象作为参数传递到FutureTack构造函数中,创建FutureTask的对象
* 5 将FutureTask对象作为参数传递到Thread类的构造函数中
* 6 执行线程的start()方法,启动线程执行
* 7 如需获取线程返回值,则调用FutureTask对象的get()方法
*/
class PrintEvenAndCalculateSum implements Callable {
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 1; i < 101; i++) {
if (i % 2 == 0) {
System.out.println(i);
sum += i;
}
}
return sum;
}
}
public class PrintEvenAndCalculateSumTest {
public static void main(String[] args) {
PrintEvenAndCalculateSum thread = new PrintEvenAndCalculateSum();
FutureTask futureTask = new FutureTask(thread);
new Thread(futureTask).start();
Object o = null;
try {
// 获取线程返回值。get()方法的返回值和Callable接口中call()方法的返回值一样
o = futureTask.get();
System.out.println("1~100内的偶数之和为:" + o);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。可以提前创建好多个现场,放入线程池,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。
线程池的好处:
JDK 5.0 起提供了线程池相关的API:ExecutorService 和 Executors
class PrintEven implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + " 打印 " + i);
}
}
}
}
class PrintOdd implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + " 打印 " + i);
}
}
}
}
public class ThreadPoolTest {
public static void main(String[] args) {
// 1 创建一个指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
// 2 执行指定线程的操作。需使用实现Runnable或Callable接口的类对象作为参数
service.execute(new PrintEven());
service.execute(new PrintOdd());
// service.execute(new CallableImpl());
// 3 关闭线程池
service.shutdown();
}
}
package com.fanyi.fy.thread_creation;
/**
* @author FanYi
* @datetime 2021/1/24 14:21
*
* Thread类常用方法:
* 1 start() 导致该线程开始执行;java虚拟机调用这个线程的 run方法。
* 2 run() 将要执行的代码写在其中,让线程执行
* 3 currentThread() 静态方法,返回当前正在执行的线程对象的引用。
* 4 getName() 返回此线程的名称。
* 5 setName(String name) 改变该线程的名称等于参数 name。
* 6 sleep(long millis) 当前正在执行的线程休眠(暂停执行)为指定的毫秒数,根据精度和系统定时器和调度的准确性。
* 7 join() 在线程a中调用线程b的join方法,则线程a进入阻塞状态,直至线程b执行完毕,线程a结束阻塞再次执行
* 8 isAlive() 判断当前线程是否存活
* 9 yield() 当前线程暂时放弃cpu使用权
* 10 stop() 强制结束当前线程。此方法已过时
* 线程优先级:
* 1 线程优先级等级
* MAX_PRIORITY 10
* MIN_PRIORITY 1
* NORM_PRIORITY 5 默认的线程优先级
* 2 获取和设置线程优先级
* getPriority() 获取线程优先级
* setPriority(int p) 设置线程优先级
* 3 Java的优先级调度策略
* 同优先级线程组成先进先出队列(先到先服务:FCFS),使用时间片策略;
* 高优先级进程,使用抢占式的优先级调度策略
* 4 说明
* 高优先级的线程可抢占低优先级线程的cpu执行权。但只是从概率上将,高优先级的线程获得cpu执行权的概率大,
* 并不意味着只有高优先级线程执行完毕,低优先级线程才能执行
*/
public class ThreadMethodsTest {
public static void main(String[] args) {
Thread thread = new Thread() {
@Override
public void run() {
// try {
// sleep(2000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " : " + i);
}
}
};
thread.setName("线程一");
System.out.println(thread.getName() +" is alive : " + thread.isAlive());
thread.start();
System.out.println(thread.getName() +" is alive : " + thread.isAlive());
Thread.currentThread().setName("主线程");
for (int i = 0; i < 100; i++) {
if (i == 49) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " : " + i);
}
System.out.println(thread.getName() +" is alive : " + thread.isAlive());
}
}
开发中,优先选择实现Runnable接口创建线程。原因:
联系:public class Thread implements Runnable。
相同点:都需要重写run()方法
举例:假如有100张票,三个售票窗口同时卖票。下面通过此例比较两种创建线程方式的异同。
实现Runnable接口
package com.fanyi.fy.thread_creation;
/**
* @author FanYi
* @datetime 2021/1/24 15:40
*
* 线程创建的两种方式。
* 开发中,优先选择实现Runnable接口创建线程。原因:
* 1 避免了类单继承的局限性
* 2 适合处理多个线程有共享数据的情况
* 联系:public class Thread implements Runnable
*/
class TicketWindow1 implements Runnable {
// 实现Runnable接口有利于资源共享,若是继承Thread类,代码应写成 private static int ticket = 100;
private int ticket = 100;
@Override
public void run() {
while (true) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + " 卖出第 " + ticket + " 张票");
ticket--;
} else {
break;
}
}
}
}
public class TicketWindowTest1 {
public static void main(String[] args) {
TicketWindow1 ticketWindow1 = new TicketWindow1();
Thread thread1 = new Thread(ticketWindow1);
Thread thread2 = new Thread(ticketWindow1);
Thread thread3 = new Thread(ticketWindow1);
thread1.setName("售票窗口1");
thread2.setName("售票窗口2");
thread3.setName("售票窗口3");
thread1.start();
thread2.start();
thread3.start();
}
}
继承Thread类
class TicketWindow2 extends Thread {
private static int ticket = 100;
@Override
public void run() {
while (true) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + " 卖出第 " + ticket + " 张票");
ticket--;
} else {
break;
}
}
}
}
public class TicketWindowTest2 {
public static void main(String[] args) {
Thread thread1 = new TicketWindow2();
Thread thread2 = new TicketWindow2();
Thread thread3 = new TicketWindow2();
thread1.setName("售票窗口1");
thread2.setName("售票窗口2");
thread3.setName("售票窗口3");
thread1.start();
thread2.start();
thread3.start();
}
}
class TicketWindow2 extends Thread { class TicketWindow1 implements Runnable {
private static int ticket = 100; private int ticket = 100;
@Override @Override
public void run() { public void run() {
//…… //……
} }
} }
public class TicketWindowTest2 { public class TicketWindowTest1 {
public static void main(String[] args) { public static void main(String[] args) {
TicketWindow1 ticketWindow1 = new TicketWindow1();
Thread thread1 = new TicketWindow2(); Thread thread1 = new Thread(ticketWindow1);
Thread thread2 = new TicketWindow2(); Thread thread2 = new Thread(ticketWindow1);
Thread thread3 = new TicketWindow2(); Thread thread3 = new Thread(ticketWindow1);
thread1.setName("售票窗口1"); thread1.setName("售票窗口1");
thread2.setName("售票窗口2"); thread2.setName("售票窗口2");
thread3.setName("售票窗口3"); thread3.setName("售票窗口3")
thread1.start(); // 其余代码与左侧相同
thread2.start();
thread3.start();
}
}
比较上述代码,class TicketWindow2 extends Thread,若ticket不加static关键字,则为成员变量,三个线程各有一个成员变量,则总共三百份票,很明显是错误的。而class TicketWindow1 implements Runnable,三个线程实现同一个接口,三个线程共享同一个变量ticket。