Java并发编程之一:线程基础,线程之间的共享与协作

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

目录

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

1.CPU时间片轮转机制

2.进程和线程

3.并行和并发

4.高并发编程的含义,好处及注意事项

(二)认识java的线程

1.3种创建线程的方式

2.线程安全停止工作的方式

3.线程常用方法和线程的状态

5.线程中的stop(),interrupt(),isInterrupted(),static方法interrupted()方法的含义和区别

(三)线程间的共享

1.synchronized内置锁

2.volatile关键字,最轻量级的同步锁

(四)线程之间的协作

3.Yield(),sleep(),wait(),notify(),notifyAll()含义


(一)基本概念

1.CPU时间片轮转机制

        时间片轮转法(Round-Robin,RR)主要用于分时系统中的进程调度。为了实现轮转调度,系统把所有就绪进程按先入先出的原则排成一个队列。新来的进程加到就绪队列末尾。每当执行进程调度时,进程调度程序总是选出就绪队列的队首进程,让它在CPU上运行一个时间片的时间。时间片是一个小的时间单位,通常为10~100ms数量级。当进程用完分给它的时间片后,系统的计时器发出时钟中断,调度程序便停止该进程的运行,把它放入就绪队列的末尾;然后,把CPU分给就绪队列的队首进程,同样也让它运行一个时间片,如此往复.

2.进程和线程

       线程,有时被称为轻量级进程,是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。

简单来说,

     进程:程序运行资源分配得最小单位,进程内部有多个线程,线程共享这个进程上的资源。

     线程:CPU调度的最小单位

3.并行和并发

并行:同一时刻,可以处理事件得能力

并发:与时间单位相关,在单位时间内可以处理事情得能力。

例:食堂打饭,有8个窗口,设每人打饭时常为20秒,则并行量为8,并发量为每分钟24

4.高并发编程的含义,好处及注意事项

含义:高并发是请求,指的是多个客户端同一时刻向服务端发送请求,它是一种现象。

比如,电商网站在双11凌晨12:00分 同时有2000个下单请求。

多线程是处理,指的是同一时刻多个执行者处理同一类的任务,它有具体的实现。比如电商网站在双11凌晨12:00分同时有100个线程处理2000个下单请求

好处:

     1.更好得利用系统资源

     2.提高程序的运行速度

注意事项:

     1.安全性问题,多个线程共享数据可能产生与期望不符合的结果

     2.活跃性问题,当某个操作无法继续进行下去时,就会产生活跃性问题,如死锁,饥饿,活锁等问题

     3 性能问题

               a.线程过多时会使得CPU频繁切换,花在调度上时间太多。

               b.多线程环境必须使用同步机制,导致很多编译器想做的优化被抑制。

               c.线程过多还会消耗过多内存。

(二)认识java的线程

1.3种创建线程的方式

1、Java使用Thread类代表线程。

    所有的线程对象必须是Thread类或其子类的实例。

    当线程继承Thread类时,直接使用this即可获取当前线程,Thread对象的getName()方法返回当前线程的名字,因此可以直接调用getName()方法返回当前线程的名字。

    Thread.currentThread():该方法总是返回当前正在执行的线程对象。

2、创建线程方式1:继承Thread类创建线程类

    这种方式创建线程的步骤一般为:

    1》定义Thread类的子类,并重写该类的run()方法,该方法作为线程的线程执行体。

    2》创建Thread子类的实例,即线程对象。

    3》调用线程对象的start()方法启动线程。

public class extendsThread extends Thread{
  @Override
  public void run(){
      ...
      //do something
      System.out.println(this.getName());  }
   public static void main(String[] agrs){
      //创建并启动第一个线程
      new extendsThread().start();
      //创建并启动第二个线程
      new extendsThread().start();
   }
}

创建线程方式2:实现Runnable接口

    这种方式创建线程的步骤一般为:

      1》定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。

      2》创建Runnable接口实现类的实例,并将该实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。本质是Thread对象负责执行实现类对象的run()方法体。

      3》调用线程对象的start()方法来启动该线程。

public class ImplRunnable implements Runnable{
   @Override
   public void run(){
       //do something 
       System.out.println(Thread.currentThread().getName());
   }

   public static void main(String agrs){
      System.out.println(Thread.currentThread().getName());
      ImplRunnable target=new ImplRunnable();
      //创建并启动第一个线程
      new Thread(target,"Thread1").start();
      //创建并启动第二个线程
      new Thread(target,"Thread2").start();
  }
}

