一、多线程
1.定义
进程
进程是程序执行的一条路径
一个系统级的应用执行就是一个进程
线程
线程在进程的内部, 一个进程中可以有多个线程
多个线程并发执行可以提高程序的效率, 可以同时完成多项工作
多线程节约的是执行程序的等待时间,如果程序排列紧密, 没有等待时间, 多线程下并不能真正的提高效率
2.多线程的应用
迅雷下载多个任务
服务器同时处理多个请求
3.多线程并发和并行的区别
并行就是两个任务同时运行,就是甲任务运行的同时,乙任务也在进行(需要多核CPU)
并发是指两个任务都请求运行,而处理器只能接受一个任务, 就把这两个任务安排轮流进行, 由于时间间隔较短,使人感觉两个任务都在运行
比如我跟两个网友聊天,左手操作一个电脑跟甲聊,同时右手用另一台电脑跟乙聊天,这就叫并行
如果用一台电脑我先给甲发个消息,然后立刻再给乙发消息,然后再跟甲聊,再跟乙聊这就叫并发
4.Java虚拟机运行流程
java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法
JVM启动后至少会创建垃圾回收线程和主线程
5.思考
四核CPU和双核四线程的CPU哪个效率更高?
四核
二. JAVA中多线程的实现方式一
1.步骤
继承Thread方式
定义类继承Thread
重写run方法
把新线程要做的事写在run方法中
创建线程对象
调用start()方法开启新线程, 内部会自动执行run方法
2.演示
public class MyThread extends Thread{
@Override-
public void run() {
while(true){
System.out.println("我是子线程");
}
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
3.原理解析
创建多线程肯定要跟系统平台的底层打交道, 我们程序猿根本就不知道如何去做, 所有,我们仅仅是提供运行代码,至于如何创建多线程全靠java来实现
继承Thread的形式,每个Thread的子类对象只能创建一个线程
4.测试题
同时启动多个线程打印不同的语句
三. JAVA中多线程的实现方式二
1.步骤
定义类实现Runnable接口
实现run方法
把新线程要做的事写在run方法中
创建自定义的Runnable的子类对象
创建Thread对象, 传入Runnable
调用start()开启新线程, 内部会自动调用Runnable的run()方法
2.演示
public class MyRunnable implements Runnable{
@Override
public void run() {
while(true){
System.out.println("我是子线程");
}
}
}
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
Thread thread2 = new Thread(myRunnable);
thread.start();
thread2.start();
}
3.原理
Thread类中定义了一个Runnable类型的成员变量target用来接收Runnable的子类对象
当调用Thread对象的start()方法的时候, 方法内首先会判断target是否为null. 如果不为null就调用Runnable子类对象的run方法
多个Thread对象可以共享一个Runnable子类对象
4.测试题
在MyThread和MyRunnable中定义一个int型成员变量num, 用这个变量作为循环的控制条件
自定义打印语句, 在主线程中使用这两种方式分别创建2个线程, 并开启多线程
自定义变量num的值, 使两种创建多线程的方式分别打印10条语句
四. 多线程两种方式的区别
1.查看源码的区别:
继承Thread : 由于子类重写了Thread类的run(), 当调用start()时, 直接找子类的run()方法
实现Runnable : 构造函数中传入了Runnable的引用, 成员变量记住了它, start()调用run()方法时内部判断成员变量Runnable的引用是否为空, 不为空编译时看的是Runnable的run(),运行时执行的是子类的run()方法
2.继承Thread
好处是:可以直接使用Thread类中的方法,代码简单
弊端是:如果已经有了父类,就不能用这种方法
3.实现Runnable接口
好处是:即使自己定义的线程类有了父类也没关系,因为有了父类也可以实现接口,而且接口是可以多实现的, 多个线程可以非常方便的使用同一个对象的成员变量
弊端是:不能直接使用Thread中的方法,需要先获取到线程对象后,才能得到Thread的方法,代码复杂
4.思考
当我们调用start()方法之后, 是否就意味着线程立即运行呢 ?
CPU的执行权(时间片): 如果当前线程被cpu 执行的时候, 我们称当前线程获取了cpu的执行权
调用start方法之后,意味着当前线程准备好被执行了
五. 匿名内部类实现多线程的两种方式
1.继承Thread类
public static void main(String[]args) {
new Thread(){
public void run(){
while(true){
System.out.println("我是子线程");
}
}
}.start();
}
2.实现Runnable接口
public static void main(String[] args) {
//创建Thread对象,提供Runnable匿名内部类对象
new Thread(new Runnable(){
public void run(){
while(true){
System.out.println("我是子线程");
}
}
}).start();
}
六. 多线程中获取名字和设置名字
1.获取名字
通过getName()方法获取线程对象的名字
此方法只适用于Thread的形式
Runnable的形式必须通过获取线程对象来获取名字
2.演示
public class MyThread extends Thread{
@Override
public void run() {
System.out.println(this.getName());
}
}
3.设置名
通过构造方法传入String类型的名字
public static void main(String[]args) {
new Thread("线程一"){
public void run(){
System.out.println(this.getName());
}
}.start();
}
通过setName方法可以设置线程对象的名字
public static void main(String[]args) {
Thread thread1 = new Thread("线程一"){
public void run(){
System.out.println(this.getName());
}
};
thread1.setName("线程一");
thread1.start();
}
七. 获取当前线程的对象
1.定义
我们可以在多线程运行代码中获取代表当前执行线程的对象,并对其进行操作
2.演示
通过获取对象的形式在Runnable运行代码中查看当前线程的名称
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
//获取线程的名称
System.out.println(Thread.currentThread().getName());
}
}).start();
}
通过获取线程对象的形式设置线程的名称
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
Thread.currentThread().setName("线程二");//设置线程的名称
System.out.println(Thread.currentThread().getName());
}
}).start();
}
通过Thread的构造方法设置线程的名称
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
//通过构造方法设置线程的名称
},"线程二").start();
}
八. 休眠线程
1.定义
在线程的执行代码中调用Thread.sleep()方法,就可以将线程休眠若干毫秒
休眠结束的线程进入可运行状态而不是直接运行
2.演示
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
long time1 = System.currentTimeMillis();
System.out.println(time1);
try{
Thread.sleep(10000);//设置线程等待10000毫秒
}catch(InterruptedException e) {
e.printStackTrace();
}
long time2 = System.currentTimeMillis();
System.out.println(time2);
}
//通过构造方法设置线程的名称
}).start();
}
3.测试题(龟兔赛跑)
需求: 乌龟和兔子赛跑总赛程100m, 兔子的速度是10m/s, 乌龟的速度是5m/s.乌龟和兔子都是每跑完10米输出一次结果, 当兔子跑到70米的时候休息2s ,编程模拟比赛过程
九. 守护线程
1.定义
围绕着其他非守护线程运行, 该线程不会单独运行,当其他非守护线程都执行结束后,自动退出
调用Thread的setDaemon()方法设置一个线程为守护线程
2.应用场景
使用飞秋聊天时, 我们打开了多个聊天窗口, 当我们关闭主程序时,其他所有的聊天窗口都会随之关闭
3.演示
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for(inti=0;i<5;i++) {
System.out.println("线程一运行中");
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for(inti=0;i<50;i++) {
System.out.println("守护线程运行..........");
}
}
});
t2.setDaemon(true);
t1.start();
t2.start();
}
十. 加入线程
1.定义
当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续
2.常用方法
join() 优先执行指定线程
join(毫秒) 优先执行指定线程若干毫秒
3.演示
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for(inti=0;i<50;i++) {
System.out.println("线程一运行中");
try{
Thread.sleep(10);
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for(inti=0;i<50;i++) {
System.out.println("线程二运行中..........");
try{
t1.join();//让线程一先执行
t1.join(100);//让线程一先执行100毫秒
}catch(InterruptedException e) {
e.printStackTrace();
}
try{
Thread.sleep(10);
}catch(InterruptedExceptione) {
e.printStackTrace();
}
}
}
});
//只有调用了加入线程的线程才会停止运行,其他线程不受影响
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
for(inti=0;i<50;i++) {
System.out.println("线程三运行中..........");
try{
Thread.sleep(10);
}catch(InterruptedExceptione) {
e.printStackTrace();
}
}
}
});
t1.start();
t2.start();
t3.start();
}
4.注意事项
只有调用其他线程加入方法的线程才会停止运行,其他线程不受影响
十一. 礼让线程(了解)
1.定义
让出当前线程的执行权
基本看不到效果
2.演示
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for(inti=0;i<50;i++) {
System.out.println("线程一运行中");
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for(inti=0;i<50;i++) {
System.out.println("线程二运行中..........");
//让出当前线程的执行权
Thread.yield();
}
}
});
t1.start();
t2.start();
}
十二. 设置线程的优先级
1.定义
每个线程都有优先级 默认是5 , 范围是1-10 ,1表示优先级最低
优先级高的线程在争夺cpu的执行权上有一定的优势,但不是绝对的
2.演示
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for(inti=0;i<50;i++) {
System.out.println("线程一运行中");
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for(inti=0;i<50;i++) {
System.out.println("线程二运行中..........");
}
}
});
t1.setPriority(1);
t2.setPriority(10);//线程二的优先级高
t1.start();
t2.start();
}
十三. 多线程安全问题
1.定义
多个线程操作同一个数据时, 因为线程执行的随机性, 就有可能出现线程安全问题
使用同步可以解决这个问题, 把操作数据的代码进行同步,同一时间只能有一个线程只操作数据就可以保证数据的安全性
2.线程安全问题演示
public static void main(String[] args) {
Thread t1 = new Thread(){
public void run(){
while(true){
if(num>0) {
System.out.println(getName()+":"+num--);
}else{
break;
}
}
}
};
Thread t2 = new Thread(){
public void run(){
while(true){
if(num>0) {
System.out.println(getName()+":"+num--);
}else{
break;
}
}
}
};
t1.start();
t2.start();
}
3.解决安全问题
使用sychronized关键字锁定部分代码
public static void main(String[] args) {
Thread t1 = new Thread(){
public void run(){
while(true){
//加上锁
synchronized(Class.class) {
if(num>0) {
System.out.println(getName()+":"+num--);
}else{
break;
}
}
}
}
};
Thread t2 = new Thread(){
public void run(){
while(true){
//加上统一把锁
synchronized(Class.class) {
if(num>0) {
System.out.println(getName()+":"+num--);
}else{
break;
}
}
}
}
};
t1.start();
t2.start();
}
4.注意事项
使用同一把锁的代码才能实现同步
没有获取到锁的线程即使得到了cpu的执行权,也不能运行
尽量减少锁的范围,避免效率低下
锁可以加在任意类的代码中或方法上
5.测试题
火车站总共有100张票, 四个窗口同时卖票, 当票卖光了之后,提示"没票了...",编程模拟场景
十四. 死锁
1.定义
使用同步的多个线程同时持有对方运行时所需要的资源
多线程同步时, 多个同步代码块嵌套,很容易就会出现死锁
锁的嵌套越多,越容易造成死锁的情况
2.演示
线程一需要先获取左边的筷子然后获取右边的筷子才能吃饭
线程二需要先获取右边的筷子然后获取左边的筷子才能吃饭
当线程一持有左边的筷子,线程二持有右边的筷子时,相互等待对方释放锁,但又同时持有对方释放锁的条件,互不相让,就会造成无限等待
private static String s1 = "筷子左";
private static String s2 = "筷子右";
public static void main(String[] args) {
new Thread() {
public void run() {
while(true) {
synchronized(s1) {
System.out.println(getName()+"...拿到"+s1+"等待"+s2);
synchronized(s2) {
System.out.println(getName()+"...拿到"+s2+"开吃");
}
}
}
}
}.start();
new Thread() {
public void run() {
while(true) {
synchronized(s2) {
System.out.println(getName()+"...拿到"+s2+"等待"+s1);
synchronized(s1) {
System.out.println(getName()+"...拿到"+s1+"开吃");
}
}
}
}
}.start();
}
总结:
1.几个基本概念
进程 线程 并行 并发
2.多线程
目的 : 认识多线程, 让多个程序可以同时执行
3.多线程的实现方式
Thread Runnable
4.线程的状态(重点)
新建 --> 就绪 --> 运行 --> 休眠 --> 消亡
线程状态之间的转换
5.几个小方法
休眠 守护 加入 礼让 优先级
线程有两种 , 非守护线程和守护线程
守护线程的特性 : 在所有非守护线程结束之后, 守护线程也会理解结束
6.线程安全问题
产生的原因 : 多个线程共享资源
解决办法: 1. 不要使用共享资源 2. 加锁
加锁的缺点 : 降低效率 尽量不要给会造成等待的代码加锁
7.死锁
使用同步的多个线程同时持有对方运行时所需要的资源
避免办法: 尽量不要使用所的嵌套, 嵌套的越多, 死锁的概率越大