线程基础,线程之间的共享和协作

1.基础概念

1.1 CPU核心数和线程数的关系

核心数:线程数=1:1 ;使用了超线程技术后---> 1:2

1.2 CPU时间片轮转机制

又称RR调度,会导致上下文切换

1.3 什么是进程和线程

进程:程序运行资源分配的最小单位,进程内部有多个线程,会共享这个进程的资源
线程:CPU调度的最小单位,必须依赖进程而存在。

1.4 澄清并行和并发

并行:同一时刻,可以同时处理事情的能力
并发:与单位时间相关,在单位时间内可以处理事情的能力

1.5 高并发编程的意义、好处和注意事项

好处:充分利用cpu的资源、加快用户响应的时间,程序模块化,异步化
问题:

  1. 线程共享资源,存在冲突;
    解决方法:利用解决线程共享问题
  2. 容易导致死锁;
    解决方法:
  • 在我们写代码的时候要注意,不要写相互竞争资源的代码;
  • 可以将需要获取资源的方式放在一个地方;比方说,A线程需要A锁,然后需要B锁,B线程需要B锁,A锁;这个时候,可以让第三方作为获取这两把锁的地方;
  • 加上过期时间。只有相对时间内可以持有锁、
  1. 启用太多的线程,就有搞垮机器的可能(CPU的时间片轮转机制,频繁地切换上下文,资源浪费)
    解决方法:
    利用线程池的方式,去把控资源的分配;

2.认识Java里的线程

2.1 线程启动的方式

一般来说我们常用的就三种,Runnable,Thread,Callable。

2.2 优雅的让线程停止工作

  1. 当然是自己走完整个生命周期;
  2. stop(),resume(), suspend()。不过这些都已经不使用。因此不展开了。stop()会导致线程不会正确释放资源(线程不安全),suspend()容易导致死锁。
  3. 线程的interrupt() 方法static方法interrupted(),isInterrupted
  • isInterrupted() :判定当前线程是否处于中断状态。
  • static方法interrupted(): 判定当前线程是否处于中断状态,同时中断标志位改为false。【底层都是调用native的isInterrupted】。两者的区别是传参


    image.png
  • interrupt():跟这个线程打个招呼,将线程的中断标志位置为true,线程是否中断,由线程本身决定。
    打断线程运行的DEMO
public class EndThread {
    private static class UserThread extends Thread {

        public UserThread(String name) {
            super(name);
        }
        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            // 判断是否中断
            while (true) {
                if (currentThread().isInterrupted()) {
                    System.out.println("state is " + isInterrupted());
                    return;
                }
                System.out.println(threadName + " is run!");
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        UserThread userThread = new UserThread("thread name");
        userThread.start();
        Thread.sleep(500);
        // 发出中断
        userThread.interrupt();
    }
}
  • 注意:方法里如果抛出InterruptedException,线程的中断标志位会被复位成false,如果确实是需要中断线程,要求我们自己在catch语句块里再次调用interrupt()。

2.3 线程常用方法和线程的状态

2.3.1 线程状态

Java线程中State中有五种状态

// 新建了线程,但是还没有运行
NEW
// 运行中的线程
RUNNABLE
// 阻塞的。一般来说,是锁的等待
BLOCKED
// 调用了wait方法,使当前线程进入等待状态
WAITING
// 等待某段时间
TIMED_WAITING
// 已经执行完该线程
TERMINATED

2.4 run,start区别

run就是普通的方法的调用,start()才是告诉JVM,我这个是多Java才会将线程对象和操作系统中实际的线程进行映射,再来执行run方法。

2.5 线程的优先级

取值为1~10,缺省为5,但线程的优先级不可靠,不建议作为线程开发时候的手段

2.6 守护线程

和主线程共死,finally不能保证一定执行。可以用setDaemon(true);设置为守护线程

3. 线程的共享

我们知道多线程编码下会带来很多问题,比如对同个资源的修改,会导致数据的不一致性;Java,为我们带来很多处理方案;

3.1 synchronized内置锁

synchronized

