1.概念
- 基本概念
- 程序是指令和数据的有序集合,其本身 没有任何运行的含义,是一个静态的概念
- 而进程祖师执行程序的一次执行过程,它是一个动态的概念.是系统资源分配额单位
- 通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义.线程是cpu调度和执行的单位
- 注意:很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核,如服务器.如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换得很快,所有就有同时执行的错觉
- 核心概念
- 线程就是独立的执行路径
- 在程序运行时,即使没有自己 创建线程,后台也会有多个线程,如主线程,gc线程
- main()称之为主线程,为系统的入口,用于执行整个程序
- 在一个进程中,如果开辟了多个线程,线程的运行有调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为的干预的
- 对同一份资源操作是,会存在资源抢夺的问题,需要加入并发控制
- 线程会带来额外的开销,如cpu调度时间,并发控制开销
- 每个线程都在自己的工作内存交互,内存控制不当会造成数据不一致
三种创建方式
- Thread class 继承Thread class 类(重点)
- Runnable接口 实现Runnnable接口(重点)
- Callable接口 实现Callable接口(了解)
2.继承Thread类
线程不一定立即执行,cpu安排调度
package study;
//创建线程方式一:继承Thread类,重写run()方法,调用start开启线程
//总结:注意,线程开启不一定立即执行,由cpu调度执行
public class Demo extends Thread {
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("run方法执行" + i);
}
}
public static void main(String[] args) {
// 创建一个线程对象
Demo s = new Demo();
// 调用start方法开启线程
s.start();
for (int i = 0; i < 800; i++) {
System.out.println("main方法执行" + i);
}
}
}
网图下载
package study;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import org.apache.commons.io.FileUtils;
//练习Thread,实现多线程同步下载图片
public class Demo extends Thread {
private String url;
private String name;
public Demo(String url, String name) {
this.url = url;
this.name = name;
}
//下载图片线程的执行体
@Override
public void run() {
WeDownloader weDownloader = new WeDownloader();
weDownloader.downloader(url, name);
System.out.println("下载了文件名为" + name);
}
public static void main(String[] args) {
Demo d1 = new Demo("https://avatar.csdnimg.cn/E/F/0/1_qq_34509897_1616259578.jpg", "1.jpg");
Demo d2 = new Demo("https://csdnimg.cn/medal/[email protected]", "002.png");
Demo d3 = new Demo("https://csdnimg.cn/medal/[email protected]", "003.png");
//下载顺序并不是按照d1,d2,d3
d1.start();
d2.start();
d3.start();
}
}
//下载器
class WeDownloader {
// 下载方法
public void downloader(String url, String name) {
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("IO异常,downloader方法出现问题");
}
}
}
3.实现Runnable接口
package study;
//创建线程方式2:实现runnable接口,重写run方法,执行线程需要丢入runnable接口实现类,调用start方法
public class Demo implements Runnable {
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("run方法执行" + i);
}
}
public static void main(String[] args) {
// 创建runnable接口的实现类对象
Demo demo = new Demo();
// 创建线程对象,通过线程对象来开启我们的线程,代理
// Thread thread = new Thread(demo);
// thread.start();
new Thread(demo).start();
for (int i = 0; i < 500; i++) {
System.out.println("main方法执行" + i);
}
}
}
- 初识并发问题
package study;
//多个线程同时操作同一个对象
//买火车票例子
public class Demo implements Runnable {
// 票数
private int ticketNums = 10;
@Override
public void run() {
while (true) {
if (ticketNums <= 0) {
break;
}
try {
// 模拟延时
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
// currentThread返回当前线程
System.out.println(Thread.currentThread().getName() + "--->拿到了第" + ticketNums-- + "张票");
}
}
public static void main(String[] args) {
Demo demo = new Demo();
new Thread(demo, "张三").start();
new Thread(demo, "李四").start();
new Thread(demo, "王五").start();
}
}
发现问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱
- 模拟龟兔赛跑
package study;
//模拟龟兔赛跑
public class Demo implements Runnable {
// 胜利者
private static String winner = null;
@Override
public void run() {
boolean flag = false;
for (int i = 0; i <= 100; i++) {
flag = gameOver(i);
if (flag) {
break;
}
System.out.println(Thread.currentThread().getName() + "-->跑了" + i + "步");
}
}
//判断是否结束比赛
private boolean gameOver(int steps) {
//如果步数为100,则判断为胜利者
if (steps == 100) {
winner = Thread.currentThread().getName();
System.out.println("winner is" + winner);
return true;
}
return false;
}
public static void main(String[] args) {
Demo demo = new Demo();
new Thread(demo, "兔子").start();
new Thread(demo, "乌龟").start();
}
}
4.实现Callable接口(了解接口)
- 实现Callable接口,需要返回值类型
- 重写call方法,需要抛出异常
- 创建目标对象
- 创建执行服务 ExecutorService ser = Executors.newFixedThreadPool(3);
- 提交执行 Future r1 = ser.submit(d1);
- 获取结果 boolean rs1 = r1.get();
- 关闭服务 ser.shutdown();
利用Callable改造下载图片案例:
package study;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.*;
import java.util.concurrent.*;
import org.apache.commons.io.FileUtils;
/**
* 线程创建方式三:实现Callable接口 1.可以定义返回值 2.可以抛出异常
*
* @author badwoman
*
*/
public class Demo implements Callable<Boolean> {
private String url;
private String name;
public Demo(String url, String name) {
this.url = url;
this.name = name;
}
//下载图片线程的执行体
@Override
public Boolean call() {
WeDownloader weDownloader = new WeDownloader();
weDownloader.downloader(url, name);
System.out.println("下载了文件名为" + name);
return true;
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
Demo d1 = new Demo("https://avatar.csdnimg.cn/E/F/0/1_qq_34509897_1616259578.jpg", "1.jpg");
Demo d2 = new Demo("https://csdnimg.cn/medal/[email protected]", "002.png");
Demo d3 = new Demo("https://csdnimg.cn/medal/[email protected]", "003.png");
// 创建执行服务
ExecutorService ser = Executors.newFixedThreadPool(3);
// 提交执行
Future<Boolean> r1 = ser.submit(d1);
Future<Boolean> r2 = ser.submit(d2);
Future<Boolean> r3 = ser.submit(d3);
// 获取结果
boolean rs1 = r1.get();
boolean rs2 = r2.get();
boolean rs3 = r3.get();
// 关闭服务
ser.shutdown();
}
}
//下载器
class WeDownloader {
// 下载方法
public void downloader(String url, String name) {
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("IO异常,downloader方法出现问题");
}
}
}
5.静态代理模式
package study;
/**
* 静态代理模式总结: 真实对象和代理对象都要同时实现一个接口
* 代理对象要代理真实角色
*
* @author badwoman
*
*/
public class Demo {
public static void main(String[] args) {
// lamda表达式
new Thread(() -> System.out.println("我爱你")).start();
new WeddingCompant(new You()).HappyMarry();
}
}
interface Marry {
// 结婚事件
void HappyMarry();
}
//这是结婚角色
class You implements Marry {
@Override
public void HappyMarry() {
System.out.println("xx要结婚了");
}
}
//代理角色,帮助结婚
class WeddingCompant implements Marry {
// 代理真实目标角色
private Marry target;
public WeddingCompant(Marry target) {
this.target = target;
}
@Override
public void HappyMarry() {
before();
this.target = target;// 真实对象
after();
}
private void before() {
System.out.println("结婚之前");
}
private void after() {
System.out.println("结婚之后");
}
}
6.lamda表达式
- 理解Functional Interface(函数式接口)是学习java8 lambda表达式的关键所在
- 函数式接口的定义:
- 只包含唯一一个抽象方法的接口
- 对于 函数式接口,我们可以通过lambda表达式来创建该接口的对象
package study;
/**
* lambda表达式只能有一行的代码的情况下才能简化成为一行,如果有多行,则用代码块包裹
* 前提是接口为函数式接口
* 多个参数也可以去掉参数类型,要去掉就全部去掉,而且不想加上括号
* @author badwoman
*
*/
public class Demo {
public static void main(String[] args) {
ILike like = a -> System.out.println(a);
like.love(5);
}
}
interface ILike {
void love(int a);
}
7.线程状态
五大状态:
- 停止线程
package study;
import java.util.Iterator;
public class Demo implements Runnable {
// 1.设置一个标识符
private boolean flag = true;
@Override
public void run() {
int i = 0;
while (flag) {
System.out.println("run...Thread" + i++);
}
}
// 2.设置一个公开的方法停止线程,转换标志位
public void stop() {
this.flag = false;
}
public static void main(String[] args) {
Demo demo = new Demo();
new Thread(demo).start();
for (int i = 0; i < 200; i++) {
System.out.println("main" + i);
if (i == 100) {
demo.stop();
System.out.println("该线程停止了");
}
}
}
}
- 线程休眠
- sleep时间达到后线程进入就绪状态
- 每一个对象都有一个锁,sleep不会释放锁
package study;
import java.sql.Date;
import java.text.SimpleDateFormat;
public class Demo {
public static void main(String[] args) {
Date date = new Date(System.currentTimeMillis());
// 打印当前系统时间
while (true) {
try {
Thread.sleep(1000);
System.out.println(new SimpleDateFormat("HH:mm:ss").format(date));
date = new Date(System.currentTimeMillis());
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
}
-
线程礼让
-
让当前正在执行的线程暂停,但不阻塞
-
将线程从运行状态转为就绪状态
-
让cpu重新调度,礼让不一定成功!看cpu心情
-
Thread.yield();礼让
-
-
join
- join合并线程,待次线程执行完成后,在执行其他线程,其他线程阻塞
- 可以想象成插队
package study;
public class Demo implements Runnable {
@Override
public void run() {
for (int i = 0; i < 500; i++) {
System.out.println("线程VIP来了" + i);
}
}
public static void main(String[] args) throws InterruptedException {
// 启动线程
Demo demo = new Demo();
Thread thread = new Thread(demo);
thread.start();
for (int i = 0; i < 200; i++) {
if (i == 100) {
thread.join();
}
System.out.println("main" + i);
}
}
}
- 线程状态观测
package study;
import java.lang.Thread.State;
public class Demo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
for (int i = 0; i <=5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
System.out.println("----------");
}
});
// 观测状态
State state = thread.getState();
System.out.println(state);
// 观测启动后
thread.start();//启动线程
state = thread.getState();
System.out.println(state);
while (state!=thread.getState().TERMINATED) {
thread.sleep(100);
state = thread.getState();
System.out.println(state);
}
// thread.start(); 再次启动线程会报错,原因:线程死亡后不能再次启动
}
}
8.线程优先级
package study;
public class Demo implements Runnable{
public static void main(String[] args) {
//主线程默认优先级
// getPriority获得当前优先级
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
Demo demo = new Demo();
Thread t1=new Thread(demo);
Thread t2=new Thread(demo);
Thread t3=new Thread(demo);
Thread t4=new Thread(demo);
Thread t5=new Thread(demo);
Thread t6=new Thread(demo);
// 先设置优先级在启动
t1.setPriority(7);
t2.setPriority(2);
t3.setPriority(1);
t4.setPriority(6);
t5.setPriority(Thread.MAX_PRIORITY);//MAX_PRIORITY=10
t6.setPriority(3);
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
t6.start();
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
}
}
9.守护线程
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕,不用等待守护线程执行完毕
- 用户线程:main() 守护线程:后台记录操作日志,监控内存,垃圾回收等待
package study;
public class Demo {
public static void main(String[] args) {
Thread thread = new Thread(new Test2());
thread.setDaemon(true);//默认是false表示是用户线程,true表示是守护线程
thread.start();
new Thread(new Test1()).start();
}
}
class Test1 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("用户线程执行" + i);
}
System.out.println("用户线程结束");
}
}
class Test2 implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("守护线程执行");
}
}
}
10.线程同步机制
11.三大不安全案例
package study;
//不安全买票
//问题:出现两个拿到同一张票
//修改: 添加synchronize 把买票设置为同步方法,锁的是对象
public class Demo {
public static void main(String[] args) {
BuyTicket satrtion = new BuyTicket();
new Thread(satrtion, "张三").start();
;
new Thread(satrtion, "李四").start();
;
new Thread(satrtion, "王五").start();
;
}
}
class BuyTicket implements Runnable {
private int ticketNums = 10;
boolean flag = true;// 外部停止方式
@Override
public void run() {
// 买票
while (flag) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
buy();
}
}
private synchronized void buy() {
// 判断是否有票
if (ticketNums <= 0) {
flag = false;
return;
}
// 买票
System.out.println(Thread.currentThread().getName() + "拿到" + ticketNums--);
}
}
package study;
//不安全取钱
//两个人去银行取钱
//问题:银行余额出现负数
//修改:使用同步块锁需要增删改的对象
public class Demo {
public static void main(String[] args) {
Account account = new Account(100, "基金");
Drawing father = new Drawing(account, 50, "父亲");
Drawing mother = new Drawing(account, 100, "母亲");
father.start();
mother.start();
}
}
//账户
class Account {
int money;// 余额
String name;// 卡名
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
//银行:模拟存款
class Drawing extends Thread {
Account account;// 账户
int drawingMoney;// 取了多少钱
int nowMoney;
public Drawing(Account account, int drawingMoney, String name) {
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
// 取钱
@Override
public void run() {
synchronized (account) {
// 判断有没有钱
if (account.money - drawingMoney < 0) {
System.out.println(Thread.currentThread().getName() + "钱不够了取不了");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
// 卡内余额
account.money -= drawingMoney;
// 手里的钱
nowMoney += drawingMoney;
System.out.println(account.name + "余额为" + account.money);
System.out.println(this.getName() + "手里的钱" + nowMoney);
}
}
}
package study;
import java.util.ArrayList;
import java.util.List;
//问题:长度不能填满集合
//修改:使用同步块
public class Demo {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (int i = 0; i < 10000; i++) {
new Thread(() -> {
synchronized (list) {
list.add(Thread.currentThread().getName());
}
}).start();
;
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
System.out.println(list.size());
}
}
总结:
- synchronized默认锁的是当前类对象
- 我们需要说的对象是变化的量,需要增删改的对象
12.补充: 线程安全的集合
package study;
//不需要添加锁
import java.util.concurrent.CopyOnWriteArrayList;
public class Demo {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
System.out.println(list.size());
}
}
13.死锁
错误示范:
由于两人同时想获得对方的锁,但又不放开已经获得的锁,造成程序停止
package study;
public class Demo {
public static void main(String[] args) {
Makeup girl1 = new Makeup(0,"girl1");
Makeup girl2 = new Makeup(1,"girl12");
girl1.start();
girl2.start();
}
}
//口红
class Lipstick {
}
//镜子
class Mirror {
}
class Makeup extends Thread {
// 需要的资源只有一份,用static来保证只有一份
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
int choice;// 选择
String girl;// 使用化妆品的人
public Makeup(int choice, String girl) {
this.choice = choice;
this.girl = girl;
}
@Override
public void run() {
try {
makeup();
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
private void makeup() throws InterruptedException {
if (choice == 0) {
synchronized (lipstick) {
System.out.println(this.girl + "获得口红的锁");
Thread.sleep(1000);
synchronized (mirror) {
System.out.println(this.girl + "获得镜子的锁");
}
}
} else {
synchronized (mirror) {
System.out.println(this.girl + "获得镜子的锁");
Thread.sleep(3000);
synchronized (lipstick) {
System.out.println(this.girl + "获得口红的锁");
}
}
}
}
}
修改后:
package study;
public class Demo {
public static void main(String[] args) {
Makeup girl1 = new Makeup(0, "girl1");
Makeup girl2 = new Makeup(1, "girl12");
girl1.start();
girl2.start();
}
}
//口红
class Lipstick {
}
//镜子
class Mirror {
}
class Makeup extends Thread {
// 需要的资源只有一份,用static来保证只有一份
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
int choice;// 选择
String girl;// 使用化妆品的人
public Makeup(int choice, String girl) {
this.choice = choice;
this.girl = girl;
}
@Override
public void run() {
try {
makeup();
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
private void makeup() throws InterruptedException {
if (choice == 0) {
synchronized (lipstick) {
System.out.println(this.girl + "获得口红的锁");
Thread.sleep(1000);
}
synchronized (mirror) {
System.out.println(this.girl + "获得镜子的锁");
}
} else {
synchronized (mirror) {
System.out.println(this.girl + "获得镜子的锁");
Thread.sleep(3000);
}
synchronized (lipstick) {
System.out.println(this.girl + "获得口红的锁");
}
}
}
}
14.Lock锁
package study;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
public class Demo {
public static void main(String[] args) {
Makeup girl1 = new Makeup(0, "girl1");
Makeup girl2 = new Makeup(1, "girl12");
girl1.start();
girl2.start();
}
}
//口红
class Lipstick {
}
//镜子
class Mirror {
}
class Makeup extends Thread {
// 需要的资源只有一份,用static来保证只有一份
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
int choice;// 选择
String girl;// 使用化妆品的人
public Makeup(int choice, String girl) {
this.choice = choice;
this.girl = girl;
}
@Override
public void run() {
try {
makeup();
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
private void makeup() throws InterruptedException {
final ReentrantLock lock = new ReentrantLock();// 定义锁
if (choice == 0) {
lock.lock();// 上锁
System.out.println(this.girl + "获得口红的锁");
lock.unlock();// 解锁
Thread.sleep(1000);
System.out.println(this.girl + "获得镜子的锁");
} else {
lock.lock();
System.out.println(this.girl + "获得镜子的锁");
lock.unlock();
Thread.sleep(3000);
System.out.println(this.girl + "获得口红的锁");
}
}
}
15.线程池
package study;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo {
public static void main(String[] args) {
// 1.创建服务,创建线程池
// 参数为:线程池大小
ExecutorService service = Executors.newFixedThreadPool(10);
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
// 2.关闭链接
service.shutdown();
}
}
class MyThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}