进程线程
程序
- 就是为了完成特定的任务,用某种语言编写的一组指令的集合。简单的说就是我们写的代码
进程
- 进程是指运行中的程序,比如
QQ
,就启动了一个进程,操作系统就会为该进程分配内存空间。当我们使用其他程序的时候,就又启动了一个进程,操作系统就会为其他进程分配新的内存空间
- 进程是程序的一次执行过程,或者是正在运行的一个程序。
- 是一个动态的过程,有他自身的产生、存在和消亡的过程
下面就是几个进程
线程
- 线程是由进程创建的,是进程的一个实体
- 一个进程可以拥有多个线程,如下图【同一个应用迅雷 下载多个内容,就产生多个线程】
多线程:
同一时刻,可以执行多个进程,比如上面的迅雷同时下载多个文件
单线程:
同一时刻,只允许执行一个线程
并发 :
同一时刻 , 多个任务交替执行, 造成一种‘貌似同时’ 的错觉, 简单的说,单核cpu
实现的多任务就是并发
看上去他们同时进行,但是在任意时间段内,他们都是相互交错执行的
比如 : ‘人的大脑一边说话,一边写字 ’
并行 :
同一时刻, 多个任务同时执行。多核cpu
可以实现并行
线程使用的两种方式
- 当一个类继承Thread类, 那么该类就可以当成一个线程
线程常用的方法
两种方式的区别
- 从java的设计来看, 通过继承Thread或者实现Runnable接口来创建线程本质上是没有区别的,他们都是通过start方法来调用start0 ,然后实现了多线程
- 从jdk帮助文档来看, Thread类本身就实现了Runnable接口
- 实现Runnable接口方式更加适合多线程共享一个资源的情况,并且避免了单继承的限制
一、 继承Thread创建线程
- 案例一: 开启一个线程,该线程每隔1秒,在控制台输出一段话
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public class Thread01 { public static void main(String[] args) { Cat cat = new Cat(); cat.start();
} } class Cat extends Thread{
int times = 0; @Override public void run() {
while(true){ System.out.println("执行一次" + "次数" +(++times)); try { Thread.sleep(1000);
} catch (InterruptedException e) { e.printStackTrace(); } } } }
|
结果就是每隔一秒输出一次
二、 实现Runnable
java
是单继承的,在某些情况下一个类可能已经继承了某个父类,这是再用Thread类方法来创建线程显然是不可能了
java
设计者们就想出了另一个创建线程的方法,就是通过实现Runnable接口来创建线程
实现案例:
写一个程序,该程序可以每隔1秒,在控制台输出一段话
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public class Thread02 { public static void main(String[] args) { Dog dog = new Dog(); Thread thread = new Thread(dog); thread.start(); }
} class Dog implements Runnable{
int times = 0; @Override public void run() { System.out.println("执行一次" + "次数" +(++times)+ "线程名:" + Thread.currentThread().getName()); try { Thread.sleep(3000);
} catch (InterruptedException e) { e.printStackTrace(); } } }
|
这里使用的是一个设计模式【代理模式】
多线程机制
当我们执行上述程序是,它的流程是这样的
主线程的执行
1 2 3 4 5 6 7 8 9 10 11
| public static void main(String[] args) throws Exception{ int count = 0; Cat cat = new Cat(); cat.start(); while (true){ System.out.println("主线程 执行次数: " + (++count) + "线程名:" + Thread.currentThread().getName()); Thread.sleep(1000); } }
|
当main方法执行一个线程 之后 ,主线程不会阻塞 ,会继续执行
最后用检测工具发现,虽然主线程的执行完一定次数之后,结束了,但是它的子线程Thread-0 没有执行完 ,那么程序就不会结束
为什么这里要调cat.start()
方法?
答 :因为run方法就是一个普通的方法,并无法启动一个真正的线程就会产生阻塞。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public synchronized void start() { if (threadStatus != 0) throw new IllegalThreadStateException(); group.add(this); boolean started = false; try { start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { } } }
private native void start0();
|
换言之, 真正实现多线程的效果是start0();
方法
多个子线程案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| public class Thread02 { public static void main(String[] args) { Dog dog = new Dog(); Catt catt = new Catt(); Thread thread1 = new Thread(dog); Thread thread2 = new Thread(catt); thread1.start(); thread2.start(); }
} class Dog implements Runnable{
int times = 0; @Override public void run() { while (true){ System.out.println("执行一次" + "次数" +(++times)+ " 线程1名:" + Thread.currentThread().getName()); try { Thread.sleep(1000);
} catch (InterruptedException e) { e.printStackTrace(); } } } } class Catt implements Runnable{
int times = 0; @Override public void run() { while (true){ System.out.println("执行一次" + "次数" +(++times)+ " 线程2 名:" + Thread.currentThread().getName()); try { Thread.sleep(1000);
} catch (InterruptedException e) { e.printStackTrace(); } } } }
|
运行结果 ,他们交叉执行
多线程售票问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| package ticket;
public class SellTicket {
public static void main(String[] args) { SellTicket01 ticket01 = new SellTicket01(); SellTicket01 ticket02 = new SellTicket01(); SellTicket01 ticket03 = new SellTicket01();
ticket01.start(); ticket02.start(); ticket03.start();
}
} class SellTicket01 extends Thread{ static int ticketNum = 100;
@Override public void run() { while (true){ if (ticketNum <= 0 ){ System.out.println("售票结束"); break; } try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("窗口 :" + Thread.currentThread().getName() + "售出一张票" + " 剩余票数为: "+ (--ticketNum)); } } }
|
运行结果 :
出现超卖问题
原因:
也就是在票数为 1 的时候, 他们三者同时进行售卖 ,导致三个线程同时进行,然后就会超卖两张
解决方案:
通知线程退出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| public class Thread01 { public static void main(String[] args) throws Exception{ int count = 0; Cat cat = new Cat(); cat.start(); Thread.sleep(10000); cat.loop = false; } } class Cat extends Thread{
int times = 0;
boolean loop = true; @Override public void run() {
while(loop){ System.out.println("执行一次" + "次数" +(++times)+ "线程名:" + Thread.currentThread().getName()); try { Thread.sleep(1000);
} catch (InterruptedException e) { e.printStackTrace(); } } } }
|
在main方法中控制变量,让另一个线程退出
运行结果:
线程中断
就是让正在休眠的线程停止休眠, 继续工作的意思
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| package Thread;
public class Thread01 { public static void main(String[] args) throws Exception{ int count = 0; Cat cat = new Cat(); cat.start(); Thread.sleep(1000);
System.out.println("线程停止休眠 ,继续执行" ); cat.interrupt();
} } class Cat extends Thread{
int times = 0;
boolean loop = true; @Override public void run() {
while(loop){ System.out.println("执行一次" + "次数" +(++times)+ "线程名:" + Thread.currentThread().getName()); try { System.out.println("休眠中--"); Thread.sleep(10000);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "被interrupt了"); } } } }
|
线程插队
- yield :将线程让出来,让其他线程执行
- join : 线程的插队。 插队线程一旦插队成功 ,则肯定先执行完插入的线程所有的任务
用户、守护 线程
- 用户线程: 也叫工作线程, 当线程的任务执行完成或者通知方式结束
- 守护线程: 一般是为工作线程服务的,当所有的用户线程结束, 守护线程自动结束
- 常见的守护线程 : 垃圾回收机制
如何将一个线程设置为守护线程?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| public class Thread02 { public static void main(String[] args) throws Exception{ Dog dog = new Dog(); Catt catt = new Catt(); Thread thread1 = new Thread(dog); thread1.setDaemon(true); thread1.start(); catt.setDaemon(true); catt.start(); for (int i = 0; i < 10; i++) { System.out.println("test... "); Thread.sleep(1000); } }
} class Dog implements Runnable{
int times = 0; @Override public void run() { while (true){ System.out.println("执行一次" + "次数" +(++times)+ " 线程1名:" + Thread.currentThread().getName()); try { Thread.sleep(1000);
} catch (InterruptedException e) { e.printStackTrace(); } } } } class Catt extends Thread{
int times = 0; @Override public void run() { while (true){ System.out.println("执行一次" + "次数" +(++times)+ " 线程2 名:" + Thread.currentThread().getName()); try { Thread.sleep(1000);
} catch (InterruptedException e) { e.printStackTrace(); } } } }
|
通过//Runnable方式实现 设置为守护线程 ,然后在启动 thread1.setDaemon(true);
的方式实现设置守护线程
注意顺序 ,先设置 再执行
线程的生命周期
线程的7大状态
jdk官方文档中可以看出有六个
但是从我们的线程生命周期转换图可以看出 我们将Runnable(可运行状态)细化为Ready(就绪) 和Running(真正运行)两个状态
线程生命周期转换图
案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| public class Thread02 { public static void main(String[] args) throws Exception{ Catt catt = new Catt(); System.out.println(catt.getName() + " 状态 " + catt.getState()); catt.start(); while(Thread.State.TERMINATED != catt.getState()){ System.out.println(catt.getName() + " 状态 " + catt.getState()); Thread.sleep(500); } System.out.println(catt.getName() + " 状态 " + catt.getState()); }
}
class Catt extends Thread{ int times = 0; @Override public void run() { while (times != 5){ System.out.println("执行一次" + "次数" +(++times)+ " 线程2 名:" + Thread.currentThread().getName()); try { Thread.sleep(1000);
} catch (InterruptedException e) { e.printStackTrace(); } } } }
|
运行结果:
线程同步机制—-Synchronized
- 在多线程编程中,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性
- 也可以这样理解:线程同步,即当有一个线程在对内存进行操作试试,其他的线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作
Synchronized
同步具体方法
1 2 3
| public synchronized void m(String name){ }
|
具体理解 ,不管有多少线程来,在某一时刻,只能有一个线程访问
分析同步原理
假设 t1 抢到锁之后就开始执行代码 ,当执行完之后就会把锁放回去 ,然后t1、t2、t3 三个继续抢这个锁
互斥锁
- java语言中引入了互斥锁的概念 ,来保证共享数据操作的完整性
- 每个对象都对应一个可以称为“互斥锁”的标记, 这个标记用来保证在任意时刻,只能有一个线程访问
- 同步的局限性: 导致程序的执行效率降低
- 同步方法(静态的) 的锁为当前类本身
- 同步方法(非静态的) 的锁可以是this ,也可以是其他对象 (必须是同一对象,可以是不同引用)
**注意事项: **
线程死锁
- 多个线程都占用了对方的所资源, 但是不肯想让 ,导致死锁, 在编程时一定要避免
上述情况导致o1、o2都无法释放,导致死锁
必须避免这种两个锁互相锁
释放锁
- 当前线程的同步方法、同步代码块执行结束
- 当前线程在同步代码块、同步方法中遇到break、return
- 当前线程在同步代码块、同步方法中出现了未处理的Error或者Exception,导致程序异常结束
- 当前线程在同步代码块、同步方法中执行了wait()方法,当前线程暂停,并且释放锁
下面的操作不会释放锁