多线程
概念
线程指进程中的一个执行场景,也就是执行流程,那么进程和线程有什么区别呢?
- 每个进程是一个应用程序,都有独立的内存空间
- 同一个进程中的线程共享其进程中的内存和资源
- (共享的内存是堆内存和方法区内存,栈内存不共享,每个线程有自己属于自己栈内存)
什么是进程?
一个进程对应一个应用程序。例如:在 Windows操作系统启动Word就表示启动了一个进程。在java的开发环境下启动JVM,就表示启动了一个进程。现代的计算机都是支持多进程的,在同一个操作系统中,可以同时启动多个进程。
多进程有什么用?
- 单进程计算机只能做一件事情。例:玩电脑,一边玩游戏(游戏进程)一边听音乐(音乐进程)。
- 对于单核计算机来讲,在同一个时间点上,游戏进程和音乐进程是同时在运行吗?不是。因为计算机的CPU只能在某个时间点上做一件事。计算机能以极快的速度来回切换线程,使得用户认为计算机运行两个线程。
什么是线程?
- 线程是一个进程中的执行场景。一个进程可以启动多个线程。
- 线程是一个进程中的执行场景。一个进程可以启动多个线程。
多线程有什么作用?
- 多线程不是为了提高执行速度,而是提高应用程序的使用率。
- 线程和线程共享“堆内存和方法区内存”,栈内存是独立的,一个线程个栈。
注:在此之前学过的都是单线程的程序!
java线程的创建与启动:
继承Thread类
// 继承Thread类 public class ThreadTest01 { public static void main(String[] args) { // 创建线程 Thread t = new Processor(); // 多态父类型引用指向子类型对象 // 启动线程 t.start(); for (int i = 0; i < 30; i++) { System.out.println("main-->"+i); } } } class Processor extends Thread { // 重写run方法 @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("Processor-->"+i); } } }
实现Runnable接口(推荐使用)
// 实现Runnable接口(推荐使用) public class ThreadTest02 { public static void main(String[] args) { // 创建线程 Thread t = new Thread(new Processor()); // 启动线程 t.start(); for (int i = 0; i < 30; i++) { System.out.println("main-->"+i); } } } class Processor2 implements Runnable{ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("Processor2-->"+i); } } }
线程的调度与控制
通常我们的计算机只有一个CPU,CPU在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。在单CPU的机器上线程不是并行运行的,只有在多个CPU上线程才可以并行运行。Java虚拟机要负责线程的调度,取得CPU的使用权目前有两种调度模型:分时调度模型和抢占式调度模型,Java使用抢占式调度模型。分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片抢占式调度模型:优先让优先级高的线程使用CPU(优先级高的线程获取的CPU时间片相对多一些),如果线程的优先级相同,那么会随机选择一个。
示例代码:
// 创建线程第二种方式(推荐写法): public class ThreadTest02 { public static void main(String[] args) { // 创建线程 Thread t = new Thread(new Processor()); // 启动线程 t.start(); for (int i = 0; i < 30; i++) { System.out.println("main-->"+i); } } } class Processor2 implements Runnable{ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("Processor2-->"+i); } } }
线程的优先级
线程优先级主要分三种:MAX_PRIORITY(最高级);MIN_PRIORITY(最低级);NORM_PRIORITY(标准)默认。注:最高:10、标准:5、最低:1。
// 线程的优先级
public class ThreadTest04 {
public static void main(String[] args) {
// 优先级分为:1-10
// 最小、标准、最大优先值
System.out.println(Thread.MIN_PRIORITY); // --> 1
System.out.println(Thread.NORM_PRIORITY); // --> 5
System.out.println(Thread.MIN_PRIORITY); // --> 10
System.out.println("-------------------------");
// 创建线程
Thread t1 = new Thread(new Processor4());
Thread t2 = new Thread(new Processor4());
// 默认优先级都为5
System.out.println("修改前"+t1.getPriority()); // --->5
System.out.println("修改前"+t2.getPriority()); // --->6
// 设置线程名称
t1.setName("t1");
t2.setName("t2");
// 设置线程优先级
t1.setPriority(5);
t2.setPriority(6);
System.out.println("修改后"+t1.getPriority()); // --->5
System.out.println("修改后"+t2.getPriority()); // --->6
// 启动线程
t1.start();
t2.start();
}
}
class Processor4 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 80; i++) {
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
线程堵塞
使用sleep()方法堵塞线程
// 1.Thread.sleep(毫秒)
// 2.sleep是一个静态方法
// 3.该方法的作用是:阻塞当前的线程腾出CPU,让给其他线程
public class ThreadTest05 {
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName()+"------>"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 创建Thread
Thread t1 = new Thread(new Processor5());
// 设置线程名称
t1.setName("t1");
// 启动线程
t1.start();
}
}
class Processor5 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()方法让给同一个优先级的线程让位。但是让位时间不固定。
注:yield()方法与sleep()方法相同,都是静态方法。
public class ThreadTest09 {
public static void main(String[] args) {
Thread thread = new Thread(new Thread09());
thread.setName("t");
thread.start();
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
class Thread09 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 200; i++) {
if (i % 20 == 0) {
Thread.yield(); // 如果是20整数时候,让位给main线程
}
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
join()方法合并线程
注:1.此方法是个成员方法。
2.此方法在哪个线程,就跟哪个线程调用。
public class ThreadTest10 {
public static void main(String[] args) {
// 创建线程
Thread t = new Thread(new Processor10());
// 重命名线程名称
t.setName("t");
// 启动t线程
t.start();
// t线程与主线程合并
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
class Processor10 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
中断堵塞
使用interrupt()方法异常中断堵塞
// 中断线程睡眠
// 让t线程中从堵塞状态中断:使用异常中断线程堵塞
public class ThreadTest07 {
public static void main(String[] args) {
Thread t = new Thread(new Processor7());
// 设置线程名称
t.setName("t");
// 启动线程
t.start();
// 主线程延迟5秒后打断t线程的堵塞
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 终端对t线程的堵塞
t.interrupt();
}
}
class Processor7 implements Runnable {
@Override
public void run() {
try {
Thread.sleep(3000000);
System.out.println("HelloWorld!"); // 注:用异常强制中断睡眠,睡眠以下的语句不会执行!
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
正常方法中断堵塞
public class ThreadTest08 {
public static void main(String[] args) {
Thread08 thread08 = new Thread08();
Thread thread = new Thread(thread08);
thread.setName("t"); // 设置线程
thread.start(); // 启动线程
try {
Thread.sleep(5000); // 启动线程
} catch (InterruptedException e) {
e.printStackTrace();
}
thread08.run = false; // 开关
/*
* 以上程序执行过程:
* 创建线程并命名为t线程
* 主线程睡眠5秒钟,在5S中内CPU时间片均为t线程的,但是t线程每次执行需要1S,所以到最后
* 一秒钟时候,主线程结束堵塞,t线程被主线程关闭.*/
}
}
class Thread08 implements Runnable {
boolean run = true; // 声名t线程开关,默认此线程是开启的.
@Override
public void run() {
for (int i = 0; i < 5; i++) {
if (run) { // t
try {
Thread.sleep(1000); // 每次执行延迟1S
System.out.println("HelloWorld!!");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->"+i);
}else {
return; // 如果t线程关闭则直接return
}
}
}
}
线程同步
引入:
异步编程模型
同步编程模型
例:t1和t2线程:
异步编程模型:t1线程执行t1的,t2线程执行t2的,两个线程之间谁也不等谁。
同步编程模型:t1线程和七线程执行,当t线程必须等t线程执行结束之后,线程才能执行,这是同步编程模型。
什么时候要同步呢?为什么要引入线程同步呢?
1.为了数据的安全,尽管应用程序的使用率降低,但是为了保证数据是安全的,必须加入线程同步机制
线程同步机制使程序变成了(等同)单线程。
2.什么条件下要使用线程同步?
第一:必须是多线程环境
第二:多线程环境共享同一个数据
第三:共享的数据涉及到修改操作
以下程序演示取款例子。
多线程同时对同一个账户进行取款操作,会出现什么问题?
答:会出现余额数据没有及时更新而导致数据不安全.
对象锁
public class ThreadTest11 {
public static void main(String[] args) {
// 创建一个对象
Account account = new Account("admin", 5000.0);
//
Thread t1 = new Thread(new Porcessor(account));
t1.setName("t1");
Thread t2 = new Thread(new Porcessor(account));
t1.setName("t2");
t1.start();
// 延迟执行t2线程
/* try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
t2.start();
}
}
// 取款流程
class Porcessor implements Runnable {
// 创建对象
Account account;
// 构造函数
public Porcessor(Account account) {
this.account = account;
}
@Override
public void run() {
account.withdraw(1000.0);
System.out.println("取款1000.0成功,余额:" + account.getBalance());
}
}
// 抽象账户
class Account {
// 账户名称
private String actno;
// 账户余额
private double balance;
public Account() {
}
public Account(String actno, double balance) {
this.actno = actno;
this.balance = balance;
}
public String getActno() {
return actno;
}
public void setActno(String actno) {
this.actno = actno;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
// 对外提供一个取款方法
public void withdraw(double money) {
// 把需要同步的代码,放到同步语句块中
// 写法一
synchronized (this) { // 使用synchronized关键字将当前对象(this)使用对象锁
double after = balance - money;
// 延迟
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 更新余额
this.setBalance(after);
}
}
// 写法二
// 把需要同步的代码,放到取款方法中
/* public synchronized void withdraw(double money) { // 使用synchronized关键字将当前对象(this)使用对象锁
double after = balance - money;
// 延迟
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 更新余额
this.setBalance(after);
}*/
/*以上两种方法哪种更好?
* 第一种方法更好;因为相比第二种方法第一种方法对需要同步的数据更加精准,极大的缩减的运行时间*/
}
类锁
当synchronized修饰静态方法时候,线程执行到此方法的时候会找类锁。
/* 类锁
* 以下程序m2方法会等m1方法执行完毕在执行m2方法吗?
* 答:不会因为以下程序的synchronized修饰的是静态方法
* 而修饰静态方法当线程执行到此处会直接使用类锁,与类锁无关
* 所以m2不会等待m1方法,但是由于主线程含有睡眠所以m1会优先执行*/
public class ThreadTest13 {
public static void main(String[] args) {
Myclass2 myclass2 = new Myclass2();
Thread t1 = new Thread(new Processor13(myclass2));
Thread t2 = new Thread(new Processor13(myclass2));
t1.setName("t1");
t2.setName("t2");
t1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
class Processor13 implements Runnable {
Myclass2 myClass2;
public Processor13(Myclass2 myClass2) {
this.myClass2 = myClass2;
}
@Override
public void run() {
if (Thread.currentThread().getName().equals("t1")) {
myClass2.m1();
}else if (Thread.currentThread().getName().equals("t2")) {
myClass2.m2();
}
}
}
class Myclass2 {
public static synchronized void m1() {
// 当synchronized修饰静态方法时候,线程执行到此方法的时候会找类锁.
System.out.println("m1");
}
public static void m2() {
System.out.println("m2");
}
}
死锁
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
// 死锁
public class DeadLock {
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = new Object();
Thread t1 = new Thread(new Myclass5(o1, o2));
Thread t2 = new Thread(new Myclass6(o1, o2));
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
}
class Myclass5 implements Runnable {
Object o1;
Object o2;
public Myclass5(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run() {
synchronized (o1) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2) {
}
}
}
}
class Myclass6 implements Runnable {
Object o1;
Object o2;
public Myclass6(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run() {
synchronized (o2) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1) {
}
}
}
}
守护线程
/* 守护线程
* 当其他所有的用户线程结束,则守护线程退出
* 守护线程一般都是无限循环的
* 例:GC(垃圾回收)就是线程守护*/
public class ThreadTest15 {
public static void main(String[] args) {
Thread t = new Thread(new Processor15());
t.setName("t");
t.setDaemon(true); // 将用户线程设置为守护线程
t.start(); // 启动线程
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "------>" + i);
}
}
}
class Processor15 implements Runnable {
int i = 0;
@Override
public void run() {
while (true) {
i++;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "------>" + i);
}
}
}
计定时器
// 定时器
public class ThreadTest16 {
public static void main(String[] args) {
// 创建定时器
try {
Timer t = null;
t = new Timer();
t.schedule(new Processor16(),new SimpleDateFormat("yyy-MM-dd HH:mm:ss SSSS").parse("2021-10-8 23:48:00 0000"),1000*60);
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Processor16 extends TimerTask {
@Override
public void run() {
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSSS").format(new Date()));
}
}