  • 可以用在代码块,用在方法上;
  • 可以用对象锁,类锁(静态代码)。两者互不干扰。【因为静态方法锁定在Class对象上,非静态方法锁定在该类的对象上。】

3.2 volatile关键字

适合于只有一个线程写,多个线程读的场景,因为它只能确保可见性。【注意,volatile并不具有原子性】

3.3 ThreadLocal

线程变量。可以理解为是个map,类型 Map<Thread,Integer>。每个线程都有自己的备份,因此不存在变量线程安全问题。但是不适合用比较大的对象。

4.线程间协作

线程间的协作(交互),主要三个方法

  • void notify():唤醒在此对象监视器上等待的单个线程。
  • void notifyAll():唤醒在此对象监视器上等待的所有线程。
  • void wait():导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。这里推荐使用notifyAll()

4.1 等待,通知标准范式(公式)

等待方
1、获取对象的锁;
2、循环里判断条件是否满足,不满足调用wait方法,
3、条件满足执行业务逻辑
通知方
1、获取对象的锁;
2、改变条件
3、通知所有等待在对象的线程
DEMO

**
 *
 *类说明:快递实体类
 */
public class Express {
    public final static String CITY = "ShangHai";
    private int km;/*快递运输里程数*/
    private String site;/*快递到达地点*/

    public Express() {
    }

    public Express(int km, String site) {
        this.km = km;
        this.site = site;
    }

    /* (通知方)变化公里数,然后通知处于wait状态并需要处理公里数的线程进行业务处理*/
    public synchronized void changeKm(){
        this.km = 101;
        notifyAll();
        //其他的业务代码

    }

    /* 变化地点,然后通知处于wait状态并需要处理地点的线程进行业务处理*/
    public synchronized void changeSite(){
        this.site = "BeiJing";
        notify();
    }

    /*等待方*/
    public synchronized void waitKm(){
        while(this.km<=100) {
            try {
                wait();
                System.out.println("check km thread["+Thread.currentThread().getId()
                        +"] is be notifed.");
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        System.out.println("the km is"+this.km+",I will change db.");

    }

    public synchronized void waitSite(){
        while(CITY.equals(this.site)) {
            try {
                wait();
                System.out.println("check site thread["+Thread.currentThread().getId()
                        +"] is be notifed.");
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        System.out.println("the site is"+this.site+",I will call user.");
    }
}

TestMain

public class TestWN {
    private static Express express = new Express(0,Express.CITY);

    /*检查里程数变化的线程,不满足条件,线程一直等待*/
    private static class CheckKm extends Thread{
        @Override
        public void run() {
            express.waitKm();
        }
    }

    /*检查地点变化的线程,不满足条件,线程一直等待*/
    private static class CheckSite extends Thread{
        @Override
        public void run() {
            express.waitSite();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        for(int i=0;i<3;i++){//三个线程
            new CheckSite().start();
        }
        for(int i=0;i<3;i++){//里程数的变化
            new CheckKm().start();
        }

        Thread.sleep(1000);
        express.changeKm();//快递地点变化
    }
}

4.2 等待超时模式范式

假设 等待时间时长为T,当前时间now+T以后超时

long overtime = now+T;
long remain = T;//等待的持续时间
while(result不满足条件&& remain>0){
    wait(remain);
    remain = overtime – now;//等待剩下的持续时间
}
return result;

4.3 其他

  • join():线程A,执行了线程B的join方法,线程A必须要等待B执行完成了以后,线程A才能继续自己的工作;【应用场景是当一个线程必须等待另一个线程执行完毕才能执行时可以使用join方法。】
  • yield():暂停当前正在执行的线程对象,并执行其他线程。【持有的锁是不释放的】
  • sleep():当前线程睡眠一段时间,【持有的锁不释放】
  • wait():【持有的锁会释放】
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。