创建线程方式3:使用Callable和Future创建线程创建有返回值的线程

    这种方式创建线程的步骤一般为:

       1》创建Callable接口的实现类,并实现call()方法,该方法作为线程执行体,且该方法有返回值。然后创建该实现类的实例。

       2》使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。

       3》使用FutureTask对象作为Thread对象的target创建并启动新线程。

       4》调用FutureTask对象的get()方法获取子线程执行结束后的返回值。

public class ImplCallable implements Callable<Integer>{
   @Override
   public Integer call(){
      //do something      
System.out.println(Thread.currentThread().getName());
      //return a value.
      return 1;
   }

    public static void main(String[] agrs) throws InterruptedException, ExecutionException{
      ...
      //创建并启动一个线程
      FutureTask<Integer> target=new FutureTask<Integer>(new ImplCallable());
      new Thread(target,"Thread3 with value.").start();
      //获取返回值      
System.out.println(target.get());
    }
}

2.线程安全停止工作的方式

    a.自然停止,线程中的代码正常运行完毕。

    b.线程内抛出异常

注意:stop(),resume(),suspend()方法会停止线程,但不会释放该线程所占用的资源

3.线程常用方法和线程的状态

常用方法:

    run():相当于线程的任务处理逻辑的入口方法,它由Java虚拟机在运行相应线程时直接调用,而不是由应用代码进行调用。简单来说就是调用一个普通方法

    srtart():启动相应的线程。启动一个线程实际是请求Java虚拟机运行相应的线程,而这个线程何时能够运行是由线程调度器决定的。start()调用结束并不表示相应线程已经开始运行,这个线程可能稍后运行,也可能永远也不会运行。

    yield():将线程从运行状态转为就绪状态

4.守护线程

在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程)

  • 用户线程是指用户自定义创建的线程,主线程停止,用户线程不会停止
  • 只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。Daemon的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器)

注意
(1) thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
(2) 在Daemon线程中产生的新线程也是Daemon的
(3) 不要认为所有的应用都可以分配给Daemon来进行服务,比如读写操作或者计算逻辑

守护线程和他的主线程共死,守护线程内的finally内得代码不一定执行

5.线程中的stop(),interrupt(),isInterrupted(),static方法interrupted()方法的含义和区别

stop():暴力中断线程,会导致线程资源未释放

interrupt():中断线程。调用该方法的线程的状态将被置为"中断"状态。注意:线程中断仅仅是设置线程的中断状 态位,不会停止线程。需要用户自己去监视线程的状态为并做处理(如果线程在wait, sleep,join的时 候受阻,调用了interrupt()方法,那么不仅会清除中断标志位,还会抛出InterruptedException异常)

isInterrupted():查询线程中断标志位,不会改变中断标志位

interrupted():第一次使用返回true,并清除中断标志位,在此之后查询中断状态isInterrupt()都会返回false,

(三)线程间的共享

1.synchronized内置锁

在并发编程中存在线程安全问题,主要原因有 :1.存在共享数据 2.多线程共同操作共享数据。关键字synchronized可以保证在同一时刻,只有一个线程可以执行某个方法或某个代码块,同时synchronized可以保证一个线程的变化可见(可见性),即可以代替volatile。

Synchronized:是一种同步锁。作用是实现线程间同步,对同步的代码加锁,使得每一次,只能有一线程进入同步块,从而保证线程间的安全性,同时它还可以保证共享变量的内存可见性

它修饰的对象有以下几种:

        a.修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的部分,进入同步代码前要获得给定对象的锁

         b.修饰一个实例方法,进入同步代码前要获得当前实例的锁

         c.修饰一个静态方法,进入同步代码前要获得当前类的锁
一句话总结synchronized:JVM会自动通过使用monitor来加锁和解锁,保证了同一时刻只有一个线程可以执行指定的代码,从而保证线程安全,同时具有可重入和不可中断的特性。

1、两个线程同时访问一个对象的相同的synchronized方法
    同一实例拥有同一把锁,其他线程必然等待,顺序执行
2、两个线程同时访问两个对象的相同的synchronized方法
    不同的实例拥有的锁是不同的,所以不影响,并行执行
3、两个线程同时访问两个对象的相同的static的synchronized方法
    静态同步方法,是类锁,所有实例是同一把锁,其他线程必然等待,顺序执行
