程序 (program) : 是一组指令和数据的集合,用来实现特定的功能,它是静态的代码,存储在磁盘或其他存储设备中 。
进程 (process) : 是程序的一次执行过程,是操作系统分配资源和调度的基本单位,它是动态的,有自己的生命周期 。
线程 (thread) : 是进程内的一个执行单元,是操作系统调度和执行的最小单位,它是轻量级的进程,可以共享所属进程的资源 。
对于java来说,当我们的程序开始执行时,也就是main方法开始执行时,就会在栈内存中开辟以main方法为栈底元素的栈帧。此时,这个这个以main方法为栈底元素的链式栈帧调用,就称为主程序。
CPU时间片: 把CPU执行5次的时间作为一个借本单位,称为一个时间片,然后将时间片分配给每个线程,分配方式由操作系统决定,一般按优先级发放。
并发: 同时发生,指的是多个任务在同一时间段内交替执行,同一时刻只有一个任务在执行。
并行: 同时进行,指的是多个任务在同一时刻同时执行,不需要交替或切换。
1个CPU同时只能做一件事,所以没有办法并行执行,但是可以处理并发
单核CPU: 是指只有一个核心的中央处理器,他只能同一时刻执行一个任务,是假的多线程。
多核CPU: 有两个及以上核心的中央处理器,他可以同一时刻执行多个任务或线程,能有效的发挥多线程的效率。
多线程的优缺点:
优点:
1. 提高应用程序的响应,对图形化界面更有意义,可增强用户体验。
2. 提高计算机系统CPU的利用率
3. 改善程序结构,将既长又复杂的程序分为多个线程,独立运行,利于理解和修改。
缺点:
1. 多线程程序涉及到线程同步、数据共享等复杂问题,编写和调试相对困难。
2. 多个线程访问共享数据时可能导致竞争条件,需要谨慎处理,否则可能引发错误。
3. 如果线程之间存在循环等待资源的情况,可能导致死锁,使程序无法继续执行。
应用场景:
1. 程序需要同时执行两个或多个任务
2. 程序需要实现一些需要等待的任务的时候,入用户输入,文件读写操作,网络操作, 搜索等。
3. 需要一些后台运行的程序时。
两种方式:
1. 继承Thread类,并覆写run()方法
2. 实现Runnable接口,并覆写run()方法
启动线程的方式只有一种,就是调用start()方法*
// 通过继承创建线程
public class Thread_00 {
public static void main(String[] args) {
// 创建线程对象
Thread t1 = new Process();
// 启动线程
t1.start();
for (int i = 0; i < 10; i++) {
System.out.println("main线程:" + i);
}
}
}
class Process extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("测试线程:" + i);
}
}
}
// 通过实现接口创建线程
public class Thread_01 {
public static void main(String[] args) {
// 创建线程对象
Thread t1 = new Thread(new Process_01());
// 启动线程
t1.start();
for (int i = 0; i < 100; i++) {
System.out.println("main线程:" + i);
}
}
}
class Process_01 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("测试线程:" + i);
}
}
}
使用Runnable接口更具灵活性,因为它避免了Java单继承的限制,并且允许多个线程共享相同的Runnable实例。通常推荐使用实现Runnable接口的方式创建线程。
java中共提供了1~10 ,10个优先级
Thread中提供了三个优先级常量,MIN_PRIORITY代表1,NORM_PRIORITY代表5,MAX_PRIORITY代表10。
默认优先级为5,子类从父类继承优先级
setPriority 设置优先级
getPriority 获取优先级
setName 设置线程名称,如果不设置线程名称,默认从Thread_0开始,依此类推
getName 获取线程名称
static currentThread 获取当前线程对象
static sleep 让当前线程进入睡眠状态,参数为毫秒
// 使用方式
public class Thread_02 {
public static void main(String[] args) {
// 创建线程
Thread t1 = new Process_02();
Thread t2 = new Process_02();
// 设置线程名
t1.setName("t1");
t2.setName("t2");
// 设置线程优先级
t1.setPriority(1);
t2.setPriority(10);
// 启动线程
t1.start();
t2.start();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}
}
class Process_02 extends Thread{
@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) {
e.printStackTrace();
}
}
}
}
// sleep方法
public class Thread_03 {
public static void main(String[] args) {
// 创建线程
Thread t1 = new Process_03();
Thread t2 = new Process_03();
// 设置线程名
t1.setName("t1");
t2.setName("t2");
// 设置线程优先级
t1.setPriority(1);
t2.setPriority(10);
// 启动线程
t1.start();
t2.start();
// 线程睡眠
// sleep为静态方法,写在哪里,哪个线程就会进入睡眠,和调用没有关系,因为最后都是通过类名调用的
// 当前睡眠的是main线程
try {
t1.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}
}
class Process_03 extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}
}
// 打断睡眠抛异常
public class Thread_04 {
public static void main(String[] args) {
Thread t1 = new Thread(new Process_04()) ;
t1.start();
try {
Thread.sleep(3000);
// 三秒后叫醒线程t1,会抛出异常
t1.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Process_04 implements Runnable{
@Override
public void run() {
try {
Thread.sleep(1000000000);
System.out.println("睡眠结束了");
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("睡眠被打断");
}
System.out.println("测试线程结束");
}
}
isAlive() 判断当前线程是否还"活着",即线程是否还未终止
getPriority() 获取当前线程优先级数值
setPriority() 设置当前线程优先级数值
Thread.sleep() 将当前线程睡眠指定毫秒数
join() 调用某线程的该方法,将当前线程于该线程合并,即等待该线程结束,回复当前线程运行
yield() 让出CPU,当前线程进入就绪队列等待调度
wait() 当前线程进入对象的wait pool
notify()/notifyAll() 唤醒对象的wait pool中的一个或多个线程
// 线程停止
public class Thread_05 {
public static void main(String[] args) {
Process_05 t1 = new Process_05();
t1.start();
try {
Thread.sleep(3000);
// stop方法结束t1线程,不推荐使用,因为会造成死锁
// t1.stop();
t1.run = false;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Process_05 extends Thread{
boolean run = true;
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (!run) {
return;
}
System.out.println(Thread.currentThread().getName() + "->" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 线程合并
public class Thread_06 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Process_06());
Thread t2 = new Thread(new Process_06());
t1.start();
t2.start();
// join写在main线程中,main线程执行到这里,等待t1线程执行完毕,在继续执行
// 相当于合并了main和t1线程,不会对t2线程的执行造成影响
t1.join();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"->"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Process_06 implements 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) {
e.printStackTrace();
}
}
}
}
/*
* Yield 线程让位
* 暂停当前正在执行的线程,执行其它线程
* Thread.yield为静态方法,写在哪个线程就作用于哪个线程
* 让位只存在于同优先级之间
*/
public class Thread_07 {
public static void main(String[] args) {
Thread t1 = new Thread(new Process_07());
// 设置t1线程优先级
t1.setPriority(2);
// 设置main线程优先级
Thread.currentThread().setPriority(2);
t1.start();
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) {
// main线程让位
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + "->" + i);
}
}
}
class Process_07 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "->" + i);
}
}
}
问题: 多个线程执行的不确定性引起执行结果的不确定性
多个线程对同一个数据或对象的共享会造成操作的不完整性,会破坏数据
线程同步:
当多个线程同时操作一个数据的时候,为了保证数据的一致性和正确性,需要使用线程同步,线程同步是一种数据的安全机制。
线程同步的原因:
同步是指数据的同步,为了数据的安全,在同时操作数据时必须等莫格线程对数据操作完,再让其它线程进行操作。同步可以看作是将多线程转为单线程。
线程同步的条件:
1. 必须是多线程多并发的情况下,才有可能出现。
2. 多个线程有可能同时操作同一个数据或同一个对象是,尤其是修改操作。
// 线程同步(未同步状态)
/*
* 输出结果:
* t1取钱完成,剩余1000.0
* t2取钱完成,剩余1000.0
*
* t1取钱完成,剩余2000.0
* t2取钱完成,剩余1000.0
*
* t1取钱完成,剩余2000.0
* t2取钱完成,剩余2000.0
*
* ......
*
*/
public class Thread_08 {
public static void main(String[] args) {
// 创建银行卡对象
BackCard card = new BackCard("1001A",3000);
// 创建两个进程,同时操作同一个银行卡对象
Thread t1 = new Thread(new Process_08(card));
Thread t2 = new Thread(new Process_08(card));
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
}
class Process_08 implements Runnable{
// 声明银行卡对象
BackCard card;
@Override
public void run() {
// 从银行卡中取出1000
card.modifyBalance(1000);
// 打印剩余余额
System.out.println(Thread.currentThread().getName() + "取钱完成,剩余" + card.getBalance());
}
public Process_08(BackCard card) {
this.card = card;
}
}
// 实体类
class BackCard {
// 卡号
private String cardNo;
// 余额
private double balance;
// 取出余额
public void modifyBalance(double money) {
balance = balance - money;
}
public BackCard() {
super();
}
public BackCard(String cardNo, double balance) {
super();
this.cardNo = cardNo;
this.balance = balance;
}
public String getCardNo() {
return cardNo;
}
public double getBalance() {
return balance;
}
}
// 线程同步(添加方法锁)
/*
* 运行结果
* t1
* t2
* t1取钱完成,剩余2000.0
* t2取钱完成,剩余1000.0
*
* t2
* t1
* t2取钱完成,剩余2000.0
* t1取钱完成,剩余1000.0
*/
public class Thread_09 {
public static void main(String[] args) {
// 创建银行卡对象
BackCard_01 card = new BackCard_01("1001A",3000);
// 创建两个进程,同时操作同一个银行卡对象
Thread t1 = new Thread(new Process_09(card));
Thread t2 = new Thread(new Process_09(card));
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
}
class Process_09 implements Runnable{
// 声明银行卡对象
BackCard_01 card;
@Override
public void run() {
// 从银行卡中取出1000
card.modifyBalance(1000);
// 打印剩余余额
System.out.println(Thread.currentThread().getName() + "取钱完成,剩余" + card.getBalance());
}
public Process_09(BackCard_01 card) {
this.card = card;
}
}
// 实体类
class BackCard_01 {
// 卡号
private String cardNo;
// 余额
private double balance;
// 取出余额
public synchronized void modifyBalance(double money) {
// synchronized 使用该修饰符后,该方法只能有一个线程进入
// ,其它线程必须等这个线程执行完毕,弹栈后才能进入
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
balance = balance - money;
}
public BackCard_01() {
super();
}
public BackCard_01(String cardNo, double balance) {
super();
this.cardNo = cardNo;
this.balance = balance;
}
public String getCardNo() {
return cardNo;
}
public double getBalance() {
return balance;
}
}
// 线程同步(语句块锁)
public class Thread_10 {
public static void main(String[] args) {
// 创建银行卡对象
BackCard_02 card = new BackCard_02("1001A",3000);
// 创建两个进程,同时操作同一个银行卡对象
Thread t1 = new Thread(new Process_10(card));
Thread t2 = new Thread(new Process_10(card));
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
}
class Process_10 implements Runnable{
// 声明银行卡对象
BackCard_02 card;
@Override
public void run() {
// 从银行卡中取出1000
card.modifyBalance(1000);
// 打印剩余余额
System.out.println(Thread.currentThread().getName() + "取钱完成,剩余" + card.getBalance());
}
public Process_10(BackCard_02 card) {
this.card = card;
}
}
// 实体类
class BackCard_02 {
// 卡号
private String cardNo;
// 余额
private double balance;
// 取出余额
public void modifyBalance(double money) {
// synchronized 使用该修饰符后,该方法只能有一个线程进入
// ,其它线程必须等这个线程执行完毕,弹栈后才能进入
System.out.println(Thread.currentThread().getName());
// 语句块锁,因为方法会把整个方法锁住,而有的时候只需要同步几行代码
// 采用语句块锁只会锁住需要同步的代码,不会对方法内其它代码进行同步
synchronized (this) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
balance = balance - money;
}
}
public BackCard_02() {
super();
}
public BackCard_02(String cardNo, double balance) {
super();
this.cardNo = cardNo;
this.balance = balance;
}
public String getCardNo() {
return cardNo;
}
public double getBalance() {
return balance;
}
}
访问一个加锁的成员方法/成员语句块锁时,该对象中所有的成员方法和对象语句块锁都会被锁定。
访问一个加锁的静态方法/静态语句块锁时,该类中所有加锁的静态方法和静态语句块锁都会被锁定。
synchronized(对象){} 对象语句块锁
synchronized(类名.class){} 类语句块锁
从jdk1.5开始,java提供了更强大的线程同步机制,通过显示定义同步锁对象来实现同步,同步锁使用Lock对象充当。
java.util.concurrent.locks.Lock 接口是控制多线程对共享资源进行访问的工具。锁提供了对共享资源的独占空间,
每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
ReentranLock类实现了Lock,它拥有和synchronized相同的并发性和内存语义,在实现线程安全控制中,比较常用的是ReentranLock,可以直接显式加锁,释放锁。
// 创建锁对象
Lock lock = new ReentrantLock();
// 取出余额
public void modifyBalance(double money) {
// 开启锁
lock.lock();
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
balance = balance - money;
// 释放锁
lock.unlock();
}
Lock是显式锁,需要手动开启和关闭,并且JVM将花费较少的时间来完成线程调度,具备更好的扩张性。
/ 守护线程
/*
* 守护线程: 每一个main主线程的开启,都会同时开启一个守护线程,来监听我们的正常程序执行
* 当没有线程执行的时候,守护线程也就终止了
*/
public class Thread_12 {
public static void main(String[] args) {
Thread t1 = new Thread(new Process_12());
// 设置为守护线程
t1.setDaemon(true);
t1.start();
for (int i = 0; i < 6; i++) {
try {
Thread.sleep(1000);
System.out.println("main线程:" + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 创建线程
class Process_12 implements Runnable{
@Override
public void run() {
int i = 0;
// 死循环,测试守护线程随被守护的线程的关闭而关闭
while(true) {
try {
Thread.sleep(1000);
System.out.println(i++);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
定时器: 在某些指定时间或按照指定规律执行的任务,即计划任务,只要有一个计划任务,就会开启一个线程,进行计时监听。
// 定时器任务
public class Thread_13 {
public static void main(String[] args) {
// 创建定时器
Timer t = new Timer();
// 开启定时器
// 第一个参数为TimerTask子类对象,也就是要执行的任务
// 第二个参数为开始时间,传入毫秒数,代表多久后开始在执行,也可以传入Date对象
// 第三个参数为每次执行的间隔时间
t.schedule(new LogTimeTask(),5000,1000);
}
}
class LogTimeTask extends TimerTask{
int i = 0;
@Override
public void run() {
// 要执行的任务
System.out.println(i++);
}
}