一:程序、进程和线程
1.1:程序概述
静态代码
例如 软件
1.2:进程概述
动态过程
如 运行中的qq等软件
-
- 一个正在运行的程序
1.3进程与程序联系与区别:
- 程序是静态的,进程是动态的;
1.4:线程概述
简而言之:线程是进程的一个执行单元。比进程更小的独立运行的基本单位。
- 注:一个程序至少一个进程,一个进程至少一个线程。比如视频中同时有声音、图像、弹幕等;
- 简单
- 进程
- 线程;任务
- 进程
二:线程的创建和启动(Java如何实现线程的)
- 概述
- Java语言的JVM允许程序运行多个线程(Java允许同时执行多个代码的),它通过java.lang.Thread类来体现。
- Thread类的特性:
- 每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体。
- 通过该Thread对象的start()方法来启动这个线程,而非直接调用run()。
- Thread类的特性:
- JDK1.5之前创建新执行线程有两种方法:
- 继承Thread类的方式;
- 实现Runnable接口的方式;
- Java语言的JVM允许程序运行多个线程(Java允许同时执行多个代码的),它通过java.lang.Thread类来体现。
2.1方式一
- 继承Thread类
- 定义子类继承Thread类。
- 子类中重写Thread类中的run方法,把新线程要做的事写在run方法中。
- 创建Thread子类对象,即创建线程对象。
- 调用线程对象start方法:启动线程,调用run方法。
- 代码实例:主线程和分支线程并发执行:
package 多线程概述01;
public class Demo {
/*
* 1:新建一个类,继承Thread
* 2:重写run方法
* 3:创建一个实例
* 4:调用start
*
* */
public static void main(String[] args) {
Thread t1 = new MyThread();
t1.start();
}
}
// 1、定义子类继承Thread类。
package 多线程概述01;
//线程干嘛的?干活的,执行任务
public class MyThread extends Thread{
//以后你想用线程做什么任务,任务代码就写进run方法
@Override
public void run() {
for (int i = 1;i<101;i++){
System.out.println("mythread:"+i);
}
}
}
2.2.0知识点补充
- 并发
- 不同的任务,在同一时刻不同时间点执行。并发现象
- 并行
- 在相同的时间,做不同的任务
2.2方式2
- 实现Runnable接口
- Thread(Runnable target, String name):创建新的Thread对象;
- 定义子类,实现Runnable接口。
- 子类中重写Runnable接口中的run方法,把新线程要做的事写在run方法中
- 创建自定义的Runnable的子类对象
- 通过Thread类含参构造器创建线程对象,将Runnable接口的子类对象作为实际参数传递给Thread 类的构造器中。
- 调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。
-
- 代码示例
package 多线程实现方式二;
public class Demo {
public static void main(String[] args) {
/*
* 1:自定义一个类,实现Runable
* 2:重写run
* 3:创建一个对象
* 4:再创建一个Thread,把刚刚创建好的对象传递进去
* 5:调用statrt方法
* */
MyRunnable myRunnable = new MyRunnable();
Thread t1 = new Thread(myRunnable);
t1.start();
}
}
package 多线程实现方式二;
public class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println(111);
}
}
2.3两种创建方式的对比
- 总结
- Thread类,可以直接使用父类的方法,有单继承的局限性
- Runnable,拓展性好
三:线程的状态
3.1:Java多线程五种基本状态
- 新建状态(New)
- 就绪状态Runable: 新建状态的线程对象被start()后
- 运行状态(Running):run方法抢占到CPU的执行权
- 阻塞状态:
-
- 等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
- 同步阻塞:线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
- 其他阻塞 :通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
- 死亡状态(Dead):结束
四:线程控制和线程安全
4.1:线程控制
- 获取当前线程、名称和设置名称
- 方法
0、获取当前线程 static Thread currentThread() 1、获取名称 通过 getName()方法获取线程对象的名字 2、设置名称 通过构造函数可以传入String类型的名字 Thread(String name) 分配新的 Thread 对象。name - 新线程的名称。 setName(String name) 改变线程名称,使之与参数 name 相同。
- 获取当前线程、成员方法实现设置线程名称
- 构造函数实现设置线程名称
4.1.1休眠线程
sleep —— 让当前的正在执行的线程暂停指定的时间,并进入阻塞状态。在其睡眠的时间段内,该线程
由于不是处于就绪状态,因此不会得到执行的机会。即使此时系统中没有任何其他可执行的线程,处于
sleep()中的线程也不会执行。因此sleep()方法常用来暂停线程执行。
Thread.sleep(1000);
4.1.2守护线程
当进程中不存在非守护线程了,则守护线程自动销毁。
t1.setDaemon(true);
4.1.3加入线程
join(), 当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续;
t1.join();
4.1.4礼让线程
static void yield() 暂停当前正在执行的线程对象,并执行其他线程
Thread.yield(); // 让出CPU
4.1.5设置线程优先级
setPriority(int newPriority) 更改线程的优先级。
getPriority() 返回线程的优先级
System.out.println(t1.getPriority()); System.out.println(t2.getPriority()); t1.setPriority(1); t1.start(); t2.setPriority(10); t2.start();
4.1.7:线程等待wait
-
- 一直在阻塞状态
-
- notify:唤醒另一个
- notifyAll:唤醒所有
4.2:数据共享
线程数据共享,很容器造成线程不安全
4.3线程同步
- Java中提供了线程同步的机制,帮我们解决线程安全问题
- 同步代码块
- 同步方法
4.3.1:同步代码块
synchronized (lock){//():中的参数指的是给一个锁? 任意对象
4.3.2同步方法
- synchronized关键字加到方法声明上
五:线程的并发与协作
5.1:线程间的通信
- 什么是线程间的通信
- 线程有一定规律的交替执行
5.2:两个线程间的通信实现
package 两个线程间的通信01;
public class Demo {
public static void main(String[] args) { //[main,5,main]
Print print = new Print();
new Thread(){
@Override
public void run() {
while (true){
print.m1();
}
}
}.start();
new Thread(){
@Override
public void run() {
while (true){
print.m2();
}
}
}.start();
}
}
class Print{
private int flag = 1;
public void m1(){
synchronized (this){
if (flag!=1){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("坏");
System.out.print("蛋");
System.out.print("吃");
System.out.print("piao");
System.out.println();
flag = 2;
this.notify();
}
}
public void m2(){
synchronized (this){
if (flag!=2){
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.print("好");
System.out.print("蛋");
System.out.print("真");
System.out.print("牛");
System.out.println();
flag = 1;
this.notify();
}
}
}
/*
class MyRunnable implements Runnable{
@Override
public void run() {
while (true){
if (Demo.a!=2){
try {
Demo.o.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
m();
Demo.a = 1;
Demo.o.notify();
}
}
public synchronized void m(){
System.out.print("坏");
System.out.print("蛋");
System.out.print("吃");
System.out.print("piao");
System.out.println();
}
}
class MyRunnable2 implements Runnable{
@Override
public void run() {
while (true){
if (Demo.a!=1){
try {
Demo.o.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
m();
Demo.a = 2;
Demo.o.notify();
}
}
public synchronized void m(){
System.out.print("好");
System.out.print("蛋");
System.out.print("真");
System.out.print("牛");
System.out.println();
}
}*/
5.3:三个线程之间的通信
5.4:生产者、消费者
- 缓冲区是实现并发的核心,缓冲区的设置有3个好处:
- 实现线程的并发协作
- 解耦了生产者和消费者
- 解决忙闲不均,提高效率
- 生产者消费者模式
package 生产者和消费者03;
public class Demo {
public static void main(String[] args) {
DataBuffer dataBuffer = new DataBuffer();
Shengchanzhe scz = new Shengchanzhe(dataBuffer);
XiaoFeiZhe xfz = new XiaoFeiZhe(dataBuffer);
scz.start();
xfz.start();
}
}
class Data{
public int data;
//给数据赋值,要多少个数据
public Data(int data) {
this.data = data;
}
public Data() {
}
}
//缓存区
class DataBuffer{
//数组的索引
private int index;
//存数据
Data[] array = new Data[10];
//存
public synchronized void push(Data data){
//存数据
//数组满了
while (index == array.length){
//数组满了,让生产数据的线程,先听一下
try {
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
//没进入循环
array[index++] =data;
notify();
}
//取
public synchronized Data pop(){
while (index == 0){
try {
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
notify();
index--;
return array[index];//我们正在从缓存区索取数据
}
}
class Shengchanzhe extends Thread{
private DataBuffer dataBuffer;
public Shengchanzhe(){}
public Shengchanzhe(DataBuffer dataBuffer) {
this.dataBuffer = dataBuffer;
}
//生产数据
@Override
public void run() {
for (int i = 0;i<10;i++){
System.out.println("我正在生产数据");
//该方法是在生产数据,用数据类(Data)来封装数据
Data data = new Data(i);
//放缓存区
dataBuffer.push(data);
}
}
}
class XiaoFeiZhe extends Thread{
private DataBuffer dataBuffer;
public XiaoFeiZhe(DataBuffer dataBuffer) {
this.dataBuffer = dataBuffer;
}
@Override
public void run() {
for (int i = 0;i<10;i++){
Data pop = dataBuffer.pop();
System.out.println("消费者"+pop.data);
}
}
}
六:线程池、组
6.1:使用Callable接口创建线程
- 使用Callable接口创建线程是JDK5.0新增的;
- 与使用Runnable相比, Callable功能更强大些:
- 相比run()方法,可以有返回值
- 方法可以抛出异常;
- 支持泛型的返回值;
- 需要借助FutureTask类,比如获取返回结果;
- 具体是创建Callable接口的实现类,并实现call()方法。并使用FutureTask类来包装Callable实现类的对象,且以此FutureTask对象作为Thread对象的target来创建线程。
- 看着好像有点复杂,直接来看一个例子就清晰了:
public class MyCallble implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println(1111);
int sum = 0;
for (int i = 1;i<101;i++){
sum+=i;
}
return sum;
}
}
6.2:线程组概述
- 数据的安全
- 同一个组,数据可以互相访问的
- 不是同一个,数据不可以互相访问
public class Demo {
public static void main(String[] args) {
//获取main线程是属于哪个组---》Thread--->getThreadGroup()
Thread tmain = Thread.currentThread();
//获取线程组
ThreadGroup threadGroup = tmain.getThreadGroup();
//获取该线程组的名字
System.out.println(threadGroup.getName());
//自定义的线程,没有分组
Thread thread = new Thread() {
@Override
public void run() {
}
};
System.out.println(thread.getThreadGroup().getName());
}
}
- 自定义线程组
package 线程池概述14;
import Callble概述13.MyCallble;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Demo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//线程池类
ExecutorService executorService = Executors.newFixedThreadPool(10);
//给线程池要线程
/* executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println("哈哈,我们在学习线程池!");
}
});*/
MyCallble myCallble = new MyCallble();
Future<Integer> future = executorService.submit(myCallble);
System.out.println(future.get());
executorService.shutdown();
}
}
6.3线程池(工厂)
6.3.1概述
我们可以提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。
package 线程池概述14;
import Callble概述13.MyCallble;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Demo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//线程池类
ExecutorService executorService = Executors.newFixedThreadPool(10);
//给线程池要线程
/* executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println("哈哈,我们在学习线程池!");
}
});*/
MyCallble myCallble = new MyCallble();
Future<Integer> future = executorService.submit(myCallble);
System.out.println(future.get());
executorService.shutdown();
}
}
6.3.2优点
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。
6.3.3使用线程池方式--Runnable接口
- 通常,线程池都是通过线程池工厂创建,再调用线程池中的方法获取线程,再通过线程去执行任务方法。
- Executors:线程池创建工厂类
- public static ExecutorService newFixedThreadPool(int nThreads):返回线程池对象
- ExecutorService:线程池类
- Future submit(Runnable task):获取线程池中的某一个线程对象,并执行
- Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用
- 使用线程池中线程对象的步骤:
- 创建线程池对象
- 创建Runnable接口子类对象
- 提交Runnable接口子类对象
- 关闭线程池
6.3.42.3使用线程池方式—Callable接口
- Callable接口:与Runnable接口功能相似,用来指定线程的任务。其中的call()方法,用来返回线程任务执行完毕后的结果,call方法可抛出异常。
- ExecutorService:线程池类
- Future submit(Callable task):获取线程池中的某一个线程对象,并执行线程中的call()方法
- Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用
- 使用线程池中线程对象的步骤:
- 创建线程池对象
- 创建Callable接口子类对象
- 提交Callable接口子类对象
- 关闭线程池