4、两个线程同时访问同一对象的synchronized方法与非synchronized方法
    非synchronized方法不受影响,并行执行
5、两个线程访问同一对象的不同的synchronized方法
    同一实例拥有同一把锁,所以顺序执行(说明:锁的是this对象==同一把锁)
6、两个线程同时访问同一对象的static的synchronized方法与非static的synchronized方法
    static同步方法是类锁,非static是对象锁,原理上是不同的锁,所以不受影响,并行执行
7、方法抛出异常后,会释放锁吗
    会自动释放锁,这里区别Lock,Lock需要显示的释放锁
3个核心思想:
    1、一把锁只能同时被一个线程获取,没有拿到锁的线程必须等待
    2、每个实例都对应有自己的一把锁,不同的实例之间互不影响;例外:锁对象是*.class以及synchronized被static修饰的时候,所有对象共用同一把锁
    3、无论是方法正常执行完毕还是方法抛出异常,都会释放锁
可参考:https://www.cnblogs.com/JsonShare/p/11433302.html

2.volatile关键字,最轻量级的同步锁

1.保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。(实现可见性

2.禁止进行指令重排序。(实现有序性

3.volatile 只能保证对单次读/写的原子性。i++ 这种操作不能保证原子性。
簡單來説,内存可见性,线程不安全(常见场景:一个线程写,多个线程读)

(詳情可參考https://blog.csdn.net/devotion987/article/details/68486942

3.ThreadLocal

ThreadLocal,顾名思义,它不是一个线程,而是线程的一个本地化对象。当工作于多线程中的对象使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程分配一个独立的变量副本。所以每一个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。从线程的角度看,这个变量就像是线程的本地变量,这也是类名中“Local”所要表达的意思。

从源码中可以推测出ThreadLocal本质上是一个Map,key为Thread,value为初始化值

 /**
     * Variant of set() to establish initialValue. Used instead
     * of set() in case user has overridden the set() method.
     *
     * @return the initial value
     */
    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

下面我们用ThreadLocal写个demo

package com.thread.day1;
public class ThreadLocalTest extends Thread{
	private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){
		@Override
		protected Integer initialValue() {
			return 1;
		}
	};
	@Override
	public void run() {
		threadLocal.set(threadLocal.get()+1);
		System.out.println("Thread name :["+Thread.currentThread().getName()+"]"+threadLocal.get());
	}
	public static void main(String[] args) throws InterruptedException {
		Thread.sleep(1000);
		for (int i = 0; i < 10; i++) {
			new ThreadLocalTest().start();
		}
	}

執行結果

Thread name :[Thread-2]2
Thread name :[Thread-1]2
Thread name :[Thread-0]2
Thread name :[Thread-3]2
Thread name :[Thread-5]2
Thread name :[Thread-4]2
Thread name :[Thread-6]2
Thread name :[Thread-8]2
Thread name :[Thread-7]2
Thread name :[Thread-9]2

(四)线程之间的协作

1.等待和通知
   
 wait()
     notify()
     notifyAll()

生产者与消费者

2.Join()
      在某些情况下,子线程需要进行大量的耗时运算,主线程可能会在子线程执行结束之前结束,但是如果主线程又需要用到子线程的结果,换句话说,就是主线程需要在子线程执行之后再结束。这就需要用到join()方法
(简单来说就是插队)

3.Yield(),sleep(),wait(),notify(),notifyAll()含义

yield();一个线程调用yield()意味着告诉虚拟机自己非常乐于助人,可以把自己的位置让给其他线程(这只是暗示,并不表绝对)。但得注意,让出cpu并不代表当前线程不执行了。当前线程让出cpu后,还会进行cpu资源的争夺,但是能不能再次分配到,就不一定了

sleep():sleep()方法属于Thread类让当前线程停止执行,把cpu让给其他线程执行,但不会释放对象锁和 监控的状态,到了指定时间后线程又会自动恢复可运行状态

wait():wait()属于Object类,与sleep()的区别是当前线程会释放锁,进入等待此对象的等待锁定池。比方说, 线程A调用Obj.wait(),线程A就会停止运行,而转为等待状态。至于等待多长时间? 那就看其他线程是否 调用Obj.notify().其优势显而易见,成为多个线程之间进行通讯的有手段!

注意:它必须包含在Synchronzied语句中,无论是wait()还是notify()都需要首先获得目标的对象的一个监视器

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值