文章目录
线程
在某个时间,CPU的某个核中只能处理一个进程,一个进程中只能处理一个线程
底层依赖于线程的轮换,才产生多个任务之间的切换
线程工作职责:
- 和CPU进行交互
- 和硬件进行交互
多线程的好处
- 线程和硬件进行交互时,CPU处于空闲状态,为了提高CPU的利用率,引入多线程(理论上可以提高100%利用率)
进程和线程
一个进程中可以有多个线程
每个进程开辟一块空间,进程中的线程不用开辟空间
线程的执行效率,速度比进程快
- 程序:静态的代码块
- 进程:具有独立的内存,动态的执行过程
- CPU执行的任务分成的多个小任务
- 线程:在进程内,动态的执行过程。线程可以同时运行(相对意义上的)
- 进程分成的多个小任务
实现线程的两种方式:
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
继承Thread类
- 实现步骤:
- 继承Thread类
- 重写public void run() 方法
- run方法也叫线程体方法,线程执行的代码放到run方法中
- 启动线程:start方法,由系统分配时间片,由cpu自动调用run方法
Thread.sleep(1000)
:线程休眠线程体方法------run()方法,线程的执行语句必须方法run方法中,run方法不用自己调用
class MyThread extends Thread{
//线程体方法------run()方法,线程的执行语句必须方法run方法中,run方法不用自己调用
@Override
public void run() {
for(int i = 1; i<=10;i++) {
//进程休眠
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("MyThread");
}
}
}
class MyThread2 extends Thread{
//线程体方法------run()方法
@Override
public void run() {
for(int i = 1; i<=10;i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("MyThread2");
}
}
}
public class Demothread {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread2 t2 = new MyThread2();
t1.start();
t2.start();
}
}
使用匿名类实现
方法一:
/**
* 使用匿名类继承Thread创建Thread对象
*/
new Thread() {
@Override
public void run() {
for(int i = 0;i<=10;i++) {
//currentThread():获取正在执行的线程对象
//getName() : 获取线程的名称
System.out.println(Thread.currentThread().getName());
}
}
}.start();
方法二:
setName(String str):给线程起名字
Thread t1 = new Thread() {
@Override
public void run() {
for(int i = 0;i<=10;i++) {
//currentThread():获取正在执行的线程对象
//getName() : 获取线程的名称
System.out.println(Thread.currentThread().getName());
}
}
};
t1.setName("线程A");
t1.start();
实现Runnable接口
实现步骤
- 实现Runnable接口
重写run()方法
- 创建Thread进程对象,把实现Runnable类的对象作为Thread构造方法的参数传进去
最常用
MyRunnable r1 = new MyRunnable(); Thread t2 = new Thread(r1); //Thread类和MyRunnable类同级类,这样的设计模式为:装饰者模式
/**
* 实现Runnable接口
* 实现Runnable接口的类,不是线程类
* 实现线程功能,Thread(Runnable r),把是现实Runnable接口的类传进去,相当于重写Thread的run方法
* @author dell
*
*/
class MyRunnable implements Runnable{
//线程体方法------run()方法
@Override
public void run() {
for(int i = 1; i<=10;i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("MyRunnable");
}
}
}
public class Demothread {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyRunnable r1 = new MyRunnable();
Thread t2 = new Thread(r1);
t1.start();
t2.start();
}
}
使用匿名类实现
方法一
currentThread()
:获取正在执行的线程对象
getName()
: 获取线程的名称
new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0;i<=10;i++) {
System.out.println(Thread.currentThread().getName());
}
}
}).start();
方法二
Thread (Runnable target,String name)
------第二个参数给线程起名字
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0;i<=10;i++) {
System.out.println(Thread.currentThread().getName());
}
}
},"线程B");
t2.start();
实现Callable接口*(了解)*
步骤:
- 实现Callable接口
- 重写call()方法
- 在call()方法中写任务,有返回值
开启线程
- 通过线程池返回执行服务器
ExecutorService e = Executors.newCachedThreadPool();
- 将线程类添加到执行服务器,返回
Future
类
Future<Integer> submit = e.submit(new CDemo());
- 通过
Future
类对象调用get()
方法,获取线程返回值
submit.get()
package cn.tedu.callable;
import java.util.concurrent.*;
public class CallableDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//开启线程
//通过线程池返回执行服务器
ExecutorService e = Executors.newCachedThreadPool();
Future<Integer> submit = e.submit(new CDemo());
System.out.println(submit.get());//22
}
}
//线程类
class CDemo implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return 22;
}
}
常用的线程方法
currentThread()
:获取正在执行的线程对象
getName()
: 获取线程的名称
join()
:线程执行顺序
join()
调用
join()
方法的线程开始执行,直到执行完毕,当前线程继续执行
package thread;
public class JoinThread {
public static void main(String[] args) {
Thread t1 = new Thread() {
@Override
public void run() {
for(int i = 0;i<10;i++) {
System.out.println("t1");
}
}
};
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0;i<10;i++) {
if(i4) {
try {
t1.join();//-----------
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t2");
}
}
});
t2.start();
t1.start();
}
}
结果 |
---|
t2 t2 t2 t2 t1 t1 t1 t1 t1 t1 t1 t1 t1 t1 t2 t2 t2 t2 t2 t2 t1.join(),t1调用的join,所以t1线程执行,执行完毕后,再继续执行t2 |
线程的分类
- 前台线程、后台线程
- 两种线程的创建方式完全相同
- 新创建的线程默认都是前台线程
- 设置当前线程为后台线程的方法:
setDaemon(true)
- 两种线程完成的功能没有区别
- 两种线程不同的地方:
- 前台所有线程执行完毕,不管后台线程是否执行完毕,都被强制停止
前台线程
//前台线程
Thread rose = new Thread() {
@Override
public void run() {
for(int i = 0;i<=3;i++) {
System.out.println("jack,help me");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("扑通");
}
};
后台线程
//后台线程
Thread jack = new Thread() {
@Override
public void run() {
System.out.println("You jump,I jump!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
//设置后台进程
jack.setDaemon(true);
启动线程
rose.start();
jack.start();
线程的生命周期
-
新建状态:创建线程
new Thread();
- 可以直接进入就绪状态或者死亡状态
-
就绪状态:
start();
-
阻塞状态:可能是网络慢、sleep()、wait()方法等,这种状态结束了,重新到就绪状态
-
运行状态:Cpu执行
run()
方法的代码 -
死亡状态:
- 执行完run()方法------正常进入
yield()
方法------强制当前进程终止
图解
线程的优先级
- 3个等级:低(1),中(5),高(10),系统会自动设置优先级(5,中等优先级);理论上,级别越大可以抢占的资源概率越大
- 如果线程之间的优先级之差大于5,理论上可以抢占的资源概率就会稍微大一点
设置优先级:
setPriority(Thread.MIN_PRIORITY)
------低优先级setPriority(Thread.MAX_PRIORITY)
------高优先级setPriority(Thread.NORM_PRIORITY)
------中等优先级- 内存足够的时候,设置优先级没什么作用,只有在内存不足的时候,才会高优先级优先执行
获取优先级:
getPriority()
类型 | 等级 | 值 |
---|---|---|
public static final int |
MAX_PRIORITY (高优先级) |
10 |
public static final int |
MIN_PRIORITY (低优先级) |
1 |
public static final int |
NORM_PRIORITY (中等优先级) |
5 |
守护线程
setDaemon(true)
:true,设置为守护线程
特点:
- 被守护线程结束,其他的守护线程随之结束
- 线程要么是守护线程,要么是被守护线程
- 被守护线程可以有多个,只有等到所有的被守护线程结束,其他的守护线程才结束
package cn.tedu.thread;
public class DeamonDemo {
public static void main(String[] args) {
//创建线程对象
Thread t1 = new Thread(new Soilder(), "小兵甲");
Thread t2 = new Thread(new Soilder(), "小兵乙");
//设置成守护线程
t1.setDaemon(true);
t2.setDaemon(true);
t3.setDaemon(true);
t4.setDaemon(true);
t1.start();
t2.start();
t3.start();
t4.start();
//Boss---主线程:被守护线程
for (int i = 20; i >= 0 ; i--) {
System.out.println("Boss还剩"+i+"滴血");
}
}
}
class Soilder implements Runnable{
@Override
public void run() {
//小兵掉血
for (int i = 10; i >= 0 ; i--) {
System.out.println(Thread.currentThread().getName() + "还剩" + i + "滴血");
}
}
}
线程的调度
- 不同优先级
- 高先执行,低后执行
- 同优先级
- 先到先执行
线程同步
问题:
- 多线程之间存在相互抢占(CPU的执行权),发生在代码的每一行
- 产生了错误数据问题(重复,跳过,负数),导致了多线程数据安全问题
解决线程抢占问题:
当一个线程执行时,线程锁会把该线程锁住,直到执行完毕,解锁
实现步骤:
- 在操作共享变量的方法上添加关键字:
synchronized
,只要添加关键字synchronized的方法,一个线程访问,其他线程不能访问;直到当前线程释放锁,其他线程获取锁,才可以访问共享变量一个栈对应一个线程
同步代码锁
synchronized(锁对象){......}
锁对象:指定线程对象共享的范围(指定方法区资源默认共享所有线程对象)
this.getClass()
Math.class
String.class
该类.class
this
:特殊。使用this时,只能有一个this代表的对象,在多线程中,只能锁住一个实现了Runnable接口的对象。
package cn.tedu.test;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;
public class Demo {
public static void main(String[] args) throws IOException {
//创建properties对象
Properties p = new Properties();
p.load(new FileReader("ticket.properties"));
String count = p.getProperty("count");//动态获取每次给定的票数
Ticket t = new Ticket(Integer.parseInt(count));//创建总票数
//售票员
Seller s1 = new Seller(t);
Seller s2 = new Seller(t);
//线程
Thread t1 = new Thread(s1, "A");
Thread t2 = new Thread(s2, "B");
//对象锁为:this的情况下
//Seller s1 = new Seller(t);
//Thread t1 = new Thread(s1, "A");
//Thread t2 = new Thread(s1, "B");
//Thread t3 = new Thread(s1, "C");
//Thread t4 = new Thread(s1, "D");
//启动线程
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Seller implements Runnable {
Ticket ticket;
public Seller(Ticket ticket) {
this.ticket = ticket;}
@Override
public void run() {
while (true) {
synchronized (Seller.class) {
//同步代码锁---代码区域不会出现抢占
//Seller.class Math.class String.class 都在方法区,方法区资源被所有线程共享
//判断出循环的条件
if(ticket.getNum()<=0) break;
ticket.setNum(ticket.getNum() - 1);
System.out.println(Thread.currentThread().getName() + "卖出一张,剩余:" + ticket.getNum());
}
}
}
}
class Ticket {
private int num;
public Ticket() {
}
public Ticket(int num) {
this.num = num;}
public int getNum() {
return num;}
public void setNum(int num) {
this.num = num;}
}
同步方法锁
- 如果加在非静态的方法上,锁对象就是:
this
- 如果加在静态方法上,锁对象就是:
当前类.class
@Override
public synchronized void run() {
while (true) {
ticket.setNum(ticket.getNum() - 1);
System.out.println(Thread.currentThread().getName() + "卖出一张,剩余:" + ticket.getNum());
}
}
同步和异步
同步:在某个时刻只能有一个资源被使用
异步:在某个时刻可以被多个线程抢占同一资源
同步的一定是安全的,不安全的一定是异步
线程的死锁问题
原因:由于锁的嵌套,产生死锁问题
解决:
- 逻辑上尽量避免锁的嵌套;
- 设置线程的优先级,指定某个线程先执行
synchronized (printer) {
//--------
printer.print();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (scann){
//-------------
scann.scan();
}
}
synchronized (scann) {
//-----------
scann.scan();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (printer){
//-----------
printer.print();
}
}
线程池
作用:
- 控制线程数量(避免因为大量的线程导致的系统崩溃)
- 重用线程(避免频繁创建销毁线程)
概念:
首先创建一些线程,它们的集合称为线程池,当服务器接受到一个客户请求后,就从线程池中取出一个空闲的线程为之服务,服务完后不关闭该线程,而是将该线程还回到线程池中。
在线程池的编程模式下,任务是提交给整个线程池,而不是直接交给某个线程,线程池在拿到任务后,它就在内部找有无空闲的线程,再把任务交给内部某个空闲的线程,
一个线程只能执行一个任务,但我们可以同时向一个线程池提交多个任务
1
提高效率原理:
现有一个集合(数组),在集合中先把线程对象放进去,如果需要线程完成工作,有集合调度线程,完成任务之后,把线程放回到线程池(集合),线程可以重复使用
实现类:ExecutorService
- 线程池对象的创建:
ExecutorService threadPool = Executors.newFixedThreadPool(3);//初始化3个线程对象
ExecutorService threadPool = Executors.newFixedThreadPool(3);//初始化3个线程对象 for(int i = 0;i<3;i++){ Thread t = new Thread(new Runnable(){ @Override public void run() { System.out.println(Thread.currentThread().getName()); } }); threadPool.execute(t);//将任务提交给线程池 }
- 将任务提交给线程池
threadPool.execute(t);//放到线程池中
线程池的关闭:
shutdown
: 只是将线程池的状态设置为SHUTWDOWN状态,正在执行的任务会继续执行下去,没有被执行的则中断------通常会用这一个shutdownNow
: 将线程池的状态设置为STOP,正在执行的任务则被停止,没被执行任务的则返回
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestThreadPool {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(3);//初始化3个线程对象
for(int i = 0;i<3;i++){
Thread t = new Thread(new Runnable(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
threadPool.execute(t);//将任务提交给线程池
}
//threadPool.shutdown();
threadPool.shutdownNow();
}
}
生产与消费模型
- 每次生产的商品数量都要求是一个随机整数,保证每次剩余商品数量不能超过1000
- 每次消费的商品数量也是一个随机整数,保证每次剩余商品数量不能小于0
- 让生产和消费交替出现
代码
通过标志位flag,控制生产还是消费
- 如果为true,执行
- 如果为false,挂起
package cn.tedu.notify;
public class NotifyWaitDemo {
public static void main(String[] args) {
//创建商品类的对象
Product p = new Product();
//创建线程对象
new Thread(new Pruductor(p)).start();//生产
new Thread(new Pruductor(p)).start();//生产
new Thread(new Pruductor(p)).start();//生产
new Thread(new Consumer(p)).start();//消费
new Thread(new Consumer(p)).start();//消费
new Thread(new Consumer(p)).start();//消费
}
}
//代表线程生产的过程
class Pruductor implements Runnable {
Product p;
public Pruductor(Product p) {
this.p = p;
}
@Override
public void run() {
while (true) {
//保证一直生产
synchronized (p) {
while (!p.flag) {
//false:挂起;true:执行
//让线程等待
try {
Thread.sleep(1000);
p.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
int max = 1000 - p.getNum();//最大生产商品的数量
int count = (int) (Math.random() * (max + 1));
p.setNum(count+p.getNum());//生产随机数量
System.out.println("生产:" + count + "个商品,剩余:" + p.getNum() + "个");
p.flag = false;
//唤醒线程
p.notifyAll();
}
}
}
}
//表示线程消费过程
class Consumer implements Runnable{
Product p;
public Consumer(Product p) {
this.p = p;
}
@Override
public void run() {
while (true) {
//保证一直消费
synchronized (p){
while (p.flag){
//false:挂起;true:执行
try {
Thread.sleep(1000);
p.wait();//让线程等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//随机消费的商品数量
int count = (int) (Math.random() * (p.getNum() + 1));
//设置剩余商品数量
p.setNum(p.getNum()-count);
System.out.println("消费:" + count + "个商品,剩余:" + p.getNum() + "个");
p.flag = true;
//唤醒线程
p.notifyAll();
}
}
}
}
//代表商品类
class Product {
private int num;
boolean flag = true;//标志位---控制哪个线程执行
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
}
等待唤醒机制
- 根据wait()方法、notify()方法、标志位可以去控制线程对象之间的执行轨迹
- 结合锁才能使用
wait()和sleep()区别
sleep()
方法:一定要指定休眠的时间,到点自然醒;- 如果使用时出现锁,休眠就会释放线程的CPU的执行权,但是不会释放锁对象
- 如果没有锁,会释放CPU的执行权
- 定义在Thread类里的静态方法
wait()
方法:可以指定等待时间,也可以不指定- 指定时间:到点自然醒
- 没有指定时间:必须需要
notify()
、notiftAll()
手动唤醒 - 如果使用时出现锁,等待时即会释放CPU执行权,也会释放锁对象
- 不使用锁,会释放CPU执行权
- 定义在
Object
类里的方法