Java线程入门

目录

一.线程相关概念

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()方法将该线程挂起,不会释放锁

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值