浅识Java多线程

前言:

本文只是对线程有一个基本的认识,多线程编程是Java里非常厚重的一层,如果想深入了解这个知识,必须花费大量的时间精力来学习。

多线程概念:

进程和线程的区别:

进程:每个进程都有独立的代码和数据空间,进程间切换会有较大的开销,一个进程包含1-n个线程(资源分配的最小单位)。

线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小(线程是cpu调度的最小单位)。

线程和进程都分五个阶段:创建、就绪、运行、阻塞、终止

多线程是指操作系统能同时运行多个程序。

并发和并行:

并发:同一时刻,多个任务交替执行,就像你开车打电话,大脑是在开车和打电话来回切换的,简单地说,单核Cpu实现的多任务就是并发。

并行:同一个时刻,多个任务同时执行,多核Cpu可以实现并行,就像你开车带着你女朋友,你开车,你女朋友打电话,即为并行。

实现多线程的方法手段:

若想实现多线程分两种方法分别是继承Thread类,或者实现Runable接口。(建议选择实现Runable接口,主要因为Java单继承的特点。)

继承Thread类
public class ThreadTest extends Thread{
    private String name;
    public ThreadTest(String name){
        this.name = name;
    }
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(name + "运行 :" + i);
            try {
                sleep(1000);//休眠1s
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        ThreadTest a = new ThreadTest("A");
        ThreadTest b = new ThreadTest("B");
        a.start();
        b.start();
    }
}
实现Runnable接口 
public class ThreadTest2 implements Runnable{
    private String name;

    public ThreadTest2(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(name + "运行 :" +i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        new Thread(new ThreadTest2("C")).start();
        new Thread(new ThreadTest2("D")).start();
    }
}

继承Thread和实现Runnable的区别

如果一个类继承了Thread类,由于Java单继承机制,则不适合资源共享。实现了Runnable接口的话更加容易实现资源共享。

实现Runnable接口比继承Thread类的优势

避免单继承的限制

增加代码的健壮性,代码可以被多个线程共享

线程池只能放入实现Runnable或callable类线程,不能直接放入继承Thread的类

Java运行时线程情况

实际上,Java的main方法也是一个线程,Java中所有的线程都是同时启动的,哪个先执行取决于谁先得到cpu的资源,上图代码中的sleep函数是为了使线程休眠,让其他线程可以获取cpu的资源,每次程序启动时候,main线程就被启动,还有一个垃圾收集线程。但是main线程结束也不影响其他(非守护)线程,但如果其他的是守护线程(使用setDaemon(true)),则main线程退出后,其他线程也退出。

线程状态转换图:

这个图特别重要,首先从左上角来看,新New了一个线程对象,该线程进入初始状态。

调用start函数后,进入了可运行态(这个状态只是表明这个线程可以运行,但仍需要去抢占cpu资源)。

抢到了cpu资源后进入运行状态,在这个状态如果调用sleep函数会让线程进入阻塞态。

在阻塞态的线程是由于某种原因放弃了cpu占用权,暂时停止运行,直到进入就绪态才有机会再次进入运行态,阻塞分为三种,

调用了sleep或join方法,只有当sleep状态超时,或join等待线程终止或者超时线程重新转入就绪态。Sleep不会释放锁

运行的线程执行wait方法,JVM会把该线程放入等待池中,wait会释放锁。

运行的线程获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会使该线程进入上锁态。

死亡状态:线程执行完或者因异常退出run方法,该线程结束生命周期。

线程休眠总结:

sleep、yield、join都会使当前线程让出cpu

sleep会休眠一段时间,不考虑其他线程的优先级,放弃cpu之后转为阻塞态。

yield没有时间间隔,它会使线程重新回到就绪态来争夺cpu

jon是会立即放弃cpu不考虑其他线程的优先级,放弃cpu后转为阻塞态。

线程同步

线程是一个独立运行的程序,有自己的运行栈,线程可能和其他线程共享文件、内存资源等

多线程同时读写一部分资源,会出现冲突,就像实现线程示例代码展示的,他们是混乱执行,线程同步就是排队,就像你上公共厕所要排队,一个个来对共享资源操作。

实现线程同步:

确保一个时间点只有一个线程访问共享资源,就像在外面上公共厕所,每一个时刻只有一个人能够进入方便,公共厕所是给门加了一把锁,而在java里也是这样做的,可以给共享资源加一把锁即Synchronized关键字同步方法或者代码块。

同步方法:

同步方法是使用synchronized修饰的方法格式为访问修饰符 synchronized 返回类型 方法名(){}

public synchronized void thread1(){
        
}

锁定的是调用这个方法的对象,其他线程不能访问这个对象的任何一个synchronized方法

不同的对象实例的synchronized方法是互相不干扰的,其他线程同样可以访问相同类的另一个对象中的synchronized方法

使用同步方法可以方便的将类变成线程安全的类。

同步代码块:

如果没有明确的对象上锁,只想让一块代码实现线程同步时,可以用同步代码块,

private byte[] lock = new byte[0];

    synchronized(lock){
        //被同步的代码
    }

线程通信

实现线程通信:

Java代码基于对共享数据进行wait()、 notify() 、notifyAll()来实现多个线程的通讯

wait():

导致当前线程等待,知道其他线程调用此对象的notify()方法或notifyAll()方法。

notify():

唤醒在此对象监视器上等待的单个线程。

notifyAll():

唤醒再次对象监视器上等待的所有线程。

线程死锁的概念:

线程死锁指的是两个线程互相持有对方的共享资源,拿老韩举的一个很形象的例子,小明妈妈和小明,小明说我先看电视再写作业,小明妈妈说你先写作业再看电视,造成了无限阻塞。

导致死锁的根源在于“synchronized”关键词的不恰当使用

解决死锁的办法:

让线程持有独立的资源

尽量不采用嵌套的synchronized语句。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值