一、线程概述
- 进程:是操作系统中一个程序及其数据在处理机上顺序执行时所发生的活动。一个进程包含多个线程。
- 线程:也成轻量进程,是进程中某个单一顺序的控制流
- 多进程:在操作系统中同时运行多个任务(程序)
- 多线程:在同-应用程序中有多个顺序流同时执行
线程的生命周期:一个线程从创建到执行完的整个过程
多线程的开发需要注意安全问题,需要使用到线程同步,加锁。
- JVM看做进程:
- 垃圾回收线程(守护线程) : 定时的去回收资源
-
二、创建线程
2.1 创建线程的方式一
继承Thread类
重写run方法public class MyThread01 extends Thread{
public static void main(String[] args) {
//开启两个线程,采用抢占式获取cpu时间片。异步的状态。
MyThread01 t1 = new MyThread01();
t1.setName("线程一");
MyThread01 t2 = new MyThread01();
t2.setName("线程二");
t1.start();
t2.start();
}
@Override
public void run() {
for(int i=1 ;i<100;i++){
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2.2 创建线程的方式二
实现runnable实例
public class MyThread02 {
public static void main(String[] args) {
//创建Runnable实例
MyRunnable myRunnable = new MyRunnable();
//创建线程对象
Thread t1 = new Thread(myRunnable);
Thread t2 = new Thread(myRunnable);
//开启线程
t1.start();//就绪状态,有争夺cou资源的能力;
t2.start();
}
}
2.2 创建线程的方式三
通过匿名内部类创建线程 FutureTask
public static void main(String[] args) {
Thread thread = new Thread(new runnable(){
public void run(){
for(int i=1 ;i<100;i++){
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
})
t1.start();
t2.start();
}
三、线程周期以及线程操作
- 新建态:创建出来时,线程处于的状态
- 就绪态:线程调用start方法后处于就绪状态
- 运行态:线程争夺到cup资源后,会进入run方法执行任务时所处状态;
- 阻塞态:当运行态的线程遇到睡眠、控制台打印后;
死亡态:执行完run方法后处于的状态。
run方法执行方法;start方法启动方法
- start启动不一定执行run方法;
- 线程调度模型:抢占式(java默认方法)、时间片轮转;
可以通过设置抢占的优先级;
//获取线程的优先级的权重
int priority1 = t1.getPriority();
int priority2 = t2.getPriority();
System.out.println(t1+"============>"+priority1);
System.out.println(t2+"============>"+priority2);
//设置权重比例
t1.setPriority(9);
t2.setPriority(1);
yield()让位方法 运行态—>就绪态
public void run() {
for (int i = 1 ; i < 100 ; i++){
/* if (i % 5 == 0){
//让位方法,让当前线程从运行状态进入就绪状态,重新争夺cpu时间片
Thread.yield();
}
*/
//Thread.currentThread() 获取当前正在执行任务的线程
System.out.println(Thread.currentThread().getName()+" ===> "+i);
其他方法
join()当前线程进入阻塞,指定线程执行,直到指定线程结束当前线程才可以继续。 ```java public static void main(String[] args) {
MyThread06 t1 = new MyThread06(); t1.setName(“线程1”); t1.start();
boolean alive = t1.isAlive();
System.out.println(“t1线程是否是存活的状态 “+alive);
for (int i = 1 ; i < 100 ; i++){
/*
main作为主线程,t1与main线程合并,
main需要等到ti执行结束才能继续执行;
if (i == 50){
try {
//合并线程
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}*/
System.out.println(Thread.currentThread().getName()+" ===> "+i);
//睡眠的方法
//自己把自己挂起
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
} //唤醒t1线程 //t1.resume();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//通过异常机制来中断睡眠 t1.interrupt();
}
/ 线程在执行任务的时候会调用该方法 / @Override public void run() {
for (int i = 1 ; i < 100 ; i++){
if (Thread.currentThread().getName().equals("线程1")){
if (i == 50){
//线程自己把自己挂起 处于长久睡眠的状态
//Thread.currentThread().suspend();
//中断线程 让当前线程立即进入死亡状态
// Thread.currentThread().stop();
try {
//睡眠一年
Thread.sleep(1000 * 60 * 60 * 24 * 365);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//Thread.currentThread() 获取当前正在执行任务的线程
System.out.println(Thread.currentThread().getName()+" ===> "+i);
//睡眠的方法
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- suspend0线程自己把自己挂起
- resume(线程自己把自己唤醒
> #t1.stop0:中断线程
> interrupt()中断睡眠
> t1.isAlive()判断指定线程是否处于活动状态
<a name="FUeiJ"></a>
### 四、“龟兔赛跑”案例
> 需求描述:<br />赛道1000m,兔子100m/s ,乌龟50m/s,兔子到900米位置把(挂起)睡觉,乌龟一直跑,乌龟到终点唤起兔子。以线程模拟。
```java
public class Rb extends Thread {
private String name;
public Rb(String name) {
super(name);
this.name = name;
}
@Override
public void run() {
for (int i = 0; i <= 900 ; i+=100) {
System.out.println(Thread.currentThread().getName()+"跑了"+(i + 100)+"米");
if (i == 800){
System.out.println(Thread.currentThread().getName() +"开始睡觉了。。。。。。");
//兔子自己把自己挂起
Thread.currentThread().suspend();
}
//模拟1s
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"跑完了全程,兔子比赛失败!");
}
}
public class To extends Thread {
private String name;
private Rb rabbit;
public To(String name, Rb rabbit) {
//给线程起名字
super(name);
this.name = name;
this.rabbit = rabbit;
}
/**
* 唤醒兔子的方法
*/
public void hxRabbit(){
System.out.println(Thread.currentThread().getName()+"唤醒了"+ rabbit.getName());
rabbit.resume();
}
@Override
public void run() {
for (int i = 0; i <= 950 ; i+=50) {
System.out.println(Thread.currentThread().getName()+"跑了"+(i + 50)+"米");
//模拟1s
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//乌龟要唤醒兔子
hxRabbit();
System.out.println(Thread.currentThread().getName()+"跑完了全程,乌龟取得了胜利!");
}
}
五、线程安全
5.1 线程不安全发生的条件
- 多线程的并发;
- 有共享的数据;
- 共享数据有修改的行为;
5.2 处理线程安全问题
1. 线程同步锁synchronized三种用法
synchronized (共享对象){}里面的共享对象一定要是所有参与同步的线程共同的对象。
第一种:同步代码块 仅锁住想锁住的内容。 synchronized(线程共享对象){ 同步代码块: }
第二种:在实例方法上使用synchronized 必须所著整个方法的内容。实例方法中加锁是共享方法。
第三种:在静态方法上使用synchronized 静态方法中的synchronized锁是类级别的锁属于共享类。
2. 线程同步
安全问题应当优先考虑,效率其次。
- 异步编程模型
线程A和线程B,各自执行各自的,A不管B,B不管A,谁也不需
要等谁,这种编程模型叫做:异步编程模型。其实就是:多线程
并发效率较高
- 同步编程模型
线程A和线程B,在线程A执行的时候,必须等待B线程执行结束,或
者说在A线程执行的时候,必须等待B线程执行结束,两个线程之间
发生了等待关系,这就是同步程模型。效率较低。
- 线程同步时使用案例
假设有张三线程和李四线程同时并发执行程序,this指的是当前的账户实例,如果张三线程和李四线程同时来执行取款的操作,那么这个两个线程会取争夺this对象锁,如果张三先抢到,那么张三先进入代码块去执行代码,这个时候,李四线程只能在锁池中进行等待。必须要等张三线程执行完之后,李四线程才能重新获取对象锁,进入代码块执行代码!哪个线程拥有对象锁,该线程就拥有了进入代码块执行代码的权力
public class Account {
//持卡人姓名
private String accountName;
private double balance;
public Account(String accountName, double balance) {
this.accountName = accountName;
this.balance = balance;
}
public void getMoney(double money){
//使用synchronized将需要同步的方法内的代码块扩起来。
synchronized (this){
//this指的是当前调用改方法的对象。是对改对象的同步操作;
//获取当前账户的余额
double balance = this.getBalance();
try {
//模拟网络延迟
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//在当前的余额基础上进行扣款
this.setBalance(balance - money);
}
System.out.println(Thread.currentThread().getName()+"取走了"+money+"元,账户剩余"+this.getBalance());
}
}
六、赛跑案例二
需求如下
龟兔赛跑:
场景如下。兔子、乌龟和公鸡进行赛跑,其中兔子每秒0.5米的速度,每跑2米休息10秒;乌龟每秒跑0.1米,
不休息;公鸡每秒0.8米,每跑3秒需要吃一条虫子,耗时0.6秒。 当其中一个跑到终点后其他动物就不跑了;
比赛道路长20米。试用多线程模拟该比赛过程。
1、创建工程。
2、使用继承技术,创建动物类,乌龟、兔子和公鸡需要从该类继承
3、时刻记录每个动物当前共计跑了多少米,用控制台输出
4、使用集合对象存储三个动物
5、其中一个到达终点后,其他动物停止跑动。
6、时刻记录三个动物一共跑了多少米,需要解决并发问题。
7、创建主函数启动该模拟过程。
案例设计思路:
创建动物类(作为父类)继承Thread类。
动物父类定义名字、速度、赛道长度名称等,并创建父类构造来给不同子<br />类继承以实现多态,封装各方法。
分别编辑子类。
每个动物类继承父类(相当于也继承了Thread类),聚合另外两个动物
类的对象(由于需要在本类结束时关停其余两个线程)。写本类构造方法
但是要super指向父类。构造方法不需要写入其余两类聚合对象,之后用
set方法赋值来解决。
在重写的run方法中写入每个线程运作的逻辑,注意本线程如果结束判断
其余线程是否存活,存活则关闭。
- 代码省略;
七、守护线程
- 守护线程一般属于后台线程,用户线程一旦结束,守护线程也会跟着结束。
- 守护线程一般是一个死循环,会守护用户线程。
守护线程的操作
thread.setDaemon(true);
thread.start;
垃圾回收线程(守护线程) : 定时的去回收资源
- 主线程 :其实就是主函数 是由jvm直接调用的线程
八、案例三:生产者消费者
8.1 Object类中的wait和notify方法
- wait()让正在该对象上活动的线程进入等待状态,无期限等待,直到被唤醒为止。wait)方法的调用,会让正在改对象的当前线程进入等待状态
- notify()方法作用只会随机选取一个处于等待池中的线程进入锁池去竞争获取锁的机会
- notifyAll:会让所有处于等待池的线程全部进入锁池去竞争获取锁的机会
- wait于notify必须要先加锁,必须在线程同步的条件下才能实现。
8.2案例需求:生产者生产了一共产品,消费者就要去消费。
实现代码如下:比较简单 ```java import java.util.HashMap; import java.util.List;
public class test01 {
} //存放产品的类 class House{ private List list;
public House(List list) {
this.list = list;
}
public List getList() {
return list;
}
public void setList(List list) {
this.list = list;
}
} //生产者类 class thread01 extends Thread{ private House house;
public thread01(House house) {
this.house = house;
}
@Override
public void run() {
int i = 10;
while(i>0){
synchronized (house){
if(house.getList().size()>0){
try {
this.house.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
house.getList().add(new Object());
System.out.println(Thread.currentThread().getName()+"生产了产品为:"+this.house.getList().get(0));
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.house.notifyAll();
}
i--;
}
}
}
class thread02 extends Thread{ private House house;
public thread02(House house) {
this.house = house;
}
@Override
public void run() {
int j = 10;
while(j>0){
synchronized (house){
if(house.getList().size()==0){
try {
this.house.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Object object = house.getList().remove(0);
//注意这个地方不能和生产者一致,由于以及在上一步移除(消费)了共享区域中数据,没办法直接通过list读取。
System.out.println(Thread.currentThread().getName()+"消费了产品为:"+object);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.house.notifyAll();
}
j--;
}
}
}
```java
public class test02 {
public static void main(String[] args) {
House house = new House(new ArrayList());
thread01 thread01 = new thread01(house);
thread02 thread02 = new thread02(house);
thread01.setName("生产者");
thread02.setName("消费者");
thread01.start();
thread02.start();
}
}