1.线程相关概念
- 什么是进程
进程是指运行中的程序,比如使用QQ的时候,就启动了一个进程,操作系统会为该进程分配内存空间,使用别的程序,又会启动一个进程,操作系统会为该进程分配新的内存空间....
进程是程序一次执行过程,或是正在运行的一个程序,又比如Java中的run“main”
线程是动态过程:有它自身的产生、存在和消亡的过程
- 什么是线程
线程由进程创建的,是进程的一个实体,一个进程可以拥有多个线程
- 其他
单线程:同一个时刻,只允许执行一个线程
多线程:同一个时刻,可以执行多个线程,比如一个qq进程,可以同时打开多个聊天窗口
并发:同一个时刻,多个任务交替执行,造成一种“貌似同时”的错觉,单核cpu实现的多任务就是并发。比如一边打电话一边开车就是并发,一个大脑交替执行这两个任务
并行:同一个时刻,多个任务同时执行。多核cpu可以实现并行。并发和并行可以同时存在
2.线程基本使用
创建线程的两种方式
继承关系示意图
1.继承Thread类,重写run方法
package com.xiaol.threaduse;
public class Thread01 {
public static void main(String[] args) {
Cat cat = new Cat();
cat.start();
}
}
//当一个类继承了Thread类,该类就可以当成线程使用
class Cat extends Thread{
int time = 0;
@Override
public void run() {
super.run();
while (true){
System.out.println("喵喵" + ++time);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (time==8){
break;
}
}
}
}
/**
关于运行的线程的理解:
运行代码时,先启用了进程(Application),
然后创建了main线程(执行完main方法的内容线程就销毁了)
但是在main中执行的Thread-0线程还没执行完(此时,进程和Thread-0都还在)
当Thread-0线程执行完,Thread-0销毁了,进程随之销毁
*/
2.实现Runnable接口,重写run方法
package com.xiaol.threaduse;
public class Thread02 {
public static void main(String[] args) {
new Thread(new Dog()).start();
}
}
class Dog implements Runnable{
int count = 0;
@Override
public void run() {
while (count++ != 5){
System.out.println("修狗" + count);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
关于start()方法
start()方法,实际是调用的start0()【源码:private native void start0();,native 原生的是跟操作系统相关的】方法,调用start0()方法后,该线程并不一定会立马执行,只是将线程变成了可运行状态。具体什么时候执行,取决于CPU,由CPU统一调度
多线程案例
package com.xiaol.threaduse;
public class Thread03 {
public static void main(String[] args) {
T1 t1 = new T1();
T2 t2 = new T2();
new Thread(t1).start();
new Thread(t2).start();
}
}
class T1 implements Runnable{
int count =0;
@Override
public void run() {
while (true){
System.out.println("hi" + ++count);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 10){
break;
}
}
}
}
class T2 implements Runnable{
int count =0;
@Override
public void run() {
while (true){
System.out.println("kkkk" + ++count);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 5 ){
break;
}
}
}
}
继承 Thread vs 实现 Runnable 的区别
- Thread类本身就实现类Runnable接口,所以从java的设计来看,通过继承Thread货值实现Runnable接口来创建线程本质上没有区别
- 实现Runnable接口方式更适合多个线程共享一个资源的情况,并避免了单继承的限制【比如Cat继承了Animal类,还想实现多线程,只能implements Runnable】,建议使用Runnable
3.线程终止
当线程完成任务后,会自动退出
还可以通过使用变量来控制run方法推退出的方式停止线程,即【通知方式】(常用)
由于需要调用外网数据,需要在预发环境搭建代理,接下来需要考虑代理的通用性,在其他需要外网数据配合的需求中可以直接使用。
4.线程常用方法
- yield:线程的礼让。让出cpu,让其他线程执行,但礼让的时间不确定,所有也不一定礼让成功,因为有时候CPU觉得它忙的过来
- join:线程的插队。插队的线程一旦插队成功,则肯定先执行插入的线程的所有任务
package com.xiaol.threaduse;
public class ThreadExercise {
public static void main(String[] args) throws InterruptedException {
int i = 0;
Thread thread = new Thread(new T3());
while (true){
System.out.println("hi" + ++i);
if (i == 5){
thread.start();
thread.join();
}
if (i == 10){
System.exit(3);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class T3 implements Runnable{
int count;
@Override
public void run() {
while (true){
System.out.println("hello" + ++count);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 10){
break;
}
}
}
}
其他常用方法
- setName //设置线程名称
- getName //返回线程的名称
- start //使该线程开始执行;java虚拟机层调用该线程的start0方法
- run //调用线程对象的run方法 【直接调用run方法不会启用线程的!必须调用start】
- setPriority //更改线程的优先级
- getPriority //获取线程的优先级
- sleep //在指定毫秒数内让当前执行的线程休眠(暂停执行)
- interrup //中断线程
注意事项和细节
- start底层会创建新的线程,调用run,run就是一个简单的方法调用,不会启动新的线程
- interrupt,中断线程,但并没有结束线程,一般用于中断正在休眠的线程
- sleep是线程的静态方法,事当前线程休眠
用户线程和守护线程
用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
守护线程:一般是为工作线程服务的,当所用用户线程结束,守护线程自动结束 t.setDaemo(true)
常见守护线程:垃圾回收机制
5.线程的生命周期
当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态,在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)五种状态
线程状态转换图:
6.线程的同步
在多线程编程中,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以确保数据的完整性
具体同步方法-synchronized
- 同步代码块
synchronized(对象){ //得到对象的锁才能操作同步代码块
//需要被同步的代码【理论上范围越小越好,效率越高】
}
- synchronized还可以放在方法声明中,表示整个方法为同步方法
public synchronized void sell(){//同步方法,在同一时刻只能有一个线程来执行sell方法
需要被同步的代码
}
7.线程的锁
1.互斥锁
模拟多个窗口售票问题
package com.xiaol.threaduse.ticket;
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();
}
}
//使用synchronized实现线程同步
class SellTicket03 implements Runnable{
private int ticketNum = 100;//让多个线程共享num
private boolean loop = true;
public synchronized void sell(){//同步方法,在同一时刻只能有一个线程来执行sell方法
if (ticketNum <= 0){
System.out.println("售票结束");
loop = false;
return;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口:" + Thread.currentThread().getName() + " 售出一张票,剩余票数:"
+ --ticketNum );
}
@Override
public void run() {
while (loop){
sell();
}
}
}
注意事项和细节
- 同步方法是非静态的,默认锁对象是this
- 如果方法是静态的,默认锁对象是 当前类.class
- 实现互锁的步骤
-
- 分析需要上锁的代码
- 选择同步代码块【推荐】或同步方法
- 要求多个线程的锁对象为同一个
2.线程的死锁
多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程中一定要避免死锁的发生
3.释放锁
释放锁的几种情形:
- 当线程的同步方法、同步代码块执行结束
- 当线程在同步代码块、同步方法遇到break/return
- 当线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
- 当线程在同步代码块、同步方法中执行了线程的wait()方法,当前线程暂停,并释放锁
不会释放锁的几种情形:
- 调用Thread.sleep()、Thread.yield()方法暂停当前线程执行,不会释放锁
- 其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