前言:
进程:运行中的程序的实例(例如应用程序)。
线程:进程中的执行单元。
关系:一个进程可以包含多个线程,线程是进程的一部分。
区别:
(1)进程拥有独立的内存空间和资源,而线程共享进程的内存空间和资源。
(2)进程间的切换开销较大,需要保存和恢复整个进程的上下文,而线程的切换开销较小,只需保存和恢复线程的上下文。
(3)多个线程可以并发地执行,从而提高程序的响应性能,而进程之间的并发性需要通过多进程或进程间通信来实现。
(4)线程之间共享内存,可以方便地访问共享数据,但也需要考虑线程安全问题;而进程之间的访问需要通过进程间通信进行,开销相对较大。
1.多线程实现的三种方式
1.1 继承Thread类的方式进行实现
import java.util.Scanner;
import java.awt.*;
public class Test5 {
public static void main(String[] args) {
Dage t1=new Dage("hello");
Dage t2=new Dage("world");
t1.start();
t2.start();
}
}
class Dage extends Thread {
private String name;
public Dage(String name) {
this.name = name;
}
public void run() {
System.out.print(name+" ");
}
}
1.2 实现Runnable接口的方式进行实现
import java.util.Scanner;
import java.awt.*;
public class Test1 {
public static void main(String[] args) {
Thread t1 =new Thread(new Dage("hello"));
Thread t2 =new Thread(new Dage("world"));
t1.start();
t2.start();
}
}
class Dage implements Runnable {
private String name;
public Dage(String name) {
this.name = name;
}
public void run() {
System.out.print(name+" ");
}
}
1.3 利用Callable接口和Future接口方式实现
import java.util.Scanner;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.awt.*;
public class Test5 {
public static void main(String[] args) throws Exception {
Dage ge=new Dage();
FutureTask<String> future=new FutureTask<>(ge);
Thread t1=new Thread(future);
t1.start();
String a=future.get();
System.out.println(a);
}
}
class Dage implements Callable<String> {
public String call() throws Exception {
String s="hello world";
return s;
}
}
1.4 扩展写法
public class Test03 {
public static void main(String[] args) throws InterruptedException {
Dage t1=new Dage("hello");
Dage t2=new Dage("world");
t1.start();
t2.start();
Thread tt=new Thread(){
@Override
public void run() {
System.out.print(" 匿名内部类 ");
}
};
//匿名内部类
tt.start();
//lambda写法
Thread ttt=new Thread( ()->{
System.out.println(" 学习 ");
});
ttt.start();
System.out.println(" play game ");
}
//内部类
static class Dage extends Thread {
private String name;
public Dage(String name) {
this.name = name;
}
@Override
public void run() {
System.out.print(name+" ");
}
}
}
问题:
在上面三种方式中,不难发现,创建两线程的话,可能会出现打印出“world hello”的情况!,这与它们抢占CPU的情况有关,如何解决这一现象呢?
1.4 可以考虑线程的优先级,具体代码如下:
public class Test6{
public static void main(String[] args) {
Dage t1=new Dage("hello");
Dage t2=new Dage("world");
t1.start();
t2.start();
t1.setPriority(10);
t2.setPriority(1);
}
}
class Dage extends Thread {
private String name;
public Dage(String name) {
this.name = name;
}
public void run() {
System.out.print(name+" ");
}
}
(说明:这样只是提高打印出“hello world"的概率但不是绝对的,所以适用性不强)
1.5 优先级不是绝对的!,那只能考虑插入线程了,具体代码如下:
public class Test6{
public static void main(String[] args) throws InterruptedException {
Dage t1=new Dage("hello");
Dage t2=new Dage("world");
t1.start();
t1.join();
t2.start();
}
}
class Dage extends Thread {
private String name;
public Dage(String name) {
this.name = name;
}
public void run() {
System.out.print(name+" ");
}
}
备注:这样总算解决了,多线程有许多常见的成员方法,解这个问题不限这一个方法。
2.经典多线程案例"卖火车票”
问题描述:某火车站出售100张火车票,有三个窗口在卖,请设计一个程序模拟卖票。
2.1 直接上代码(猜猜有没有问题?),代码如下:
public class Test6{
public static void main(String[] args){
Dage t1=new Dage();
Dage t2=new Dage();
Dage t3=new Dage();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Dage extends Thread {
int ticket=0;
public void run() {
while(true){
if(ticket<100){
ticket++;
System.out.println(getName()+"正在卖第"+ticket+"张票");
}else{
break;
}
}
}
}
问题:运行程序不难发现三个窗口竟然都卖了100张票,加起来有300张,看来这程序需要优化。
2.1 多线程加锁,具体代码如下:
public class Test6{
public static void main(String[] args){
Dage t1=new Dage();
Dage t2=new Dage();
Dage t3=new Dage();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Dage extends Thread {
static int ticket=0;
static Object abc=new Object();
public void run() {
while(true) {
synchronized (abc) {
if (ticket < 100) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ticket++;
System.out.println(getName() + "正在卖第" + ticket + "张票");
} else {
break;
}
}
}
}
}
2.在上面打印“hello word”的两线程中,不难发现它们之间竟然没有什么联系!,这可不符合多线程存在的意义,所以优化一下双线程,于是有了一个简单的“猜数字”游戏的程序,一个线程负责输入猜的数字,一个线程负责判断猜的情况,案例具体代码如下:
import java.util.Scanner;
public class Test4{
public static void main(String[] args) {
Num n = new Num();
CreateThread create = new CreateThread(n);
JudgeThread judge = new JudgeThread(n);
judge.start();
create.start();
}
}
// Num类完成线程之间的同步
class Num {
int number;// 保存猜测的数
boolean first = true;
boolean stop = false;
}
//输入猜测的数
class CreateThread extends Thread {
Num num;
public CreateThread(Num num) {
this.num = num;
}
public void run(){
Scanner sc = new Scanner(System.in);
while (true) {
try {
synchronized (num) {
if (num.first) {// 如果是第一次执行
num.wait();
} else if (num.stop) {
System.out.println("猜数结束");
return;
} else {
System.out.print("请输入猜测的数(1-100):");
int guess = sc.nextInt();
// 猜数结束就需要等待,判断线程
num.number = guess;
num.notify();
num.wait();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
//判断线程
class JudgeThread extends Thread {
Num num;
public JudgeThread(Num num) {
this.num = num;
}
public void run() {
int random = 0;
while (true) {
try {
synchronized (num) {
if (num.first) {
random = (int) (Math.random() * 100 + 1);
// System.out.println("产生的随机数为:" + random);
num.first = false;
num.notify();
num.wait();
} else {
// 判断过程
if (num.number < random) {
System.out.println("猜小了!");
} else if (num.number > random) {
System.out.println("猜大了!");
} else {
System.out.println("猜对了!");
num.stop = true;
num.notify();// 猜对结束之前
return;
}
num.notify();
num.wait();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
3.线程池
线程池是一种多线程处理的机制,它包含了一定数量的线程,当有任务需要处理时,就从线程池中取出一个线程来处理任务。在任务处理完成后,该线程并不会立即退出,而是继续留在线程池中等待下一次任务的到来。这样可以避免频繁创建和销毁线程所带来的开销,提高了程序的效率。
线程池通常由两个部分组成:工作线程和任务队列。工作线程是线程池中的线程,它们用于执行具体的任务;任务队列是用来存储任务的队列,当有新的任务到达时,就将任务加入到队列中等待被执行。
线程池的主要优点如下:
- 线程复用:减少线程创建和销毁的开销,提高程序的效率;
- 控制并发度:通过限制线程池中的线程数量,可以有效地控制系统的并发度,避免资源竞争和阻塞;
- 提高响应速度:线程池可以预先创建一定数量的线程,当有任务到达时,可以立即执行,从而提高系统的响应速度。
具体原理见图解:
代码模板:
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
//线程池:系统的渐状性
public class Test05 {
public static void main(String[] args) {
//核心线程池的大小
int corePoolSize=2;
//核心线程池的最大数量
int maxPoolSize=4;
//线程最大空闲时间
long keepAliveTime=10;
//时间单位:秒
TimeUnit unit=TimeUnit.SECONDS; //enum枚举,常量
//有界阻塞队列 容量为2 最多允许放入两个空闲任务
BlockingQueue<Runnable> workQueue=new ArrayBlockingQueue<>(2);
// 线程创建工厂
ThreadFactory threadFactory=new NameTreadFactory();
//线程池拒绝策略
RejectedExecutionHandler handler=new MyIgnorePolicy();
ThreadPoolExecutor executor=null;
try{
//推荐的创建线程池的方式, 不推荐的使用现成API创创建线程池,
executor=new ThreadPoolExecutor(corePoolSize,maxPoolSize,keepAliveTime,unit,workQueue,threadFactory,handler);
//预启动所有核心线程 提升效率
executor.prestartAllCoreThreads();
//任务数量
int count=10;
for (int i =1; i <=count; i++) {
RunnableTask task=new RunnableTask(String.valueOf(i));
executor.submit(task);//提交任务到线程池 (如果任务为10,则有4个任务无法执行)
}
}finally {
assert executor!=null; //断言, 可开关
executor.shutdown();
}
}
static class NameTreadFactory implements ThreadFactory{
//线程id AtomicInteger 原子类
private final AtomicInteger threadId=new AtomicInteger();
@Override
public Thread newThread(Runnable runnable){
Thread t=new Thread(runnable,"线程"+threadId.getAndIncrement());
System.out.println(t.getName()+"已经被创建");
return t;
}
}
public static class MyIgnorePolicy implements RejectedExecutionHandler{
@Override //被拒绝任务 线程池对象
public void rejectedExecution(Runnable runnable,ThreadPoolExecutor e){
doLog(runnable,e);
}
private void doLog(Runnable runnable,ThreadPoolExecutor e){
System.err.println("线程池:"+e.toString()+runnable.toString()+"被拒绝执行");
}
}
//任务类
static class RunnableTask implements Runnable{
private String name;
public RunnableTask(String name){
this.name=name;
}
@Override
public void run(){
try{
System.out.println(this.toString()+"is running");
Thread.sleep(3000);
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public String toString(){
return "RunnableTask [name="+name+"]";
}
}
}