本文章目录:

  1. 进程与线程的概念
  2. 两者的优缺点和对比
  3. 多线程的使用
    两种创建的方式:Thread 和 Runnable
    两种启动的方式start 和 run
    多线程的关闭
  4. 多线程信息共享
  5. 多线程的管理
  6. 锁浅谈



推荐小白入门视频:
B站的2019Java多线程精讲【千锋大数据】点击直达https://www.bilibili.com/video/BV1Z4411G7vn/?p=1

该视频讲漏的知识点(对比我知道的):
1.volatile:解决共享变量突破上下限的



简单来说下多进程的实质概念:

OS(操作系统)将CPU的使用时间分成N多极其短暂的时间片段,例如几纳秒几微秒,然后分配给多个进程。

(这里指单核的情况下)实质上是串行依次进行多个任务。因为CPU交替间隔太短,造成了同时进行的错觉。
多核自然就能实现真正意义的并行多进程任务,多个嘛

简单概括就是:OS把CPU的心掰成很多片,给了多个人(进程),轮流使用


多进程的优点:

  • 同时运行多个任务
  • 但某个程序被IO堵塞时,可以释放CPU给其他进程使用

多进程的缺点:

太笨重,不好管理,也不好切换


多线程的概念:

————多线程是对多进程的优化方案

线程简单讲就是降低了量级的小进程,它是将单个进程 进行 进一步分化,更加合理的利用CPU资源

多线程是把创造它的进程当作小OS,这样就可以实现单进程下的多线程串行、并行任务


注意:(单核下)多线程的并行任务本质上也和多进程一样,都是CPU的串行执行,不是同时执行!!!

多线程存在的好处:

  1. 资源利用: 不会造成当程序某个模块IO堵塞时,就立马释放整个进程的CPU,直接切换进程,造成极大浪费资源(进程笨重),而是会继续停留在该进程执行其他线程
  2. 执行效率: 在执行同样数量的任务时,单进程多线程和单进程单线程相比,显然多线程更快,并行比串行快


多进程和多线程的对比:

  • 量级方面: 线程更轻量级,更容易管理和切换,CPU切换的代价小
  • 数据共享:(同个进程下)多线程可以共享数据(同个运行空间),通讯更高效。而多进程之间的数据一般不能共享和通讯。

概念终于理清了



多线程的使用


一、创建新线程:

  • 继承java.lang.Thread类:
    可以直接new ,即 new 继承类的类名.start();
  • 实现java.lang.Runnable接口:
    不能直接new启动,必须依赖Tread类,即new Thread(new 实现接口类的类名.start() );

两种方式都要实现run方法



用Thread 和 Runnable 创建的对比:

  • Runnable的唯一缺点: 需要Thread类的支持,即无法直接new 启动
  • Runnable不需要占用继承的名额(单继承,多接口)
  • Runnable实现数据共享更容易:Runnable普通变量就可以共享,而Thread方式必须static变量才能共享

建议使用实现Runnable接口方式来完成多线程


题外话——java的四大接口

  • Comparable:用于对象比较
  • Runnable:用于对象线程化
  • Serializable:用于对象序列化
  • Clonable:用于对象克隆

共享资源 又称为 临界资源


二、多线程启动

多线程启动的两种方式:
————————————易错处

  • 利用start方法启动:会自动以新线程启动,是并行 运行
  • 直接执行run方法:将变成串行执行!(这样和单线程差不多)

简单讲就是run启动的直接就是直线执行代码!被run启动的线程不结束,就不会执行该线程之后的代码(即串行)

