目录
一.线程相关概念
1.程序(program)
2.进程
3.线程
4.其他相关概念
二.线程的创建
1.继承Thread
2.Runnable接口
3.多线程机制(重要)
4.start()
三.线程终止--通知
四.线程(Thread)方法
1.常用方法
2.yield和join
3.setDaemon
五.线程的生命周期
六.Synchronized
1.线程同步机制
2.实现方法
2.1 同步代码块
2.2 同步方法
3.互斥锁
4.释放锁
一.线程相关概念
1.程序(program)
为了完成特定的任务,用某种语言编写的一组指令的集合。简单点说就是我们写的代码。
2.进程
进程是程序的一次执行过程,或是正在运行的一个程序。它是一个动态过程:产生 -> 存在 -> 消亡。
3.线程
由进程创建,是进程的一个实体,一个进程里有多个线程,线程也可以有子线程。
单线程:同一个时刻,只允许执行一个线程
多线程:同一个时刻,可以执行多个线程
4.其他相关概念
并发:同一时刻,多个任务交替执行,简单来说就是单核CPU实现多个任务。这里注意,单核CPU,一次就能进行一个任务,比如我们切换应用,看起来是都两个应用都在运行,实则是CPU的切换速度很快,快速切换完成的,不是在同时进行多个任务。
并行:同一个时刻,多个任务同时执行。多核CPU可以实现并行。这个跟上面的例子相对,每一个应用都有一个CPU工作。
二.线程的创建
1.继承Thread
我们可以通过继承Thread(线程)来创建线程。
当一个类继承了 Thread 类,该类就可以当线程使用。我们会重写run()方法,写上自己的业务代码。Thread 类 实现了 Runnable 接口的run方法
比如这里有一个Cat类:
class Cat extends Thread{
public void run(){
//重写run方法,实现我们需要的业务逻辑
int time=1;
while(true){
System.out.println("time = "+time);
try {
//睡眠1s
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(time==8){
break;
}
time++;
}
}
}
现在Cat类可以当作线程使用,我们在main方法中使用了这个线程:
public static void main(String[] args) {
//创建一个cat对象
Cat cat=new Cat();
cat.start();
2.Runnable接口
我们知道,Java一个类只能继承一个父类,如果我们要继承Thread的类已经继承了一个父类,那么怎么才能实现?
使用Runnable接口,使用这个接口后,实现run方法即可(前面说了Thread类的run方法是实现Runnable中的run方法)。
这里举一个Dog的例子,假设我们的Dog已经继承了Animal类了:
class Dog extends Animal implements Runnable{
int count=0;
@Override
public void run() { //普通方法
while(true){
System.out.println("count = "+count);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
}
}
}
在main方法中的使用方法也不一样了:
Dog dog=new Dog();
//dog.start(); 这里不能调用start
Thread thread = new Thread(dog);
thread.start();
3.多线程机制(重要)
现在我们运行下面的代码:
public class Thread01 {
public static void main(String[] args) throws InterruptedException {
//创建一个cat对象
Cat cat=new Cat();
cat.start();
for (int i = 1; i <= 8; i++) {
Thread.sleep(1000);
System.out.println("i = "+i);
}
}
}
class Cat extends Thread{
public void run(){
int time=1;
while(true){
System.out.println("time = "+time);
try {
//睡眠1s
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(time==8){
break;
}
time++;
}
}
}
查看输出的内容,我们发现输出的内容是交替出现的,这里就说明了一个事实:主线程与子线程交替执行。
当一个main线程启动一个子线程Thread-0,主线程不会阻塞,会继续执行。这也就是为什么我们可以看到其交替出现的原因。
当我们执行程序的时候(开了一个进程)先会开一个main线程,随后开一个子线程Thread-0,main线程结束后,子线程还会进行进行,直到结束为止。
总而言之,一个main线程结束,不代表整个进程都会结束,当全部线程结束的时候,进程才会结束。
4.start()
有人可能会发现,如果我们调用run()方法,一样可以打印,为什么要用start()而不用run()呢?
因为我们写的run() 方法,它只是一个普通的方法。我们调用start()方法,其底层会调用start0()方法,这个方法才是关键。真正实现多线程的效果是start0()。
start0()是本地方法,是JVM调用的。
三.线程终止--通知
顾名思义,通知线程,让其关闭。当然,程序听不懂我们说的,但我们可以给它传一个值告诉它:停下吧。这里我们传入了一个布尔变量,让其控制线程的结束:
class T extends Thread{
public boolean loop=true;
public void run(){
while(loop){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("T 在运行");
}
}
public void setLoop(boolean b) {
loop=b;
}
}
四.线程(Thread)方法
1.常用方法
方法 | 说明 |
setName | 设置线程名称 |
getName | 返回线程名称 |
start | 是该线程开始执行,JVM调用该线程的start0()方法 |
run | 调用线程对象 run 方法 |
setPriority | 更改线程的优先级 |
getPriority | 获取线程的优先级 |
sleep | 在指定的毫秒内让当前正在执行的线程休眠(暂停执行) |
interrupt | 中断线程,注意不是结束 |
2.yield和join
方法 | 说明 |
yield | 线程礼让,即让出CPU,让其它线程执行,但礼让的时间不确定,所以不一定礼让成功 |
join | 线程插队,线程插队一旦插队成功,肯定会先执行完插入的线程的所有任务 |
比如一开始线程A,B都在执行,想让A先暂停,让B先执行完。如果用yield方法,不一定能让A暂停,这取决于CPU资源占用量。但如果使用join,直接插队,B先把线程全部执行完,A就在那暂停。
3.setDaemon
方法 | 说明 |
setDaemon | 设置为true,就是开启守护线程 |
用户线程(工程线程):当线程的任务全部执行完或通知方式结束
守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束。常见守护线程:垃圾回收机制。
五.线程的生命周期
线程一共有六种状态,这里特别说明一下Runnable状态。
当进入到Runnable状态时,不是马上就开始运行了,是先变成Ready,被调度器选中才执行。
整个过程看图就可以理解。
六.Synchronized
1.线程同步机制
在多线程编程中,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性。
锁是实现线程同步的一种常见手段。
2.实现方法
2.1 同步代码块
得到对象的锁,操作同步代码:
synchronized (对象){
//需要被同步的代码
}
2.2 同步方法
放在方法声明中:
public synchronized void fun(){
//需要被同步的代码
}
3.互斥锁
Java中引入了互斥锁的概念来保证共享数据操作的完整性。每个对象都有一个互斥锁的标记,这个标记用来确保在任一时刻,只有一个线程访问该对象。当一个对象用Synchronized 修饰时,表明该对象在任一时刻只能由一个线程访问。
但是同步也有局限性,那就是程序执行效率降低。比如一个本来一条路(有多个车道)可以自由通行,现在把它堵上了,安了一个卡口,一次就能通过一辆车,那么效率不就降低了。
如果同步的方法是非静态的,这时锁可以时this或者是其他对象。这里要注意,多线程操作每一个线程必须是同一个对象,如果是多个的话锁不就失效了嘛。比如说我们有一个Cat类,我们new了两个对象,一个cat1,一个cat2,这就不行。要new一个cat,然后用这个cat.start()两次,开两个线程才可以。
如果同步的方法是静态的,这时锁为当前类的本身,不能是this。
4.释放锁
我们不能只拿到了锁,用完了还要释放。下面是释放锁的几种途径:
1.当前线程的同步方法、同步代码块执行结束
2.当前线程在同步方法、同步代码块中遇到了break、return
3.当前线程在同步方法、同步代码块中出现了为处理的Error或Exception,导致异常结束
4.当前线程在同步方法、同步代码块中执行了线程对象的wait()方法,当前线程暂停,并释放锁
下面几种操作不会释放锁:
1.线程执行同步方法或同步代码块时,程序调用Thread.sleep()、Thread.yield()方法,暂停当前线程的执行,不会释放锁
2.线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,不会释放锁