程序、进程和线程
程序:为了完成特定的任务,用某种语言编写的一组指令的集合。程序是一段静态的代码,静态对象。
进程:是程序的一次执行过程或正在运行的程序。(进程是一个任务)。进程是一个动态的过程:有产生、存在和消亡的过程——即拥有生命周期。
线程:进程可进一步细化为线程,是一个程序内部的一条执行路径。一个进程可以包含一个或多个线程。
并行与并发
多线程的优点
何时需要多线程
多进程和多线程比较
和多线程相比,多进程缺点:
多进程优点:
Java语言内置了多线程支持:一个Java应用程序实际上是一个JVM进程,JVM进程用一个主线程执行main()方法,在main()内部,又可以启动多个线程。(一个java.exe,至少有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程。)
线程的分类
Java线程分为两类:一种是守护线程,一种是用户线程。
start() 前调用 thread.setDaemon(true) 可以把一个用户线程变为守护线程创建多线程有四种方法,这里有两种,后续java 1.5增加了两种新方法。
Java语言的JVM允许程序运行多个线程,通过 java.lang.Thread类来体现。
创建一个新线程有两种方法:
将一个类声明为 Thread 的子类,子类应重写Thread类的run方法,然后分配并启动子类的实例。
创建线程步骤:
示例:
// 1. 继承于Threadclass MyThread extends Thread{ // 2. 重新run方法 @Override public void run() { System.out.println("child thread name: "+getName()); // 也可以用 Thread.currentThread().getName() // 业务写在这个方法中 }}public class TheadTest { public static void main(String[] args) { // 3. 创建对象 MyThread myThread = new MyThread(); myThread.setName("子线程"); // 设置线程名称 // 4. 通过对象调用 start 方法 myThread.start(); Thread.currentThread.setName("主线程"); // 设置主线程名称 System.out.println("main thread name: "+Thread.currentThread().getName()); }}输出结果:main thread name: 主线程child thread name: 子线程如果要创建多个线程
// 如果创建多个子线程的话,需要MyThread myThread = new MyThread();myThread.start();MyThread myThread2 = new MyThread();myThread2.start();MyThread myThread3 = new MyThread();myThread3.start();多个线程共享一个静态变量:
当多个线程都想要共用一个值时,比如卖票时的票数,可以将类变量设置为 staitc。
start() 方法的作用
run方法创建Thread 的匿名子类
简单一点的话,可以写Thread 的匿名子类
// 创建Thread 的匿名子类new Thread(){ @Override public void run() { System.out.println("test2"); }}.start();使用Java8引入的 lambda
new Thread(()->{ System.out.println("t3");}).start();线程的常用方法
start() 启动当前线程;调用run方法run() 通常需要重写Thread类的此方法,将业务写在此方法中currentThread() 静态方法,返回执行当前代码的线程getName() 获取当前线程的名称setName() 设置当前线程的名称yield() 线程让步,释放当前CPU的执行权。(暂停当前正在执行的线程,给优先级相同或更高的线程让步)join() 当某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到被调用线程执行完为止。(在线程a中调用线程b的join(), 此时线程a进入阻塞状态,直到线程b执行完之后,线程a才结束阻塞状态,继续执行后续程序。)stop() 强制线程生命期结束,不推荐sleep(long millis) 使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)isAlive() 判断当前线程是否存活getPriority() 返回此线程的优先级setPriority(int newPriority) 更改线程的优先级线程的调度
调度策略:
Java的调度方法:
线程的优先级
线程创建时继承父线程的优先级。
低优先及只是获得调用的概率低,并不意味着只有当高优先级的线程执行完以后才会执行低优先级。
第二种创建线程的方式是:创建一个实现 Runnable 接口的类,类实现了run() 方法,将类的对象传递给Thread() 并启动。
创建线程步骤:
示例:
/** * 多线程的创建,方式二,实现Runnable接口的方式 * @author chadJ * @create 2022-03-04 18:49 *///1. 创建一个实现了 Runnable 接口的类class MyThread2 implements Runnable{ //2. 实现类去实现 Runnable 的抽象方法 run() @Override public void run() { System.out.println("t2"); }}public class ThreadRunnable { public static void main(String[] args) { //3. 创建实现类的对象 MyThread2 t2 = new MyThread2(); //4. 将对象作为参数传递到 Thread 类的构造器中,创建 Thread 类的对象 Thread thread = new Thread(t2); //5. 通过 Thread 类的对象调用 start() 方法 thread.start(); }}如果要创建多个线程
// 如果创建多个子线程的话// t2 对象可以直接使用,也能共享里面的变量,因为只有一个对象MyThread2 t2 = new MyThread2();Thread thread = new Thread(t2);thread.start();Thread thread2 = new Thread(t2);thread2.start();Thread thread3 = new Thread(t2);thread3.start();start()方法如何执行到run
这里,最后的 start() 方法,是怎么执行到 MyThread2 中的 run() 方法的呢。原来在
Thread.run中有如下代码,如果target 不为空,则执行 target 的run() 方法
public void run() { if (target != null) { target.run(); } }其中,target 是我们调用 Thread(t2) 时通过构造函数传过去的 Runnable
public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0);}一种是继承Thread, 一种是实现Runnable 接口。
开发中,优先选择:实现 Runnable 接口的方式,
原因:
联系:Thread 也实现的 Runnable。
相同点:两种方式都需要重写 run() 方法,将线程要执行的逻辑声明在 run() 中。
JDK5.0 新增两种线程创建方式:实现 Callable 接口、使用线程池 方式。
与使用 Runnable 相比,Callable 功能更强大:
相比 run() 方法,可以有返回值
方法可以抛出异常(之前的run()不可以抛出,因为原方法没抛出异常,重写的不能抛出)
支持泛型的返回值
需要借助 FutureTask 类,比如获取返回结果
Futrue 接口
创建线程步骤:
call() 方法,将业务放在call()中。可以有返回值,可以抛出异常start()get() 获取call() 中的返回值。( get() 返回值即为 FutureTask 构造器参数 Callable 实现类重写 call()方法的返回值。)package com.acfuu.java;import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.FutureTask;/** * 创建线程方式三:实现Callable接口 * @author chadJ * @create 2022-03-06 14:11 */// 1.创建一个实现 Callable 的实现类class NumThread implements Callable{ // 2.实现 call() 方法,将业务放在call()中。可以有返回值,可以抛出异常 @Override public Object call() throws Exception { int sum=0; for (int i = 0; i <= 100; i++) { sum+=i; } return sum; }}public class CallableThread { public static void main(String[] args) { // 3. 创建 Callable 接口实现类的对象 NumThread numThread = new NumThread(); // 4. 将此 Callable 实现类的对象作为参数传递 FutureTask 构造器中,创建 FutureTask 对象 FutureTask futureTask = new FutureTask(numThread); // 5. 将FutureTask 对象作为参数传递到 Thread类构造器中,创建 Thread 对象,并调用 start() new Thread(futureTask).start(); try { // 6. 可以使用 get() 获取call() 中的返回值。 // get() 返回值即为 FutureTask 构造器参数 Callable 实现类重写 call()方法的返回值。 Object r = futureTask.get(); System.out.println("返回值:"+r); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } }}背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建和销毁,实现重复利用。
好处:
线程池相关 API
JDK5.0起提供了线程池API:ExecutorService 和 Executors
ExexutorService:真正的线程池接口。常见子类 ThreadPoolExecutor
void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行 Runnable<T> Future<T> submit(Callable<T> task):执行任务,有返回值,一般用来执行 Callablevoid shutdown():关闭连接池Executors: 工具类,线程池的工厂类,用于创建并返回不同类型的线程池
Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池Executors.newFixedThreadPool():创建一个可重用固定线程池数的线程池(常用)Executors.newSingleThreadExecutor():创建一个只有一个线程的线程池Executors.newScheduledThreadPool(n):在给定延迟后创建一个线程池线程创建步骤:
示例:
package com.acfuu.java;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.ThreadPoolExecutor;/** * 创建线程方式四:使用线程池 * @author chadJ * @create 2022-03-06 14:47 */class NumThread2 implements Runnable{ private boolean odd = true; public NumThread2(boolean odd) { this.odd = odd; } @Override public void run() { for (int i = 0; i <= 10; i++) { if(odd){ if(i%2==1) System.out.println(Thread.currentThread().getName()+":"+i); } else{ if(i%2==0) System.out.println(Thread.currentThread().getName()+":"+i); } } }}public class ThreadPool { public static void main(String[] args) { // 1. 提供指定线程数量的线程池 ExecutorService executorService = Executors.newFixedThreadPool(10); // 设置线程池属性 // System.out.println(executorService.getClass()); // ThreadPoolExecutor executor = (ThreadPoolExecutor) executorService; // executor.setCorePoolSize(1); // 设置为1后,线程池中就不是10个线程,就只有一个线程了 // 2. 执行指定的线程操作,需要提供实现 Runnable 接口或 Callable 接口实现类的对象 executorService.execute(new NumThread2(true)); // 适用于 Runnable executorService.execute(new NumThread2(false)); //executorService.submit(xxx); // 适用于 Callable // 3. 关闭连接池 executorService.shutdown(); }}输出:pool-1-thread-1:1pool-1-thread-2:0pool-1-thread-2:2pool-1-thread-2:4pool-1-thread-2:6pool-1-thread-2:8pool-1-thread-2:10pool-1-thread-1:3pool-1-thread-1:5pool-1-thread-1:7pool-1-thread-1:9JDK 中的Thread.State 类定义了线程的状态。一个线程对象只能调用一次 start() 方法启动新线程(所以不能一个Thread对象多次start),并在新线程中执行 run() 方法。一旦 run() 执行完毕,线程就结束了。
Java 线程的状态有:
run()方法的代码sleep() 方法正在计时等待run() 执行完毕线程声明周期:

线程的同步是为了解决线程的安全问题。
线程的同步是为了解决线程的安全问题。
问题:比如卖票出现重票、错票,这就叫出现了线程的安全问题。
解决:当一个线程a在操作的时候,其他线程不能参与进来,直到线程a操作结束其他线程才可以开始操作。即使线程a出现了阻塞,也不能执行其他线程。
线程的安全问题
在多线程模型下,要保证逻辑正确,对共享变量进行读写时,必须保证一组指令以原子方式执行:即某一个线程执行时,其他线程必须等待。通过加锁和解锁操作,就能保证指令总是在一个线程执行期间,不会有其他线程会进入此指令区间。即使在执行期间被操作系统中断执行,其他线程也会因为无法获得锁导致无法进入此指令区间。只有释放后,其他线程才有机会获得锁并执行。这种加锁和解锁之间的代码块我们称之为临界区,任何时候临界区最多只有一个线程能执行。
在Java中,通过同步机制,解决线程的安全问题。
使用synchronized (发音 /'s??kr?na?zd/),有两种方法:
synchronized(lock) { // lock锁也叫同步监视器 // 需要被同步的代码。(操作共享数据的代码)}任何一个类的对象都可以充当锁。要求:多个线程必须共用同一把锁。在实现Runnable 接口创建多线程的方式中,可以考虑使用 this 充当 lock。在继承Thread 类创建多线程的方式中,(慎用this充当lock),考虑使用当前类充当lock。(比如 MyThread.class)同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。
需要同步的方法使用 synchronized 修饰:
1.在实现 Runnable 接口的方式中:
此种方法中,lock 是 this。
public synchronized void test(){ // 在test中,同步监视器/锁就是 this...}public void run(){ ... test(); ...}`run()` 方法也可以使用 synchronized修饰, `sysnchronized void run()`,这种要确保 `run()`中使完整的同步数据。不然包裹的太多,效率会低。2.在继承 Thread 类的方式中:为了保证正常执行,需要多加个 static 。此种方法中,lock 是 当前类。public static synchronized void test2(){ // 在test2中,同步监视器/锁就是 当前类...}public void run(){ ... test2(); ...}总结:- 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明- 非静态的同步方法,同步监视器是 *this*- 静态的同步方法,同步监视器是 *当前类本身*。线程同步优缺点
只有有共享数据的代码才需要使用线程的同步解决问题。
从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁来实现同步。同步锁使用Lock对象充当。
1.使用ReentrantLock
public class LockTest { public static void main(String[] args) { Window w = new Window(); Thread t1 = new Thread(w); Thread t2 = new Thread(w); Thread t3 = new Thread(w); t1.start(); t2.start(); t3.start(); }}class Window implements Runnable{ private int ticket = 100; // 1. 实例化 ReentrantLock private final ReentrantLock lock = new ReentrantLock(); // 参数 fair=true/false, 公平或者非公平 @Override public void run() { while(true){ try { // 2. 调用lock方法 lock.lock(); if(ticket>0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+" 票号:"+ticket); ticket--; }else{ break; } } finally { // 3. 解锁方法 lock.unlock(); } } }class Bank{ private Bank(){} private static Bank instance = null; // 普通获取实例的方法 public static Bank getInstance(){ if(instance==null){ instance = new Bank(); } return instance; } // 线程安全的获取实例的方法一 public static synchronized Bank getInstance2(){ if(instance==null){ instance = new Bank(); } return instance; } // 线程安全的获取实例的方法二,其中还有两种写法,后一种效率高些 public static Bank getInstance3(){ // 效率不高 /* 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; }}死锁:
说明:
解决方法:
示例,下面的代码会出现死锁,程序没输出没结束。在第一个线程运行到sleep的时候,线程二继续运行,结果都需要sb1 sb2,产生了死锁。
public static void main(String[] args) { StringBuffer sb1 = new StringBuffer(); StringBuffer sb2 = new StringBuffer(); new Thread(()->{ synchronized (sb1){ sb1.append("a"); sb2.append("1"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (sb2){ sb1.append("b"); sb2.append("2"); System.out.println(sb1); System.out.println(sb2); } } }).start(); new Thread(new Runnable() { @Override public void run() { synchronized (sb2){ sb1.append("c"); sb2.append("3"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (sb1){ sb1.append("d"); sb2.append("4"); System.out.println(sb1); System.out.println(sb2); } } } }).start(); }多线程协调(线程通信)问题使用 wait() 和 notify()。
示例:两个线程交替打印1-100
/** * 线程通信的示例:两个线程交替打印1-100 * @author chadJ * @create 2022-03-05 22:31 */class Number implements Runnable{ private int number = 1; @Override public void run() { while(true){ synchronized (this) { // 唤醒其他线程 notify(); if(number<101){ System.out.println(Thread.currentThread().getName()+":"+number); number++; try { // 使得调用wait()方法的线程进入阻塞状态,并且wait回释放锁 wait(); } catch (InterruptedException e) { e.printStackTrace(); } }else{ break; } } } }}public class ThreadCommunication { 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(); }}线程通信涉及到的方法:
wait() 一旦执行此方法,当前线程进入阻塞状态,并释放同步监视器(锁)notity() 执行此方法,会唤醒被wait的一个线程,如果有多个线程被wait,就唤醒优先级最高的线程notifyAll() 执行此方法,会唤醒所有被 wait 的线程说明:
wait() notify() notifyAll() ,必须使用在同步代码块或同步方法中。Lock想要线程通信使用其他方法wait() notify() notifyAll() 的调用者必须使同步代码块或同步方法中的同步监视器,否则会出现 IllegalMonitorStateException。比如class Test implements Runnable{ ... public void run() { synchronized (this) { notify(); ... wait(); }}或class Test implements Runnable{ ... public void run() { synchronized (obj) { obj.notify(); ... obj.wait(); }}java.lang.Object 类中的。(从上一条可知,任何一个对象都要能够调用 wait() notify() notifyAll() 方法)描述:生产者将产品交给店员,消费者从店员处取走商品,店员一次能持有产品数量有容量限制,如果生产者试图生产更多的产品,店员会叫停生产者,如果有空位了再通知生产者继续生产。如果店中没有商品,店员会叫停消费者,如果有产品了再通知消费者来取走产品。
分析:
/** * 线程通信应用,生产者、消费者问题。 实现Runnable方式 * @author chadJ * @create 2022-03-06 13:13 *//** * 店员类 */class Clerk{ // 商品数量 private int goodsNum = 0; public synchronized void createProduct() { if(goodsNum<20){ goodsNum++; System.out.println(Thread.currentThread().getName()+": 生产商品 "+goodsNum); notify(); }else{ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } public synchronized void useProduce() { if(goodsNum>0){ System.out.println(Thread.currentThread().getName()+": 消费商品 "+goodsNum); goodsNum--; notify(); }else{ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } }}class Productor implements Runnable{ private Clerk clerk; public Productor(Clerk clerk) { this.clerk = clerk; } @Override public void run() { System.out.println(Thread.currentThread().getName()+":开始生产商品"); while(true){ try { Thread.sleep(30); // sleep模拟生产过程耗费的时间 } catch (InterruptedException e) { e.printStackTrace(); } clerk.createProduct(); } }}class Customer implements Runnable{ private Clerk clerk; public Customer(Clerk clerk) { this.clerk = clerk; } @Override public void run() { System.out.println(Thread.currentThread().getName()+":开始消费商品"); while(true){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } clerk.useProduce(); } }}public class ProductorCustomerTest { public static void main(String[] args) { Clerk clerk = new Clerk(); Thread p1 = new Thread(new Productor(clerk)); p1.setName("生产者"); p1.start(); Thread c1 = new Thread(new Customer(clerk)); c1.setName("消费者1"); c1.start(); Thread c2 = new Thread(new Customer(clerk)); c2.setName("消费者2"); c2.start(); Thread c3 = new Thread(new Customer(clerk)); c3.setName("消费者3"); c3.start(); }}synchronized 与 Lock 的异同?
相同:二者都可以解决线程安全的问题
不同:
优先使用顺序:(建议)
Lock → 同步代码块(已经进入了方法体,分配了相应的资源)→ 同步方法(方法体之外,作用整个方法)
如何解决线程安全问题,有几种方式
同步机制说两种三种都可以,要都讲到
两种:synchronized方式(又分同步代码块和同步方法)和Lock方式
三种:synchronized 同步代码块、synchronized 同步方法 和 Lock 方式
sleep() 和 wait() 的异同?
相同点:都可以让线程进入阻塞状态
不同点:
sleep();Object 类中声明的 wait()sleep() 可以在任何需要的场景下调用;wait() 必须使用在同步代码块或同步方法中。sleep()不会释放锁,wait() 会释放锁。如何理解实现 Callable 接口的方式创建多线程比实现 Runnable 接口创建多线程方式更强大?
call() 可以有返回值。可以抛出异常,被捕获后获取异常信息创建多线程有几种方式?
四种:继承Thread类,实现Runnable接口,实现 Callable 接口,使用线程池(后两种为JDK5.0 新增)。