多线程
基础概念
- 程序:为完成特定任务,用某种语言编写的一组指令的集合。(代码)
- 进程:运行中的程序。(Run代码)
① 启动进程需要CPU和操作系统分配的内存空间
② 进程是一个动态的过程,有自身的产生、存在和消亡的过程 - 线程:线程由进程创建,是进程的一个实体。(如百度某盘可以同时下载多个文件,下载一个文件就是一个线程)
- 单线程:同一时刻,只允许执行一个线程。
- 多线程:同一时刻,可以执行多个线程。
- 并发:同一时刻,多个任务交替执行,造成一种“貌似同时”的错觉。即单核CPU实现的多任务并发
- 并行:同一时刻,多个任务同时执行。即多核CPU可以实现并行。
一个小程序:计算自己电脑CPU核数
public class CpuNums {
public static void main(String[] args) {
Runtime runtime= Runtime.getRuntime();
int cpuNums=runtime.availableProcessors();
System.out.println("我的CPU是"+cpuNums+"核");
}
}
线程的基本使用
- 创建线程有两种方式:
- 继承
Thread
类,重写run()
方法 - 实现
Runnable
接口,重写run()
方法
Thread
类与Runnable
接口的关系图:
- 继承
方式一:继承Thread
类,重写run()
方法例子
public class Thread01 {
public static void main(String[] args) {
Computer computer = new Computer();
computer.start();//启动线程-> 最终会执行cat的run()方法
int times=0;
while (true){
System.out.println("main working..."+Thread.currentThread().getName());//获得主线程的状态
try {
Thread.sleep(1000);//休息1000毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
times++;
if(times==20)
break;
}
}
}
class Computer extends Thread{
@Override
public void run() {
int times=0;
while(true){
System.out.println("Computer working..."+Thread.currentThread().getName());//获得当前线程的状态
try {
Thread.sleep(1000);//休息1000毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
times++;
//执行60次后退出
if(times==60){
break;
}
}
}
}
如图当 main 线程启动一个子线程 Thread-0, 主线程不会阻塞, 会继续执行
- Q:若将start()换成run()有何不同
如图所示,换成run()后,run()相当于一个普通的方法,先执行完run()方法后再执行main()中的内容,而进程只有一个main
方式二:实现Runnable
接口,重写run()
方法
public class Thread02 {
public static void main(String[] args) {
Computer computer = new Computer();
//computer.start();//接口Runnable中没有start()方法,因此这个创建线程的方法失效
//创建Thread对象将computer放入,用thread调用start()方法
Thread thread = new Thread(computer);
thread.start();
}
}
class Computer implements Runnable{
int times=0;
@Override
public void run() {
while(true){
System.out.println("Computer"+times+Thread.currentThread().getName()+ "is working...");
times++;
try {
Thread.sleep(1000);//休息1000毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
if(times==5)//执行5次后停止
break;
}
}
}
- Q:computer.start()失效的底层原因?
没有这个函数
代理模式:模拟实现Runnable接口
public class SimulationThead {
public static void main(String[] args) {
Computer02 computer02 = new Computer02();//实现了 Runnable
ProxyThread threadProxy = new ProxyThread(computer02);
threadProxy.start();
}
}
class Computer02 implements Runnable{
@Override
public void run() {
System.out.println("Computer02 is working...");
}
}
class ProxyThread implements Runnable{
private Runnable target=null;
@Override
public void run() {
if(target!=null)
target.run();//动态绑定
}
public ProxyThread(Runnable target) {
this.target=target;
}
public void start(){
start01();
}
public void start01(){
run();
}
}
模拟一个售票机
一个售票机有三个窗口同时售卖票
public class SellTicket {
public static void main(String[] args) {
// //Thread方式
// SellTicket01 sellTicket0101 = new SellTicket01();
// SellTicket01 sellTicket0102 = new SellTicket01();
// SellTicket01 sellTicket0103 = new SellTicket01();
// sellTicket0101.start();
// sellTicket0102.start();
// sellTicket0103.start();
//Runnable方式
SellTicket02 sellTicket02 = new SellTicket02();
new Thread(sellTicket02).start();
new Thread(sellTicket02).start();
new Thread(sellTicket02).start();
}
}
class SellTicket01 extends Thread{
private static int ticket=1000;
@Override
public void run() {
while(true){
if(ticket<=0){
System.out.println("售完");
break;
}
System.out.println(Thread.currentThread().getName()+"运行ing... 此时还有"+ticket+"张票,花费一张后还有"+--ticket+"张");
try {
Thread.sleep(1);//休息1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class SellTicket02 implements Runnable{
private static int ticket=1000;
@Override
public void run() {
while(true){
if(ticket<=0){
System.out.println("售完");
break;
}
System.out.println(Thread.currentThread().getName()+"运行ing... 此时还有"+ticket+"张票,花费一张后还有"+--ticket+"张");
try {
Thread.sleep(1);//休息1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果:
方法一的结果:
方法二的结果:
存在的问题:不管是Thread还是Runnable创建3个进程,这3个进程同时进入run()
函数,会出现如上图的问题。
线程方法
线程终止
- 线程终止有两种方式:
- 自动退出:当线程完成执行完毕后,线程自动退出
- 通知退出:使用变量来控制
run()
方法停止线程,即通知方式//方法二:使用变量loop来控制run()方法停止线程 public class ThreadExit { public static void main(String[] args) throws InterruptedException { Thread01 thread01 = new Thread01();//创建thread01线程 thread01.start();//thread01启动 //主线程休息2s后结束thread01 Thread.sleep(2000); thread01.setLoop(false);//通过设置loop变量来停止thread01的运行 System.out.println(Thread.currentThread().getName()+"运行ing... 结束thread01线程"); } } class Thread01 extends Thread{ private int counts=0; private boolean loop=true; @Override public void run() { while (loop){ System.out.println(Thread.currentThread().getName()+"运行ing... 运行了"+(++counts)); try { Thread.sleep(100);//休息0.1s } catch (InterruptedException e) { e.printStackTrace(); } } } public void setLoop(boolean loop) { this.loop = loop; } }
线程中断
方法名 | 功能 | 说明 |
---|---|---|
interrupt() | 中断线程 | 中断的一般是休眠中的线程 |
线程插队
方法名 | 功能 | 说明 |
---|---|---|
yield() | 线程的礼让 | 礼让时间不确定,不一定礼让成果 |
join() | 线程插队 | 若插队成功,先将插队的内容执行完毕 |
/* eg.
1.主线程每隔1s输出hi,输出5次
2.当输出2次后,启动子线程,子线程每隔1s输出hello,输出5次后退出子线程
3.主线程继续输出hi直到结束
*/
public class ThreadMethodExercise {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new T());
System.out.println("主线程启动");
for(int i=1;i<=5;i++){
System.out.println("hi");
Thread.sleep(1000);
if(i==2){
t.start();//启动子进程
t.join();//子进程插队
}
}
System.out.println("主线程结束");
}
}
class T implements Runnable{
private int count=0;
@Override
public void run() {
System.out.println("子线程启动");
while(true){
System.out.println("hello");
count++;
try {
Thread.sleep(1000);//休息1s
} catch (InterruptedException e) {
e.printStackTrace();
}
if(count==5){
System.out.println("子线程结束");
break;
}
}
}
}
运行结果:
守护线程
- 用户线程(工作线程):线程的任务执行完毕/通知方式结束
- 守护线程:当所有用户线程结束,守护线程自动结束,如:垃圾回收机制
工作线程设置为守护线程
/*
主线程启动子线程,主线程输出完5次hello后,子线程随之停止工作
*/
public class ThreadMethod {
public static void main(String[] args) throws InterruptedException {
MyDaemon t = new MyDaemon();
t.setDaemon(true);//注意要先将子线程t设置为守护线程
t.start();//启动子线程t
for (int i=0;i<5;i++){
System.out.println("hello");
Thread.sleep(1000);
}
}
}
class MyDaemon extends Thread{
@Override
public void run() {
while(true){
System.out.println("hi");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果: 若不将t设置为守护线程则子线程不会停止工作
线程的生命周期
【说明】:在JDK的文档中 Thread.State
只有6中状态,但Runnable中包含两个状态Ready和Running,所以可以看成有7个状态
//eg.测试状态NEW,RUNNABLE,WAITING,TERMINATED的存在
public class ThreadState {
public static void main(String[] args) throws InterruptedException {
T t = new T();
System.out.println(t.getName()+" 状态为:"+t.getState());
t.start();
while (Thread.State.TERMINATED != t.getState()) {
System.out.println(t.getName() + " 状态 " + t.getState());
Thread.sleep(500);
}
System.out.println(t.getName() + " 状态 " + t.getState());
}
}
class T extends Thread{
@Override
public void run() {
for (int i=0;i<5;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果:
Synchronized(线程同步)
- 线程同步机制:当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对这个内存地址进行操作。
- 同步原理:方法需要线程有锁才能使用,第一个线程使用完这个方法后将锁给下一个线程,在前一个线程未使用完当前线程无法进入该方法。
- 两种同步方法:
- 同步代码块
synchronized (对象){ //得到对象的锁才能操作同步代码 //需要被同步的代码 }
- 同步方法:将关键字 synchronized 放在方法声明中
public synchronized void func(){ //需要被同步的代码 }
- 同步代码块
解决售票机存在的问题
方法一:同步方法
public class SellTicket {
public static void main(String[] args) {
SellTicket03 sellTicket03 = new SellTicket03();
new Thread(sellTicket03).start();
new Thread(sellTicket03).start();
new Thread(sellTicket03).start();
}
}
class SellTicket03 implements Runnable{
private static int ticket=1000;
private boolean loop=true;
@Override
public synchronized void run() {
while(loop){
if(ticket<=0){
System.out.println("售完");
loop=false;
return;
}
System.out.println(Thread.currentThread().getName()+"运行ing... 此时还有"+ticket+"张票,花费一张后还有"+--ticket+"张");
try {
Thread.sleep(1);//休息1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
方法二:同步代码块
public class SellTicket {
public static void main(String[] args) {
SellTicket03 sellTicket03 = new SellTicket03();
new Thread(sellTicket03).start();
new Thread(sellTicket03).start();
new Thread(sellTicket03).start();
}
}
class SellTicket03 implements Runnable{
private static int ticket=1000;
private boolean loop=true;
@Override
public void run() {
synchronized (this){
while(loop){
if(ticket<=0){
System.out.println("售完");
loop=false;
return;
}
System.out.println(Thread.currentThread().getName()+"运行ing... 此时还有"+ticket+"张票,花费一张后还有"+--ticket+"张");
try {
Thread.sleep(1);//休息1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
运行结果:
-
Q:为什么同步代码块的对象是this
(互斥锁解释) -
Q:为什么不使用继承Thread的类
(互斥锁解释)+ 观察Thread和Runnable创建对象的不同,可以发现Thread创建了三个不同的对象,所以每个对象对应着自己的this,此时同步失效。
SellTicket03 sellTicket0301 = new SellTicket03();
SellTicket03 sellTicket0302 = new SellTicket03();
SellTicket03 sellTicket0303 = new SellTicket03();
sellTicket0301.start();
sellTicket0302.start();
sellTicket0303.start();
锁
互斥锁
- 互斥锁(Mutual exclusion):防止两条线程同时对同一公共资源(比如全域變數)进行读写的机制
当某个对象用 Synchronized 修饰,表明该对象在任一时刻只能由一个线程访问
【说明】:
- 同步的局限性:导致程序执行效率变低
- 同步方法的锁:
- 非静态:this
- 静态:当前类本身
死锁
- 死锁:两个或两个以上的进程(线程)在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。
- 死锁形成的条件:
- 互斥条件:线程(进程)对于所分配到的资源具有排它性,即一个资源只能被一个线程(进程)占用,直到被该线程(进程)释放
- 请求与保持条件:一个线程(进程)因请求被占用资源而发生阻塞时,对已获得的资源保持不放。
- 不剥夺条件:线程(进程)已获得的资源在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
- 循环等待条件:当发生死锁时,所等待的线程(进程)必定会形成一个环路(类似于死循环),造成永久阻塞
//模拟死锁
public class DeadLock {
public static void main(String[] args) {
DeadLockDemo A = new DeadLockDemo(true);
A.setName("A");
DeadLockDemo B = new DeadLockDemo(false);
B.setName("B");
A.start();
B.start();
}
}
class DeadLockDemo extends Thread{
private static Object resource1 = new Object();//资源 1
private static Object resource2 = new Object();//资源 2
private boolean flag=true;
public DeadLockDemo(boolean flag) {//构造器
this.flag = flag;
}
@Override
public void run() {
if(flag){
synchronized (resource1){
System.out.println("1) 资源 1 被 线程"+Thread.currentThread().getName()+" 占用");
synchronized (resource2){
System.out.println("2) 资源 2 被 线程"+Thread.currentThread().getName()+" 占用");
}
}
}
else {
synchronized (resource2){
System.out.println("3) 资源 2 被 线程"+Thread.currentThread().getName()+" 占用");
synchronized (resource1){
System.out.println("4) 资源 1 被 线程"+Thread.currentThread().getName()+" 占用");
}
}
}
}
}
运行结果:
释放锁
- 释放锁有四种情况:
1.
【附录】线程常用方法
方法名 | 功能 | 说明 |
---|---|---|
setName() | 设置线程名字 | - |
getName() | 返回线程名字 | - |
start() | 线程开始执行 | - |
run() | 调用线程对象run方法 | - |
setPriority() | 设置线程优先级 | 最高:10,中间:5,最低:1;main函数默认5 |
getPriority() | 返回线程优先级 | - |
sleep() | 休眠(毫秒) | - |