并发编程-线程共享与协作

什么是进程和线程

进程是程序运行资源分配的最小单位:进程是操作系统进行资源分配的最小单位,其中资源包括:CPU、内存空间、
磁盘IO 等
线程是CPU 调度的最小单位,必须依赖于进程而存在:线程是进程的一个实体,是CPU 调度和分派的基本单位,它是比进程更小的、能独立运行的基本单位

进程是并发执行的程序在执行过程中分配和管理资源的基本单位。线程是进程的一个执行单元,是比进程还要小的独立运行的基本单位。

CPU 核心数和线程数的关系

核心数、线程数:目前主流CPU 都是多核的。增加核心数目就是为了增加线
程数,因为操作系统是通过线程来执行任务的,一般情况下它们是1:1 对应关系,也
就是说四核CPU 一般拥有四个线程。但Intel 引入超线程技术后,使核心数与线程
数形成1:2 的关系

CPU 时间片轮转机制

如果在时间片结束时进程还在运行,则CPU 将被剥夺并分配给另一个进程。
如果进程在时间片结束前阻塞或结来,则CPU 当即进行切换。调度程序所要做的
就是维护一张就绪进程列表,当进程用完它的时间片后,它被移到队列的末尾

并行和并发

并发: 指应用能够交替执行不同的任务,比如单CPU 核心下执行多线程并非是
同时执行多个任务,如果你开两个线程执行,就是在你几乎不可能察觉到的速度不
断去切换这两个任务,已达到"同时执行效果",其实并不是的,只是计算机的速度太
快,我们无法察觉到而已.
并行: 指应用能够同时执行不同的任务,例:吃饭的时候可以边吃饭边打电话,
这两件事情可以同时执行
两者区别:一个是交替执行,一个是同时执行.
在这里插入图片描述

高并发编的好处

(1)充分利用CPU 的资源
(2)加快响应用户的时间
(3)可以使你的代码模块化,异步化,简单化

多线程程序需要注意事项

(1)线程之间的安全性
(2)线程之间的死锁
(3)线程太多了会将服务器资源耗尽形成死机当机

Java中的线程

启动:启动线程的方式有
1、X extends Thread;,然后X.start
2、X implements Runnable;然后交给Thread 运行

Thread 和Runnable 的区别
Thread 才是Java 里对线程的唯一抽象,Runnable 只是对任务(业务逻辑)
的抽象。Thread 可以接受任意一个Runnable 的实例并执行。

//1):定义一个类A继承于java.lang.Thread类.  
class MusicThread extends Thread{  
    //2):在A类中覆盖Thread类中的run方法.  
    public void run() {  
        //3):在run方法中编写需要执行的操作  
        for(int i = 0; i < 50; i ++){  
            System.out.println("播放音乐"+i);  
        }  
    }  
}  
  
public class ExtendsThreadDemo {  
    public static void main(String[] args) {  
          
        for(int j = 0; j < 50; j ++){  
            System.out.println("运行游戏"+j);  
            if(j == 10){  
                //4):在main方法(线程)中,创建线程对象,并启动线程.  
                MusicThread music = new MusicThread();  
                music.start();  
            }  
        }  
    }  
  
}
//1):定义一个类A实现于java.lang.Runnable接口,注意A类不是线程类.  
class MusicImplements implements Runnable{  
    //2):在A类中覆盖Runnable接口中的run方法.  
    public void run() {  
        //3):在run方法中编写需要执行的操作  
        for(int i = 0; i < 50; i ++){  
            System.out.println("播放音乐"+i);  
        }  
          
    }  
}  
  
public class ImplementsRunnableDemo {  
    public static void main(String[] args) {  
        for(int j = 0; j < 50; j ++){  
            System.out.println("运行游戏"+j);  
            if(j == 10){  
                //4):在main方法(线程)中,创建线程对象,并启动线程  
                MusicImplements mi = new MusicImplements();  
                Thread t = new Thread(mi);  
                t.start();  
            }  
        }  
    }  
  
}

继承方式:

  1. 从设计上分析,Java中类是单继承的,如果继承了Thread了,该类就不能再有其他的直接父类了.
  2. 从操作上分析,继承方式更简单,获取线程名字也简单.(操作上,更简单)
  3. 从多线程共享同一个资源上分析,继承方式不能做到.

实现方式:

  1. 从设计上分析,Java中类可以多实现接口,此时该类还可以继承其他类,并且还可以实现其他接口,设计更为合理.
  2. 从操作上分析,实现方式稍微复杂点,获取线程名字也比较复杂,得使用Thread.currentThread()来获取当前线程的引用.
  3. 从多线程共享同一个资源上分析,实现方式可以做到(是否共享同一个资源).

