进程与线程
进程(软件)
- 是指一个在内存中运行的应用程序,每个进程都有一个独立的内存空间
线程(软件的执行路径)
- 是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行,一个进程最少有一个线程
- 线程实际上是进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程
线程调度
分时调度
- 所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间
抢占式调度
- 优先让优先级高的线程使用CPU,如果线程优先级相同,那么会随机选择一个,Java使用的是抢占式调度
- 多线程并不能提高程序的运行速度,但能够提高程序的运行效率,让CPU的使用率更高
同步与异步
同步:排队执行,效率低但安全
异步:同时执行,效率高但是数据不安全
并发与并行
并发:指多个事件在同一时间段内发生
并行:指多个事件在同一时刻发生(同时发生)
Java实现多线程的两种方式
1. 继承Thread
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("新线程"+i);
}
}
}
复写run方法来编写新线程的任务,通过调用start()方法开启新线程
实现Runnable接口
public class RunnableImpl implements Runnable{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("新线程执行了");
}
}
}
//1. 创建一个任务对象
RunnableImpl r = new RunnableImpl();
//2. 创建一个线程并为其分配一个任务
Thread t = new Thread(r);
//3. 执行这个线程
t.start();
for (int i = 0; i < 10; i++) {
System.out.println("主线程被执行了");
}
实现Runnable接口与继承Thread相比有如下优势
- 通过创建任务然后给线程分配的方式来实现多线程,更适合多个线程执行相同任务的情况
- 可以避免单继承带来的局限性
- 任务与线程本身是分离的,提高了程序的健壮性
- 线程池技术,接受Runnable类型的线程,不接受Thread类型的线程
线程阻塞
线程的所有消耗时间的操作都可以看作是阻塞,常见的如文件的读取,读取用户输入等
线程中断
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//直接return则线程自杀,不处理则线程继续执行
System.out.println("继续执行");
}
}
}
});
t.start();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
//给t添加中断标记,会抛出InterruptedException异常,进入上面的catch块,如何处理由程序员在catch块中决定
t.interrupt();
守护线程
线程分为守护线程和用户线程
守护线程用于守护用户线程,当所有用户线程结束时,守护线程自动死亡
当一个进程不包含任何存活的用户线程时,进行结束
通过对用户线程执行setDaemon(true)将该线程设置为守护线程,在用户线程全部结束时,守护线程自动结束
线程安全问题
- 多个线程同时使用使用修改同一资源,容易产生线程不安全问题
-
同步代码块(隐式锁)
Object o = new Object(); synchronized(o){ }
-
同步方法(隐式锁)
- 如果不是静态的同步方法它的锁是this(调用它的对象)
- 如果是静态方法同步方法的锁是该类的.class
public synchronized boolean 方法名(){
}
- 显式锁
private Lock l =new ReentrantLock();
l.lock();//调用lock方法上锁
l.unlock();//调用unlock方法解锁
公平锁与不公平锁
公平锁:先来先到,排队执行(A线程先到,解锁后则A线程先获得锁)
不公平锁:抢占式
- 显式锁中fair参数为true即为公平锁
线程死锁
- 在任何出现锁的的方法中不要再调用出现锁的方法,不然有可能会出现死锁的情况
多线程通信问题
- 生产者与消费者问题
- 生产着消费者问题的场景是:消费者消费生产者生产出并且放在队列里面的产品,如果产品用完了消费者需要等待,如果队列满了,生产者等待。
public class Demo4 {
/**
* 多线程通信问题, 生产者与消费者问题
* @param args
*/
public static void main(String[] args) {
Food f = new Food();
new Cook(f).start();
new Waiter(f).start();
}
//厨师
static class Cook extends Thread{
private Food f;
public Cook(Food f) {
this.f = f;
}
@Override
public void run() {
for(int i=0;i<100;i++){
if(i%2==0){
f.setNameAndSaste("老干妈小米粥","香辣味");
}else{
f.setNameAndSaste("煎饼果子","甜辣味");
}
}
}
}
//服务生
static class Waiter extends Thread{
private Food f;
public Waiter(Food f) {
this.f = f;
}
@Override
public void run() {
for(int i=0;i<100;i++){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
f.get();
}
}
}
//食物
static class Food{
private String name;
private String taste;
//true 表示可以生产
private boolean flag = true;
public synchronized void setNameAndSaste(String name,String taste){
if(flag) {
this.name = name;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.taste = taste;
flag = false;
this.notifyAll();
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void get(){
if(!flag) {
System.out.println("服务员端走的菜的名称是:" + name + ",味道:" + taste);
flag = true;
this.notifyAll();
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
线程的六种状态
- NEW
- 线程刚刚被创建但是还未启动
- RUNNABLE
- 在JVM中执行的线程处于此状态
- BLOCKED
- 被阻塞等待监视器锁定的线程属于此状态
- WAITING
- 无限期等待另一个线程执行特定操作的线程处于此状态
- TIMEDWAITTING
- 正在等待另一个线程执行最多指定等待时间的的线程处于此状态
- TERMINATED
- 已退出的线程处于此状态
带返回值的线程Callable
Runnable 与 Callable
//Callable接口
public interface Callable {
V call() throws Exception;
} //Runnable接口
public interface Runnable {
public abstract void run();
}
Callable使用步骤
1. 编写类实现Callable接口 , 实现call方法
class XXX implements Callable {
@Override
public call() throws Exception {
return T;
}
}
2. 创建FutureTask对象 , 并传入第一步编写的Callable类对象
FutureTask future = new FutureTask<>(callable);
3. 通过Thread,启动线程
new Thread(future).start();
Runnable 与 Callable的相同点
都是接口 都可以编写多线程程序 都采用Thread.start()启动线程
Runnable 与 Callable的不同点 Runnable没有返回值;Callable可以返回执行结果
Callable接口的call()允许抛出异常;Runnable的run()不能抛出 Callable获取返回值
Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执 行,如果不调用不会阻塞。
线程池
缓存线程池
长度无限制
任务加入后的执行流程
1. 判断线程池是否存在空闲线程
2. 存在则使用
3. 不存在,则创建线程,并放入线程池,然后使用
//创建缓存线程池
ExecutorService service = Executors.newCachedThreadPool();
//向线程池中加入新的任务
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"执行了");
}
});
定长线程池
//创建固定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
单线程线程池
//创建单线程的线程池
ExecutorService service = Executors.newSingleThreadExecutor();
周期定长线程池
//创建一个有两个线程周期定长线程池
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
/**
* 1. 定时执行一次
* 参数1. 定时执行的任务
* 参数2. 时长数字
* 参数3. 时长数字的时间单位,TimeUnit的常量指定
*/
service.schedule(new Runnable() {
@Override
public void run() {
System.out.println("被执行了");
}
},3, TimeUnit.SECONDS);
/**
* 2. 周期性执行任务
* 参数1 任务
* 参数2 延长时间数字(第一次执行是在什么时间以后)
* 参数3 周期时长数字(每隔多久执行一次)
* 参数4 时长数字单位
*/
service.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
System.out.println("周期性任务被执行了");
}
},3,1,TimeUnit.SECONDS);
Lambda表达式
- 当接口只有一个抽象方法的时候才可以使用Lambda表达式
- 如果任务只有一行代码,可以同时省略大括号和分号(有大括号必须有分号)
Thread t = new Thread(()->{
System.out.println("使用Lambda表达式实现的线程任务被执行了。");
});
t.start();
在网络编程中使用多线程
//为每一个连接的客户端开启一个线程,并与其交互
ServerSocket serverSocket = new ServerSocket(55555);
while (true){
Socket socket = serverSocket.accept();
new Thread(new Runnable() {
@Override
public void run() {
try {
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os);
ps.println("欢迎来到我的服务器");
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String text = br.readLine();
System.out.println(text);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}