注意点:

  • 一个线程只能有0或者1个start方法
  • 多个线程的启动顺序是随机的(真正意义上的,只能确定main线程是最先启动

三、多线程的关闭:

  • run方法结束后,系统自动关闭
  • 程序(进程) 结束: 当所有线程(包括main线程)都结束,程序才结束
  • 手动关闭:stop( )

注意点:main线程结束不等于程序结束,其他线程也可以继续单独进行




四、多线程信息共享(重点)

这里有个概念

  • 粗粒度:子线程与子线程之间、子线程与main线程缺乏交流,没有同步协调操作,如信息不共享
  • 细粒度:线程之间有交流,有同步协调操作,最简单的体现就是信息共享

换句话说就是:线程之间没有信息共享,就是粗粒度,有就是细粒度


信息共享方法:

  • 通过共享变量实现信息共享
  • MPI并行库实现线程之间直接互发消息 (MPI是C/C++的并行库,java原生库并不支持这种形式。题外话:MPI是高性能计算的主要模型


共享变量:

  • static变量: 大家都懂,静态嘛,略。(继承Tread方式的只能使用这种static变量达到共享!)
  • 同一Runnable里的成员变量,即实现了Runnable接口的类同一个实例的成员变量都可以共享

注意点:如果是同一Runnable类的不同实例,那么他们的成员变量是不共享的!!!必须同一个Runnable类的同一个实例

这里暂时忽略数据同步的问题

public class ThreadDemo {
    public static void main(String[] a){
        ThreadRun r1 = new ThreadRun();
        new Thread(r1).start();
        new Thread(r1).start();
        new Thread(r1).start();
        new Thread(r1).start();
    }
}

class ThreadRun implements Runnable{
    private int tickets = 200;

    @Override
    public void run() {
        while(tickets > 0){
            try{
                Thread.sleep(50);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println("卖出门票一张,剩余:"+ tickets--);
        }
    }
}

结果:

java使用多进程 java实现多进程_System


不同实例r1、r2,代码和结果如下,显然不能数据共享

public class ThreadDemo {
    public static void main(String[] a){
        ThreadRun r1 = new ThreadRun();
        ThreadRun r2 = new ThreadRun();
        new Thread(r1).start();
        new Thread(r2).start();
        new Thread(r1).start();
        new Thread(r2).start();
    }
}

class ThreadRun implements Runnable{
    private int tickets = 200;

    @Override
    public void run() {
        while(tickets > 0){
            try{
                Thread.sleep(50);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println("卖出门票一张,剩余:"+ tickets--);
        }
    }
}

java使用多进程 java实现多进程_java使用多进程_02





现在来解决一下数据不同步的问题

两方面下手(两种选一种就行):

  • 数据同步:共享变量加个volatile修饰符
  • 关键步骤加锁:给代码块或者函数加synchronized修饰符

题外话——关键步骤加锁:

  • 互斥:同一时间只能有一个线程执行该代码块或函数
  • 同步:多个线程的运行必须按照某种规定的先后顺序来执行这个代码段
    互斥是同步的一种特例,同步限制更严

synchronized会加大性能负担,但使用方便,是java里互斥的一种简单实现


数据加volatile:
        该共享数据所有线程都能看到它的实时变化,就不会出现数据不同步的问题

代码块或函数加synchronized:
        该代码块或者函数在同一瞬间只能让一个线程进入,不能同时被多个线程执行,数据就同步了

上面两种都要做的:在最后的临界资源语句(即加锁的地方)那里加判断条件,就不会出现数据上限或下限被突破的问题(第一张图片里的-1,-2)


synchronize使用注意点:

  • 依赖单例对象锁: synchronize需要一个对象作为锁,而且这个对象是单例的,即不可重复synchronize(new 对象)
  • 使用synchronize加锁方法或函数时,直接当修饰符用,不需要写对象锁:如果是静态的方法或函数,则锁是当前类,即默认synchronize(当前类名);若是非静态的,那么对象锁就是this,即默认synchronize(this)

修改后的代码如下:

package com.company;

public class ThreadDemo {
    public static void main(String[] a){
        ThreadRun r1 = new ThreadRun();//必须同个实例才能共享
        new Thread(r1).start();
        new Thread(r1).start();
        new Thread(r1).start();
        new Thread(r1).start();
    }
}

class ThreadRun implements Runnable{
//    private volatile int tickets = 200;	//volatile和synchronize两个有一个存在就行
	private volatile int tickets = 200;
    String socket = "";     //锁
    @Override
    public void run() {
        while(tickets > 0){
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized(socket) {      //这里是24行,synchronized依赖一个对象(任意),用该对象作为锁
                if (tickets>0){
                    System.out.println("卖出门票一张,剩余:" + --tickets);
                }
            }
            //24到28也可以换成注释的写法,下面的写法就不需要再重新new个对象当锁(例如上面的socket)
            //printStr();
        }
    }

//    public synchronized void printStr(){
//        if (tickets>0){
//            System.out.println("卖出门票一张,剩余:" + --tickets);
//        }
//    }


}

结果:

java使用多进程 java实现多进程_多线程_03




五、多线程管理

5.1、先了解下线程的状态:

1. NEW 刚创建: new
2. RUNNABLE就绪态:start
3. RUNNING运行中:run
4. BLOCK阻塞:sleep
5. TERMINATED结束



线程状态流程图:(借用别人网课的图,嘿嘿偷下懒,看不清的,去上面的视频链接自己看图)

java使用多进程 java实现多进程_java使用多进程_04

若有侵权请通知我,立马删

5.2、管理的内容实际上就两种:

  • 信息共享: 上面说了,略
  • 改变线程状态:改变自己线程或其他线程的状态,以达到想要的程序效果


改变线程状态:

  • 新生态 (NEW) ------> 就绪态(RUNNABLE) : 利用start( )
  • 就绪态 < ------> 运行态(互转):OS 自己控制(这两个状态就暂时合称为活动期吧)
  • 活动期 ------> 阻塞态(BLOCK):suspend( )
  • 阻塞(BLOCK) ------>活动期 :resume( )
  • 活动期------>TERMINATED(死亡态):stop( )
  • 阻塞(BLOCK) ------>TERMINATED(死亡态):stop( )


被废弃的API:(不安全、旧的)

  • suspend( ):暂停
  • resume( ):恢复
  • stop( ):停止
  • distory( ):销毁

API解释:

  • join( ):等待另一个线程结束才运行
  • interrupt( ):向另一个线程发送中断信号,使他进入阻塞状态


管理分被动和主动两种方式

  • 方式一:被动停止或恢复自身:在一个线程里用上述API管理另一个线程
  • 方式二:主动停止或恢复自身:线程自身用上述API管理自身(实际上就是其他线程利用共享变量来告知另一个线程,间接让另一个线程停止或恢复活动,不直接干预


被动管理是粗暴的且不受它自身控制的:有可能在被结束时,来不及释放自身持有的资源,或者来不及执行其他指定的结束操作!

5.3、线程的优先级
        简简单单的就一句:线程名.setPriority(数值);

注意点:

  • 优先级的设置范围是1到10数值越大,越优先
  • 优先级默认值是5
  • 优先级必须在线程启动前设置
  • 优先级只是减小或增大该线程抢夺到CPU的概率,并不是百分百决定线程先后的执行顺序

5.4、线程的礼让
        概念:强迫该线程放弃抢到的CPU资源,重新回到就绪态,继续抢CPU资源
        简单说就是强迫该家伙把到手的肥肉扔出去,重新去抢肥肉,美其名曰:孔融让梨。
        就一句:线程名.yield( );



例子一(被动停止或恢复自身的线程):
百度网盘——生产者与消费者例子的链接,点击直达
https://pan.baidu.com/s/1ClceQNd_vvDN5nza6MPxHg 提取码:rio1



例子二(主动停止或恢复自身的线程):
线程主动管理的例子,点击直达
https://pan.baidu.com/s/1oX5pQMvqXlgFNVMhIpbA8Q 提取码:k77f


六、锁浅谈(极浅极浅的)

1.互斥锁:上面的synchronize

  • 必须:需要一个对象锁(类也是对象)
  • 其他:略

2.显式锁:ReentTranLock

  • lock( ):给后面代码加锁
  • unlock( ) :线程归还锁,即解锁
import java.util.concurrent.locks.ReentrantLock;

public class SimpleThreadDemo {
    public static void main (String[] a){
      //显式锁ReenTranLock
        ThreadTickets tickets = new ThreadTickets();
        Thread threadTickets1 = new Thread(tickets);
        Thread threadTickets2 = new Thread(tickets);
        Thread threadTickets3 = new Thread(tickets);
        threadTickets1.start();
        threadTickets2.start();
        threadTickets3.start();
    }
}


//显式锁例子
class ThreadTickets implements Runnable{
    ReentrantLock RTlock = new ReentrantLock();//实例化一个显式锁
    private int tickets = 200;
    @Override
    public void run() {
        while(tickets > 0){
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            RTlock.lock();  //给后面的代码加锁
            if(tickets > 0){
                System.out.println(Thread.currentThread().getName()
                        + "卖出门票一张,剩余:" + --tickets);
            }
            RTlock.unlock();    //线程归还锁,即解锁
        }
    }
}

3.死锁:
        死锁就是在多线程里,自己需要的锁被被别的线程持有,而自己持有的锁是别的线程需要的,互相不放开已有的锁,互相卡住,导致彼此都是wait

        简单说就是:A、B是邻居,A在B家里,B在A家里,两家的门都是锁的,但A、B赌气就是都不通过窗户扔手里的钥匙给对方,两人就都被锁住了。

import java.util.concurrent.locks.ReentrantLock;

public class SimpleThreadDemo {
    public static void main (String[] a){
        //死锁例子
        Thread threadDead1 = new Thread(new ThreadDeadA());
        Thread threadDead2 = new Thread(new ThreadDeadB());
        Thread threadDead3 = new Thread(new ThreadDeadC());
        threadDead3.start();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        threadDead1.start();
        threadDead2.start();
    }
}
//死锁例子
class ThreadDeadA implements Runnable{
    @Override
    public void run() {
        try {
            synchronized ("钥匙1"){
                System.out.println("我是A,我有1号房钥匙,但我现在在2号房间,我不想给B钥匙");
                Thread.sleep(3500);
                System.out.println("我堂堂高贵的A,就算饿死在2号房间,我也不会先扔钥匙给B!");
                //解除下面四行的注释符,则死锁解开
//                Thread.sleep(4000);
//                System.out.println("我是A,这2号房也太多蚊子了,叮得老子浑身痒,受不了了,我就先扔钥匙给B吧");
//                Thread.sleep(2500);
//                "钥匙1".wait();
                synchronized ("钥匙2"){
                    System.out.println("我是A,我拿到2号房钥匙了,我出来了,终于不用被蚊子叮了,真香");
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
class ThreadDeadB implements Runnable{
    @Override
    public void run() {
        try{
            synchronized ("钥匙2"){
                System.out.println("我是B,我有2号房钥匙,但我现在在1号房间, 我不想给A钥匙");
                Thread.sleep(1000);

                synchronized ("钥匙1"){
                    System.out.println("我是B,没想到半夜A睡觉前偷偷给我钥匙了。" +
                            "我拿到1号房钥匙了,我出来了");
                    Thread.sleep(3000);
                    System.out.println("两把钥匙对我也没用了,我就偷偷扔钥匙进去," +
                            "弄点声音叫醒他吧,别让他继续困在里面了");
//                    "钥匙1".notify();     //随机叫醒一个拿过1号钥匙的人,不吵到别的邻居
                    "钥匙1".notifyAll();     //叫醒所有拿过1号钥匙的邻居们,不吵到别的邻居
//                    notifyAll();    //管他们睡不睡,老子就是想唱大河向东流
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
class ThreadDeadC implements Runnable{
    public String c = "钥匙1";
    @Override
    public void run() {
        synchronized ("钥匙1"){
            try {
                System.out.println("我是路人C,我脚崴了,痛死我啦");
                "钥匙1".wait();
                Thread.sleep(2000);
                System.out.println("我是C,有神秘人救了我,是不是上帝?");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}