中止
4. 线程自然终止stop
5. 中断interrupt()
线程通过方法isInterrupted()来进行判断是否被中断,也可以调用静态方法
Thread.interrupted()来进行判断当前线程是否被中断,不过Thread.interrupted()
会同时将中断标识位改写为false。
在这里插入图片描述
注意:处于死锁状态的线程无法被中断

1. run()和start()的区别

start()方法让一个线程进入就绪队列等待分配cpu,分到cpu 后才调用实现
的run()方法,start()方法不能重复调用,如果重复调用会抛出异常。
而run 方法是业务逻辑实现的地方,本质上和任意一个类的任意一个成员方
法并没有任何区别,可以重复执行,也可以被单独调用。
在这里插入图片描述
在这里插入图片描述
run()方法使用的主线程类,start()方法才是使用对应的应用线程类。

2.其他的线程相关方法

在这里插入图片描述
yield()方法:使当前线程让出CPU 占有权,但让出的时间是不可设定的。
wait()/notify()/notifyAll()

join 方法

把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行。插队的意思

线程的优先级

通过一个整型成员变量priority 来控制优先级,优先级的范
围从1~10,在线程构建的时候可以通过setPriority(int)方法来修改优先级,默认
优先级是5,优先级高的线程分配时间片的数量要多于优先级低的线程。数字越大优先级越高

守护线程

Daemon(守护)线程是一种支持型线程,因为它主要被用作程序中后台调
度以及支持性工作

线程间的共享

1. synchronized 内置锁

保证了线程对变量访问的可见性和排他性

对象锁和类锁:

对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态
方法或者一个类的class 对象上的。

①对象锁,锁的是Object o
在这里插入图片描述
②对象锁,锁当前实例
在这里插入图片描述
③类锁(锁的static方法),本质上是锁了每个类所独有的Class对象。
在这里插入图片描述

synchronized实现原理: 线程执行monitorenter指令时尝试获取monitor的所有权:
1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
2、如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.
3.如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。

2. volatile,最轻量的同步机制

volatile 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某
个变量的值,这新值对其他线程来说是立即可见的。
volatile 最适用的场景:一个线程写,多个线程读。

3. ThreadLocal

ThreadLocal 和Synchonized 都用于解决多线程并发訪问。可是ThreadLocal
与synchronized 有本质的差别。synchronized 是利用锁的机制,使变量或代码块
在某一时该仅仅能被一个线程訪问。而ThreadLocal 为每个线程都提供了变量的
副本,使得每个线程在某一时间訪问到的并非同一个对象,这样就隔离了多个线
程对数据的数据共享。
ThreadLocal 的4 个方法

  1. void set(Object value) 设置当前线程的线程局部变量的值。
  2. public Object get() 该方法返回当前线程所对应的线程局部变量。
  3. public void remove() 将当前线程局部变量的值删除,目的是为了减少内存的占用
  4. protected Object initialValue() 返回该线程局部变量的初始值,该方法是一个protected 的方法,显然是为了让子类覆盖而设计的。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    取到当前线程,然后调用getMap 方法获取对应的ThreadLocalMap,ThreadLocalMap 是ThreadLocal 的静态内部类,然后Thread 类中有一个这样类型成员,所以getMap 是直接返回Thread 的成员。

其实就是拿到每个线程独有的ThreadLocalMap然后再用ThreadLocal 的当前实例,拿到Map 中的相应的Entry,然后就可以拿到相应的值返回出去。当然,如果Map 为空,还会先进行map 的创建,初始化等工作。

引发的内存泄漏分析
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
因为ThreaLocalMap是虚引用,key发生垃圾回收时就会被回收掉,但是value是强引用,不会被回收。
Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value,而这块value 永
远不会被访问到了,所以存在着内存泄露。
**解决OOM内存溢出的办法:**使用ThreadLocal 变量后,都调用它的remove()方法,清除数据。

线程间的协作

1. 等待/通知机制

notify():通知一个在对象上等待的线程,使其从wait 方法返回,而返回的前提是该线程
获取到了对象的锁,没有获得锁的线程重新进入WAITING 状态。
notifyAll():通知所有等待在该对象上的线程
wait() 调用该方法的线程进入WAITING 状态,只有等待另外线程的通知或被中断才会返回
wait(long) 超时等待一段时间,这里的参数时间是毫秒,也就是等待长达n 毫秒,如果没有通知就超时返回
wait (long,int) 对于超时时间更细粒度的控制,可以达到纳秒

在调用wait()、notify()系列方法之前,线程必须要获得该对象的对象级
别锁,即只能在同步方法或同步块中调用wait()方法、notify()系列方法。

notify 和notifyAll 应该用谁
尽可能用notifyall(),谨慎使用notify(),因为notify()只会唤醒一个线程,我
们无法确保被唤醒的这个线程一定就是我们需要唤醒的线程

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值