进程:正在运行的应用程序称作为一个进程, 负责内存空间的划分
线程:进程中的一个执行序列,负责进程中的代码执行,轻量级进程
多线程:在一个进程中有着多个线程,同时执行多个任务代码
多线程好处:
1. 解决了在一个进程中可以同时执行多个任务代码的问题。
2. 提高资源的利用率
多线程弊端:
1. 增加了cpu的负担
2. 降低了一个进程中线程的执行概率。
3. 引发了线程安全问题。
4. 引发了死锁现象。
线程执行过程中的状态
1 | 就绪(Runnable): | 线程准备运行,不一定立马就能开始执行。 |
2 | 运行中(Running): | 进程正在执行线程的代码 |
3 | 等待中(Waiting): | 线程处于阻塞的状态,等待外部的处理结束 |
4 | 睡眠中(Sleeping): | 线程被强制睡眠。 |
5 | I/O 阻塞(Blocked on I/O): | 等待 I/O 操作完成。 |
6 | 同步阻塞(Blocked on Synchronization): | 等待获取锁。 |
7 | 死亡(Dead): | 线程完成了执行 |
自定义线程的步骤:
方式一:继承类
1. 自定义一个类继承Thread
2. 重写run方法,把自定义线程的任务定义在run方法中。
3. 创建Thread子类的对象,然后调用start方法开启线程。
//方式一:继承Thread类
public class Thread1 extends Thread {
@Override
public void run() {
// 自定义线程的任务代码
for (int i = 0; i < 100; i++) {
System.out.println("自定义线程:" + i);
}
}
public static void main(String[] args) {
// 创建线程对象
Thread1 td1 = new Thread1();
// 开启线程
td1.start();
for (int i = 0; i < 100; i++) {
System.out.println("主线程(main):" + i);
}
}
}
方式二:实现runable接口
1. 自定义一个类去实现Runnable接口。
2. 实现了Runnable接口的run方法, 把自定义线程的任务定义在run方法上。
3. 创建Runnable实现类的对象。
4. 创建Thread对象,并且把Runnable实现类对象作为参数传递进去。
5. 调用thread对象的start方法开启线程。
//方式二:实现Runnable接口
public class Runnable1 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
// 当前线程对象是:
System.out.println("当前线程对象:" + Thread.currentThread());
}
public static void main(String[] args) {
// 创建Runnable实现类的对象
Runnable1 d = new Runnable1();
// 创建Thread对象,并且把Runnable实现类对象作为参数传递进去
Thread t = new Thread(d, "奥特曼");
// 调用thead对象的start方法开启线程。
// 调用了Runanble实现类对象的run方法。 就是相当于把Runnable实现类的run方法作为了线程的任务代码去执行
t.start();
// 主线程执行的。
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
System.out.println("当前线程对象:" + Thread.currentThread());
}
}
注意:
1.不能直接调用run方法,直接调用run相当于调用了一个普通的方法
2.必须调用start()方法,run()自动调用
3.Runnable实现类的对象并不是一个线程对象,只是实现了Runnable接口的对象
4.把Runnable实现类的对象作为参数传递给thread对象是为了执行Runnable实现类的对象的run方法
重写run方法目的:自定义线程的任务代码就是run方法中的所有代码
main线程的任务代码是main方法里面的所有代码
方式三:Executor 框架来创建线程池
线程常用方法:
Thread(String name) | 初始化线程的名字 |
setName(String name) | 设置线程对象名 |
getName() | 返回线程的名字 |
static sleep() | 线程睡眠指定毫秒数 |
currentThread() | 返回当前执行该方法的线程对象 |
在哪个线程里就返回那个线程 | |
getPriority() | 返回当前线程对象的优先级 默认5 |
setPriority(int x) | 设置线程的优先级 (0--10) |
public class Thread3 extends Thread {
public Thread3(String name) {
// 调用Thread类一个参数的构造方法
// 初始化线程名字
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
public static void main(String[] args) throws InterruptedException {
// 创建一个自定义的线程对象
Thread3 td = new Thread3("奥特曼");
// 设置线程优先级
td.setPriority(1);
// 获取线程优先级
Thread.sleep(3000);
System.out.println("自定义线程的优先级:" + td.getPriority());
// 开启线程
td.start();
// 指定线程睡眠的毫秒数 沉睡的是main主线程
Thread.sleep(3000);
// 返回当前线程
Thread mainThread = Thread.currentThread();
System.out.println(Thread.currentThread());
// 默认的优先级是5
System.out.println("主线程的优先级:" + mainThread.getPriority());
// 设置线程的优先级 优先级越高的线程得到cpu的概率越大。 优先级的范围:1~10
mainThread.setPriority(10);
System.out.println("主线程的名字:" + mainThread.getName());
for (int i = 0; i < 100; i++) {
System.out.println(mainThread.getName() + ":" + i);
}
}
}
线程安全问题根本原因:
1. 存在两个或者两个以上的线程。
2. 多个线程共享着一个资源,而且操作资源的代码有多句。
线程安全问题解决:
方式一:同步代码块
格式:
synchronized(锁对象){
需要被同步的代码
}
注意事项:
1. 锁对象可以是任意的一个对象。
2. 锁对象必须是多个 线程共享 的资源。
3. 调用了sleep方法的线程并不会释放锁对象
4. 不存在着线程安全问题时,不要使用同步代码块或者是同步函数
//三个窗口卖票,同步代码块解决
class SaleTickets extends Thread {
// 非静态成员变量。 非静态成员变量在每个对象中都维护了一份数据。
// 使用static修饰票数num,让该数据共享出来给所有的对象使用
static int num = 50;
// 定义一个锁对象 共享
static Object o = new Object();
// 调用父类一个参数的构造函数, 初始化线程的名字。
public SaleTickets(String name) {
super(name);
}
// 线程的任务代码...使用同步代码块
@Override
public void run() {
while (true) {
// synchronized ("锁") {
synchronized (o) {
if (num > 0) {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "卖出了" + num + "号票");
num--;
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
System.out.println("售罄了...");
break;
}
}
}
}
}
public class Thread4 extends Thread {
public static void main(String[] args) {
// 创建线程对象
SaleTickets thread1 = new SaleTickets("窗口1");
SaleTickets thread2 = new SaleTickets("窗口2");
SaleTickets thread3 = new SaleTickets("窗口3");
// 开启线程
thread1.start();
thread2.start();
thread3.start();
}
}
//同步代码块,使用runnable接口解决
class SaleTickets implements Runnable {
// 非静态成员变量
int num = 50;
@Override
public void run() {
while (true) {
synchronized ("锁") {
if (num > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了第" + num + "号票");
num--;
} else {
System.out.println("售罄了...");
break;
}
}
}
}
}
public class Runnable2 {
public static void main(String[] args) {
// 创建Runnable实现类的对象
SaleTickets saleTickets = new SaleTickets();
// 创建三个线程对象
Thread t1 = new Thread(saleTickets, "窗口1");
Thread t2 = new Thread(saleTickets, "窗口2");
Thread t3 = new Thread(saleTickets, "窗口3");
// 调用start方法开启线程
t1.start();
t2.start();
t3.start();
}
}
方式二:同步函数 : 使用synchronized修饰的函数
注意事项:
非静态同步函数的锁对象是this对象,静态函数的锁对象是当前所属类的class文件对象。
- 推荐使用: 同步代码块
- 推荐原因:
- 1. 同步代码块的锁对象可以自己指定,同步函数的锁对象是固定的。
- 2. 同步代码块可以随意指定范围需要被同步,同步函数必须是整个函数都同步
每个人都拥有其他人需要的资源,同时又等待其他人拥有的资源,并且每个人在获得所有需要的资源之前都不会放弃已经拥有的资源
死锁现象出现 的根本原因:
1. 存在两个或者两个以上的线程存在。
2. 多个线程必须共享两个或者两个以上的资源却释放。
没法解决,只能避免!
-
简单的避免死锁的方式就是:指定获取锁的顺序,并强制线程按照指定的顺序获取锁。如果所有的线程都是以同样的顺序加锁和释放锁,就不会出现死锁了。
class DeadLockThread extends Thread {
public DeadLockThread(String name) {
super(name);
}
@Override
public void run() {
if ("张三".equals(this.getName())) {
synchronized ("遥控器") {
System.out.println(this.getName() + "取走了遥控器,准备取电池");
synchronized ("电池") {
System.out.println(this.getName() + "取到了电池,开着空调爽歪歪的吹着 !!");
}
}
} else if ("李四".equals(this.getName())) {
synchronized ("电池") {
System.out.println(this.getName() + "取走了电池,准备取取遥控器");
synchronized ("遥控器") {
System.out.println(this.getName() + "取走了遥控器,开着空调爽歪歪的吹着 !!");
}
}
}
}
}
public class DeadLock {
public static void main(String[] args) {
// 创建了线程对象
DeadLockThread thread1 = new DeadLockThread("张三");
DeadLockThread thread2 = new DeadLockThread("李四");
thread1.setPriority(10);
thread2.setPriority(1);
// 调用start方法启动线程
thread1.start();
thread2.start();
}
}
线程通讯
方法:
wait() 该线程进入以锁对象建立的线程池中暂停并等待执行
notify() 该线程会唤醒线程池中一个等待线程中
notifyAll(); 唤醒所有的线程
注意:
1. wait、notify、notifyAll方法都是属于Object对象方法
2. wait、notify、 notifyAll方法必须要在同步代码块或者是同步函数中调用。
3. wait、notify、 notifyAll方法必须由锁对象调用
4. 一个线程执行了wait方法会释放锁对象
//线程通讯:生产者生产一个消费者消费一个
//产品类
class Product {
String name;
int price;
// 产品是否生成完毕的标识 false为还没有生成完毕, true 生成完毕了.
boolean flag;
}
// 生产者类
class Producer extends Thread {
// 维护一个产品
Product p;
public Producer(Product p) {
this.p = p;
}
@Override
public void run() {
int i = 0;
while (true) {
synchronized (p) {
if (p.flag == false) {
if (i % 2 == 0) {
p.name = "摩托车";
p.price = 4000;
} else {
p.name = "自行车";
p.price = 300;
}
System.out.println("生产了" + p.name + " 价格:" + p.price);
i++;
// 生成完毕 --- 改标识
p.flag = true;
// 唤醒消费者去消费
p.notifyAll();
;
} else {
// 如果产品已经生产完毕,应该等待消费者先消费
try {
p.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
// 消费者
class Customer extends Thread {
// 维护一个产品类
Product p;
public Customer(Product p) {
this.p = p;
}
@Override
public void run() {
while (true) {
synchronized (p) {
if (p.flag == true) {
System.out.println("消费者消费了:" + p.name + " 价格:" + p.price);
// 改标识
p.flag = false;
p.notifyAll();
} else {
// 如果产品已经被消费完毕,应该唤醒生产者去生成
try {
p.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
public class Thread8 {
public static void main(String[] args) {
// 创建一个产品对象
Product p = new Product();
// 创建线程对象
Producer producer = new Producer(p);
Customer customer = new Customer(p);
// 启动线程
producer.start();
customer.start();
}
}
守护线程(后台线程)
d.setDaemon(true) 设置线程为守护线程
当前一个java应用只剩下守护线程的时候,那么守护线程马上结束。
守护线程应用场景:新的软件版本下载
//守护线程应用场景
//模拟QQ在下载更新包
public class Thread6 extends Thread {
public Thread6(String name) {
super(name);
}
@Override
public void run() {
// 子类抛出的异常类型必须要小于或者等于父类抛出 的异常类型。
for (int i = 1; i < 100; i++) {
System.out.println(this.getName() + "已经下载了:" + i + "%");
}
System.out.println("下载完毕,正在安装更新包!!!");
}
public static void main(String[] args) {
// 创建一个线程对象
Thread6 d = new Thread6("守护线程");
// 设置一个线程为守护线程
d.setDaemon(true);
// 判断一个线程是否为守护线程。
System.out.println("是守护线程吗?" + d.isDaemon());
// 启动线程
d.start();
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}//只剩守护线程时,守护线程会立即结束
}
线程让步:join()方法
当前线程执行join方法,那当前线程就会让步给新线程先完成任务,然后继续的执行自己的任务
停止线程:interrupt()
注意:
1. 我们停止一个线程一般都会配合一个变量去控制。
2. 如果停止的是一个等待状态下的线程,那么需要配合interrupt方法使用。
public class Thread9 extends Thread {
boolean flag = true;
public Thread9(String name) {
super(name);
}
@Override
public synchronized void run() {
int i = 0;
while (flag) {
System.out.println(Thread.currentThread().getName() + ":" + i);
i++;
}
}
public static void main(String[] args) {
// 创建线程对象
Thread9 d = new Thread9("奥特曼");
d.start();
// 当主线程的i到50的时候,停止奥特曼线程。
for (int i = 0; i < 100; i++) {
if (i == 80) {
// interrupt() 无法停止一个线程,
d.flag = false;
// 强制清除一个线程的wait、 sleep状态。 可以指定清除那个线程。
d.interrupt();
}
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
sleep()和wait()的区别:
- (1)sleep()来自Thread类,wait()来自Object类。
- (2)sleep是Thread的静态类方法,谁调用的谁去睡觉;wait()是Object类的非静态方法
- (3)sleep()释放资源不释放锁,wait()释放资源释放锁;
- (4)wait,notify和notifyAll只能在同步方法或者同步块里面使用,而sleep可以在任何地方使用
线程的状态
- 新建:new一个Thread对象或者其子类对象就是创建一个线程,只是对象线程对象开辟了内存空间和初始化数据。
- 就绪:新建的对象调用start方法,就开启了线程,线程就到了就绪状态。 在这个状态的线程对象,具有执行资格,没有执行权。
- 运行:当线程对象获取到了CPU的资源。 在这个状态的线程对象,既有执行资格,也有执行权。
- 冻结:运行过程中的线程由于某些原因(比如wait,sleep),释放了执行资格和执行权。
- 死亡:当线程对象调用的run方法结束,或者直接调用stop方法,就让线程对象死亡,在内存中变成了垃圾。