程序
:为完成特定任务,用某种编程语言编写的一组指令的结合(一段静态代码,静态对象)
进程
:是程序的一次执行的过程或是正在运行的一个程序,是一个动态的过:有他自身的产生、存在、消亡的过程(生命周期),进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域。
线程
:进程可以进一步细化为线程,是一个程序内部的一条执行路径。
知识小盲区:
若一个进程同一时间并行多个线程,就是支持多线程的。
线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(PC),线程切换的开销小。
一个进程的多个线程共享相同的内存单元、内存地址空间—>他们从同一堆中分配对象,可以访问相同的变量和对象。就使得线程间通信更加简便,高效。但多个线程操作共享的系统资源可能就会带来安全的隐患。
线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(PC)。
本地方法栈— native
虚拟机栈和PC 每一个线程都有一份
方法区和堆 是一个进程一份
一个进程中有多个线程 多个线程共享方法区和堆
单核CPU,是一种假的多线程
多核的话,能更好地发挥多线程的效率
并行
:多个CPU同时执行多个任务 ,例如:多个人同时做不同的事儿
并发
: 一个CPU同时执行多个任务 ,例如:秒杀多个人同时做一件事儿
提高应用程序的响应。对图形化界面更有意义,可增强用户体验
提高计算机系统CPU的利用率
改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
java语言的JVM允许程序运行多个线程通过继承 java.lang.Thread
多线程的创建
方式一 继承Thread类
//1.创建一个继承Thread类的子类
class MyThread extends Thread {
//2.重写Thread类的run();
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//3.创建Thread类的子类的对象
MyThread t1 = new MyThread();
//4.通过对象调用start(); 两个:启动当前线程,调用当前线程的run方法
t1.start();//通过t1调用run 是不可以的 啊
//以下方法仍然是在main线程中执行的
System.out.println("hello");
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i + "main");
}
}
t1.start();
}
}
为什么是通过对象调用start();方法?
答:
通过t1调用run 是不建议的
void start()
:启动线程,并执行对象run方法
run()
:线程被调度时执行的操作
String getName()
:返回线程的名字
void setNAme (String name )
:设置该线程的名称
static Thread currentThread()
:返回当前线程。在Thread子类中就是this ,通常用于主线 程和Runnable实现类
static void yield()
:线程让步
暂停当前正在执行的线程,把执行的机会让给优先级相同的或者更高的线程
join()
:当某个成序执行流中调用其他线程的join();方法,调用线程将被阻塞,直到join方法加 入的join线程执行完为止
static void sleep(long millitime)
:令当前活动线程在指定时间段内放弃对CPU的控 制,是其他线程有机会被执行,时间到后重新排队
stop()
:强制线程生命期结束,不推荐使用
boolean isAlive()
:返回boolean,判断线程是否还活着
调度策略
java调度方法
线程的优先级等级
MAX_PRIOPITY :10
MIN_PRIOPRITY:1
NPRM_PRIORITY:5
涉及的方法:
getPriority:返回县城优先值
setPriority(int newPriority): 改变线程的优先级
说明:
线程创建时继承父线程的优先级
低优先级只是获得调度的概率低,并非一定是高优先级线程被调度后才会被调用
实现Runnable接口
创建一个实现了Runnable接口的类
子类中重写Runnable接口中的run方法
创建实现类的对象/通过Thread类含参构造器创建线程对象
将Runnable接口的子类对象作为实现参数传递给Thread类的构造器中
通过Thread类的对象调用start();方法:开启线程,调用Runnable子类接口的run方法
//1.创建一个实现了Runnable接口的类
class MThread implements Runnable{
//2.实现类去实现Runnable中的抽象方法run();
@Override
public void 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) {
//3.创建实现类的对象
MThread mThread = new MThread();
// 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
Thread t1 = new Thread(mThread);
// 5.通过Thread类的对象调用start();方法
t1.setName("线程1");
t1.start();
//这是t1线程
//再启动一个线程
Thread t2 = new Thread(mThread);
t2.start();
}
}
通过Thread类的对象调用start()方法
两个功能 :
比较线程的两种方式
开发当中优先选择实现Runnable接口的方式
原因
1:实现的方式没有类的单继承的局限性
2:实现的方式更适合来处理多个线程有共享数据的情况
联系
:public class Thread implements Runnable
相同点
:都是需要去重写run(),并且将线程要执行的逻辑声明在run()中
线程的分类:
java中,线程分两种:守护线程
和用户线程
守护线程依赖用户线程
问题一
:
我们不能直接通过调用run方法的方式启动线程
我们要调用start
问题二
:
再启动一个线程,遍历100以内的偶数
应该再创建一个对象
IllegalThreadStartException
不可以让已经start的线程去再执行
要想创建多个线程就要创建多个对象
创建Thread类的匿名子类的方式
通过Callable和Future创建线程
实现步骤:
①
创建Callable接口的实现类,并实现call()方法,改方法将作为线程执行体,且具有返回值。
②
创建Callable实现类的实例,使用FutrueTask类进行包装Callable对象,FutureTask对象封装了Callable对象的call()方法的返回值
③
使用FutureTask对象作为Thread对象启动新线程。
④
调用FutureTask对象的get()方法获取子线程执行结束后的返回值。
继承Thread类和实现Runnable接口、实现Callable接口的区别
继承Thread
:线程代码存放在Thread子类run方法中。
优势:编写简单,可直接用this.getname()获取当前线程,不必使用Thread.currentThread()方法。
劣势:已经继承了Thread类,无法再继承其他类。
实现Runnable
:线程代码存放在接口的子类的run方法中。
优势:避免了单继承的局限性、多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。
劣势:比较复杂、访问线程必须使用Thread.currentThread()方法、无返回值。
实现Callable
:
优势:有返回值、避免了单继承的局限性、多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。
劣势:比较复杂、访问线程必须使用Thread.currentThread()方法
建议使用实现接口的方式创建多线程
新建 NEW
:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
就绪RUNNABLE
:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
运行BLOCKED
:当就绪的线程被调度并获得CPU资源时便进入运行状态,run()方法定义了线程的操作和功能
阻塞WAITING
:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态
死亡
:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
同步方法
同步代码块
Lock
小案例
创建三个窗口买票
总票数100张 使用实现Runnable接口的方式
存在线程安全问题 需要解决
方式一:同步代码块
synchronized(同步监视器){
大括号中是需要被同步的代码
}
说明 :
要求: 多个线程必须共用一把锁
补充: 在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器
方式二:同步方法
如果操作共享数据的代码完整的声明在一个方法中 ,
我们不妨将此方法声明同步的
同步的好处:解决了线程安全问题
操作同步代码时只能有一个线程参与其他线程等待 相当于单线程的过程 效率低
使用同步代码块的方式解决
class Window2 extends Thread {
private static int ticket = 100;
private static Object obj = new Object();
@Override
public void run() {
while (true) {
// synchronized(obj) {
synchronized(Window2.class) {
if (ticket > 0) {//还有余票
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + ":出售的票编号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class WindowTest2 {
public static void main(String[] args) {
//这是由三个对象 不能用this
Window2 w1 = new Window2();
Window2 w2 = new Window2();
Window2 w3 = new Window2();
w1.setName("窗口一");
w2.setName("窗口二");
w3.setName("窗口三");
w1.start();
w2.start();
w3.start();
}
}
class Window1 implements Runnable{
private int ticket = 100;
// Object obj = new Object();
@Override
public void run() {
// Object obj = new Object();
while (true){
synchronized(this){//this唯一的window的对象
if (ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":所售票的票号为:"+ ticket);
ticket--;
}else {
break;
}
}
}
}
}
public class WindowTest1 {
public static void main(String[] args) {
Window1 w = new Window1();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
使用同步方法
class Window3 implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
show();
}
}
private synchronized void show() {//同步监视器就是this
// synchronized(this){
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":所售票的票号为:" + ticket);
ticket--;
}
}
}
public class WindowTest3 {
public static void main(String[] args) {
Window3 w = new Window3();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Window4 extends Thread {
private static int ticket = 100;
@Override
public void run() {
while (true) {
show();
}
}
private static synchronized void show(){//方法改为静态的 同步监视器是唯一 的了 就是当前类Window4.class唯一的
// private synchronized void show(){//同步监视器 t1 t2 t3 此种解决方法是错误的
if (ticket > 0) {//还有余票
try {
Thread.sleep(
10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":出售的票编号为:" + ticket);
ticket--;
}
}
}
public class WindowTest4 {
public static void main(String[] args) {
Window4 w1 = new Window4();
Window4 w2 = new Window4();
Window4 w3 = new Window4();
w1.setName("窗口一");
w2.setName("窗口二");
w3.setName("窗口三");
w1.start();
w2.start();
w3.start();
}
}
线程安全问题之懒汉式
package com.yer.java1;
/**
* 使用同步机制 将单例模式中的懒汉式 改写为 线程安全的
*
* @author Darling
* @create 2022-02-11-17:28
*/
public class BankTest {
}
class Bank {//单例的 创建一个当前类的实例
private static Bank instance = null;
private Bank() {
}//构造器私有化
public static Bank getInstance() {
// public static synchronized Bank getInstance() { //当前类本身
//方式一:效率稍差 第一个创建完,后面的就没必要等了 啊
// 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;
}
}
线程的死锁问题
死锁:不同的几线程分别占用对方的资源不释放,都在等待对方释放自己需要的同步资源,就想成了线程的死锁
出现思索后,不会出现异常,么有提示,所有线程都处于阻塞状态
解决方法:
java.util.concurrent.locks.Lock
package com.yer.java1;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
*
* 解决线程安全问题的方式三: Lock锁 ---- JDK5.0 新增的新特性
*
*
* 1.面试题synchronized 与lock的异同
* 解答: 相同点:都是解决线程安全问题
* 不同点:synchronized机制在执行完相应对的同步代码以后,自动释放同步监视器
* Lock手动启动同步(lock.lock()),同时结束同步也需要手动的实现 (lock.unlock();)
* @author Darling
* @create 2022-02-11-19:33
*/
class Window implements Runnable{
private int ticket = 100;
//1.实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock();//true 公平 先进先出
@Override
public void run() {
while (true){
try{
//2.调用lock方法 获取了同步监视器
lock.lock();
//就相当于同步代码块中的一样
if (ticket > 0){
try {
Thread.sleep(99);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"售出车票编号为:"+ticket);
ticket--;
}else {
break;
}
}finally {
//3.调用解锁的方法
lock.unlock();
}
}
}
}
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.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
面试题synchronized 与lock的异同
解答:
相同点:都是解决线程安全问题
不同点:synchronized机制在执行完相应对的同步代码以后,自动释放同步监视器
Lock手动启动同步(lock.lock()),同时结束同步也需要手动的实现(lock.unlock()
Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),
synchronized是隐式锁,出了作用域自动释放
Lock只有代码块锁,synchronized有代码块锁和方法锁
使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有
更好的扩展性(提供更多的子类)
优先使用顺序:
Lock→同步代码块(已经进入了方法体,分配了相应资源)→同步方法(在方法体之外)
线程通信
wait();
notify();唤醒一个(优先级比较高的)
notifyAll(); 唤醒所有
这三个方法必须使用在在同步代码块 或者 同步方法
Lock不可以! 有其他的
这三个方法的调用者,必须是同步代码块 同步方法的调用者 的否则报异常
这三个方法是Object类中的
面试题:关于sleep(); wait();的相同点和不同点
相同点:一旦执行,都可以使得当前的线程进入阻塞状态
不同点:1)两个方法声明的位置不一样Thread类中声明sleep();
Object类中声明wait();
2)调用的要求不一样,sleep();可以在任何需要的场景下调用
wait();必须在同步代码块或同步方法中调用
3)关于是否释放同步监视器:如果两个方法都是用在同步代码块或同步方法中,
sleep不会释放锁,wait会释放同步 监视器
实现Callable接口
package com.yer.java1;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 创建线程方式三 :实现Callable接口 ---JDK5.0新增
*
*
* 如何理解实现Callable接口 创建多线程,比实现Runnable接口强大呢?
* 1.call方法是可以有返回值的
* 2.call方法是可以抛出异常的 被外面的操作捕获,获取异常的信息
* 3.Callable支持泛型的
* @author Darling
* @create 2022-02-11-20:24
*/
//1.创建一个实现Callable 的实现类
class NumThread implements Callable {
//2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
//遍历100以内偶数,并且返回偶数的和
int sum = 0;
for (int i = 0; i < 100; i++) {
if (i%2 == 0){
System.out.println(i);
sum += i;
}
}return sum;
}
}public class ThreadNew {
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.获取Callable中的call方法的返回值 ---不用返回值可以省略啊
//get()返回值即为FutureTask构造器参数Callable实现类重写的返回值
Object sum = futureTask.get();
System.out.println("总和:"+sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
实现Callable接口
相比run();call();有返回值
方法可以抛出异常
支持泛型的返回值
需要借助FuntureTask类
Future fut = new FutureTask(numThread)
背景
: 经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路: 提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
好处
:
线程池相关API
JDK5.0起提供了线程池相关APl:
ExecutorService和Executors
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
voidexecute(Runnablecommand):执行任务/命令,没有返回值,一般用来执行 Runnable
< T>Future< T>submit(Callable< T>task):执行任务,有返回值,一般又来执行Callable
void shutdown():关闭连接池
Executors:工具类 、线程池的工厂类,用于创建并返回不同类型的线程池
ExecutorsnewCachedThreadPool():创建一个可根据需要创建新线程的线程池
ExecutorsnewFixedThreadPool(n);创建一个可重用固定线程数的线程池
ExecutorsnewSingleThreadExecutor():创建一个只有一个线程的线城池
ExecutorsnewScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
package com.yer.java1;
import java.util.concurrent.AbstractExecutorService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 创建线程的方法四:
* 使用线程池
*
* 1.提高响应速度(减少了创建新线程的时间)
* 2.降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
* 3.便于线程管理
*
* corePoolSize-------核心池的大小
* maximumPoolSize-----最大线程数
* keepAliveTime--线程没有任务时最多保持多长时间后会终止
*
* @author Darling
*/
class NumberThread 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 NumberThread1 implements Runnable {
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//1.提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//创建一个可以重用的固定线程数的线程池
//在执行之前去设置线程池的属性 接口 在接口的实现类中--
System.out.println(service.getClass());//class java.util.concurrent.ThreadPoolExecutor
// ThreadPoolExecutor
// public class ThreadPoolExecutor extends AbstractExecutorService {
// public abstract class AbstractExecutorService implements ExecutorService {
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
service1.setCorePoolSize(20);
// service1.setKeepAliveTime();
//2.执行指定的线程的操作,需要提供实现Runnable或Callable接口实现类的对象
//service.execute();//适合Runnable
//提交submit 执行execute
service.submit(new NumberThread());//适合Callable
service.submit(new NumberThread1());//适合Callable
//3.关闭连接池
service.shutdown();
//真正在开发使用线程池
}
}
//创建多线程有四种方式
static void main(String[] args) {
//1.提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//创建一个可以重用的固定线程数的线程池
//在执行之前去设置线程池的属性 接口 在接口的实现类中--
System.out.println(service.getClass());//class java.util.concurrent.ThreadPoolExecutor
// ThreadPoolExecutor
// public class ThreadPoolExecutor extends AbstractExecutorService {
// public abstract class AbstractExecutorService implements ExecutorService {
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
service1.setCorePoolSize(20);
// service1.setKeepAliveTime();
//2.执行指定的线程的操作,需要提供实现Runnable或Callable接口实现类的对象
//service.execute();//适合Runnable
//提交submit 执行execute
service.submit(new NumberThread());//适合Callable
service.submit(new NumberThread1());//适合Callable
//3.关闭连接池
service.shutdown();
//真正在开发使用线程池
}
}
//创建多线程有四种方式