线程
线程(thread)是一个程序内部的一条执行路径
我们之前启动程序执行后,main方法的执行其实就是一条单独的执行路径
程序中只有一条执行路径,那么这个程序就是单线程的程序
多线程
概述
多线程是指从软硬件上实现多条执行流程的技术
多线程的作用
多线程的创建
方式一:继承Thread类
Thread类:
Java是通过java.lang.Thread类来代表线程
按照面向对象的思想,Thread类应该提供多线程的方式
方案:
缺点;
线程类已经继承了Thread类,无法继承其他类,不利于扩展
优点:
编码简单
package com.itxue.d1_creat; //多线程创建方式一:继承thread类实现 public class ThreadDemo01 { public static void main(String[] args) { Thread r = new MyThread(); r.start(); //执行的还是run方法 for (int i = 0; i < 5; i++) { System.out.println("主线程执行输出"+i); } } } class MyThread extends Thread{ // 重写Run方法 public void run(){ for (int i = 0; i < 5; i++) { System.out.println("子线程执行输出"+i); } } }
方式二:实现Runnable接口
方案
Thread的构造器
package com.itxue.d1_creat; public class ThreadDemo02 { public static void main(String[] args) { // 创建一个任务对象 Runnable r = new MyRunnable(); Thread t = new Thread(r); t.start(); for (int i = 0; i < 5; i++) { System.out.println("主线程执行输出"+i); } } } class MyRunnable implements Runnable{ public void run(){ for (int i = 0; i < 5; i++) { System.out.println("子线程在执行"+i); } } }
优缺点
方案二:实现Runnable接口(匿名内部类形式)
package com.itxue.d1_creat; public class ThreadDemo02Other { public static void main(String[] args) { // 创建一个任务对象 Runnable r =new Runnable() { @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println("子线程执行输出"+i); } } }; Thread t = new Thread(r); t.start(); for (int i = 0; i < 5; i++) { System.out.println("主线程执行输出"+i); } } }
package com.itxue.d1_creat; public class ThreadDemo02Other { public static void main(String[] args) { // 创建一个任务对象 Runnable r = new Runnable() { @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println("子线程1执行输出"+i); } } }; Thread t = new Thread(r); t.start(); new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("子程序3执行输出"+i); } } }).start(); new Thread(()-> { for (int i = 0; i < 5; i++) { System.out.println("子线程2执行输出"+i); } }).start(); for (int i = 0; i < 5; i++) { System.out.println("主线程执行输出"+i); } } }
方式三:JDK5.0新增:实现Callable接口
方案
package com.itxue.d1_creat; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; public class ThreadDemo03 { public static void main(String[] args) { //创建任务对象 Callable<String> call = new MyCallable(100); //4.将Callable任务对象交给FutureTask对象 // FutureTask对象的作用1:是Runnable的对象(实现了Runnable接口,可以交给Thread) // FutureTask对象的作用2:线程执行完毕之后通过get方法返回线程完成的结果 FutureTask<String> f1 = new FutureTask<>(call); // 5.交给线程处理 Thread r = new Thread(f1); //6.启动线程 r.start(); Callable<String> call2 = new MyCallable(200); FutureTask<String> f2 = new FutureTask<>(call2); Thread t2 = new Thread(f2); t2.start(); try { String rs1 = f1.get(); System.out.println("第二个线程的结果是:"+rs1); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } try { String rs2 = f2.get(); System.out.println("第二个线程的结果是"+rs2); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } } class MyCallable implements Callable<String>{ private int n; public MyCallable(int n){ this.n = n; } @Override public String call() throws Exception { int sum = 0; for (int i = 1; i <= n; i++) { sum += i; } return "子线程的输出结果是:"+sum; } }
Thread的常用方法
currentThread 那个主线程执行它,他就得到那个线程对象
Thread的构造器
Thread类的线程休眠方法
package com.itxue.d2_api;
public class ThreadDemo02 {
public static void main(String[] args) throws Exception {
for (int i = 0; i < 5; i++) {
System.out.println("输出"+i);
if (i == 3){
Thread.sleep(3000);
}
}
}
}
总结
线程安全问题
多个线程同时操作同一个共享资源的时候可能会出现业务安全问题
实例演示
总结
存在多线程并发
同时访问共享资源
存在修改共享资源
案例:取钱案例
Account类
package com.itxue.d3_thread_safe;
public class Account {
private String IdName;
private int money;
public Account() {
}
public Account(String idName, int money) {
IdName = idName;
this.money = money;
}
public String getIdName() {
return IdName;
}
public void setIdName(String idName) {
IdName = idName;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
public void drawMoney(double money){
String name = Thread.currentThread().getName();
if(this.money >= money){
System.out.println(name + "取钱成功,吐出"+money);
this.money -= money;
System.out.println(name+"取钱后,剩余的钱为"+this.money);
}else{
System.out.println("余额不足");
}
}
}
Thread类
package com.itxue.d3_thread_safe;
public class DrawThread extends Thread{
private Account acc;
public DrawThread(Account acc,String name){
super(name);
this.acc = acc;
}
@Override
public void run() {
acc.drawMoney(100000);
}
}
package com.itxue.d3_thread_safe;
public class ThreadDemo {
public static void main(String[] args) {
Account acc = new Account("ACBC-123",100000);
new DrawThread(acc,"小明").start();
new DrawThread(acc,"小红").start();
}
}
线程同步
可以解决安全问题
线程同步的核心思想
加锁:把共享资源进行上锁,每次只能一个线程进入访问完毕后解锁,然后其他线程进来
方式一:同步代码块
作用;把出现线程安全问题的核心代码上锁
原理:每次只能一个线程进入,执行完毕后自动解锁,其他线程才可以进来执行
锁对象的要求
理论上:锁对象只要对于当前同时执行的线程来说是同一个对象即可
锁对象的规范要求
1.锁对象用任意唯一的对象好不好?
不好,会影响其他线程的执行(例如:小黑,小白也前来取钱需要等待小红,小明的之一的线程结束才可以执行)
2.要求;
规范上:建议使用共享资源作为锁对象
对于实例方法建议使用this作为锁对象
Account类
对于静态方法建议使用字节码(类名.class)对象作为锁对象
同步方法
作用 :把出现的线程安全问题的核心方法上锁
原理;每次只能一个线程进入,执行完毕后自动解锁,其他线程才可以进来执行执行
同步方法的底层原理
Lock锁
概述
Lock的API
package d5_thread_synchronized_lock.d3_thread_safe;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Account {
private String IdName;
private int money;
// 锁对象唯一且不可替换
private final Lock lock = new ReentrantLock();
public Account() {
}
public Account(String idName, int money) {
IdName = idName;
this.money = money;
}
public String getIdName() {
return IdName;
}
public void setIdName(String idName) {
IdName = idName;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
public void drawMoney(double money){
String name = Thread.currentThread().getName();
lock.lock();
try {
if (this.money >= money) {
System.out.println(name + "取钱成功,吐出" + money);
this.money -= money;
System.out.println(name + "取钱后,剩余的钱为" + this.money);
} else {
System.out.println(name + "余额不足");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
线程通信
线程通信案例模拟
线程通信的前提 :线程通信通常是在多个线程操作同一个共享资源的时候需要进行通信,且要保证线程安全
Object类等待和唤醒的方法
package com.itxue.d6_thread_communication;
public class Phone {
private boolean flag = false;
public void run(){
//负责来电提醒的线程
new Thread(new Runnable() {
@Override
public void run() {
try {
while (true){
synchronized (Phone.this){
if(!flag){
System.out.println("来电提醒");
flag = true;
Phone.this.notify();
Phone.this.wait();
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
//负责接电话线程,正是接听
new Thread(new Runnable() {
@Override
public void run() {
try {
while (true){
synchronized (Phone.this) {
if (flag) {
//可以接听电话了
System.out.println("接听电话,通话五分钟");
Thread.sleep(5000);
flag = false;//代表继续等待接通电话
//等待自己,唤醒别人
Phone.this.notify();
Phone.this.wait();
}else{
Phone.this.notify();
Phone.this.wait();
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
public static void main(String[] args) {
Phone xiaomi = new Phone();
xiaomi.run();//开机了
}
}