首页 > 编程语言 > 详细

线程基础

时间:2021-01-25 14:18:31      阅读:26      评论:0      收藏:0      [点我收藏+]

多线程基础

一、基本介绍

1.1 进程

当一个程序进入到内存运行,即变成了一个进程,进程是处于运行过程中的程序,并且具有一定的独立功能

  • 正在运行中的程序
  • 具有一定的独立功能

比如正在运行中的(QQ、迅雷)

1.2 线程

线程是进程中的一个执行单元,负责当前进程中的程序的执行。

  • 一个进程至少有一个线程
  • 一个进程可以包含多个线程
  • 线程开启不一定立即执行

单线程:多个任务只能依次执行。上一个任务结束后,下一个任务开始(qq音乐一首一首播放)

多线程:多个任务同时执行(迅雷同时下载多个文件)

1.3 运行原理

  1. 分时调度:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。

  2. 抢占式调度优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性)

技术分享图片

实际上,CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。(CPU调度)

二、基本线程类使用

创建基本线程类有四种方式:

  • 实现Runnable接口

  • 继承Thread

  • 使用JDK 8 的Lambda

  • 使用Callable和Future

2.1 Thread类 主要方法

方法 说明
start() 启动线程
setName(String name) 设置线程名称
setPriority(int priority) 设置线程优先级,默认5,取值1-10
join(long millisec) 挂起线程xx毫秒,参数可以不传
interrupt() 终止线程
isAlive() 测试线程是否处于活动状态

Thread静态(static)方法

方法 说明
yield() 暂停当前正在执行的线程对象,并执行其他线程。
sleep(long millisec)/sleep(long millis, int nanos) 挂起线程xx秒,参数不可省略
currentThread() 返回对当前正在执行的线程对象的引用
holdsLock(Object x) 当前线程是否拥有锁

2.2 实现Runnable接口

  • 启动线程:传入目标对象+Thread对象.start()
  • 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
  public class MyThread implements Runnable{
      @override
      public void run(){
         	System.out.println("正在运行"+Thread.currentThread().getName());
      }     
      public static void main(String[] args) {
          MyThread t = new MyThread();
          new Thread(t).start();
  	}
  }

2.3 继承Thread类

  • 启动线程:子类对象.start()
  • 不建议使用:避免OOP单继承局限性
public class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("正在运行"+Thread.currentThread().getName());
    }
    public static void main(String[] args) {
        MyThread t = new MyThread();
        t.start();
    }
}

2.4 Callable接口

  • 能接受一个泛型,需要实现返回值
  • 重写call方法,可以抛出异常
  • get()方法的阻塞性
//callable的接口定义
public interface Callable<V> {
  V call() throws Exception;
}
public class MyThread implements Callable<Boolean> {
    @Override
    public Boolean call() throws Exception {
        System.out.println("call方法在执行");
        return true;
    }
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyThread callable = new MyThread();
        //创建1个线程
        ExecutorService ser = Executors.newFixedThreadPool(1) ;
		//提交执行
        Future<Boolean> result = ser.submit(callable) ;
        //获取返回值,返回值Future也是一个接口,通过他的get()方法可以获得任务执行的返回值。
        boolean b = result.get();
        //因为get()方法的阻塞性,获取返回值以后主线程才会继续执行
        ser.shutdown();//关闭
    }
}

2.5 run()和start()的区别

真正启动线程的是start()方法而不是run(),run()和普通的成员方法一样,可以重复使用,但不能启动一个新线程。

三、线程状态

3.1 线程状态概览

  • 初始NEW :新建尚未启动
  • 运行RUNNABLE: 可运行
  • 阻塞BLOCKED: 阻塞的(被同步锁或者IO锁阻塞)
  • 等待WAITING: 永久等待状态
  • 超时等待TIMED_WAITING: 等待指定的时间重新被唤醒的状态
  • 终止TERMINATED: 执行完成

线程的状态可以使用getState()查看

3.2 线程的优先级

  • 优先级高的不一定先执行
  • 优先级低只是获得CPU调度的概率低
  • 先设置优先级再启动
  • 优先级用数字表示,范围从1~10
public class TestPriority {
    public static void main(String[] args) {
        //默认优先级为5
        System.out.println(Thread.currentThread().getName()+"---"+Thread.currentThread().getPriority());
        Thread t0 = new Thread(new MyPriority()) ;
        Thread t1 = new Thread(new MyPriority()) ;
        Thread t2 = new Thread(new MyPriority()) ;

        t0.setPriority(Thread.MIN_PRIORITY);//1
        t0.start();

        t1.setPriority(Thread.NORM_PRIORITY);//5
        t1.start();

        t2.setPriority(Thread.MAX_PRIORITY);//10
        t2.start();

    }
}

class MyPriority implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() +"----"+ Thread.currentThread().getPriority());
    }
}

3.3 守护线程 Daemon

  • 线程分为用户线程和守护线程
  • 虚拟机必须等待用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
  • 后台记录操作日志,监控内存,垃圾回收等待
setDaemon(boolean on) 
isDaemon() 

四、线程安全

4.1 线程同步

发生多个线程同时操作同一个资源的情况下,需要线程同步,线程同步其实是一种排队机制

4.1.1 队列和锁

同一进程的多个线程共享同一存储空间,带来方便,也带来了访问冲突,为保证访问的正确性,访问时接入锁机制synchronized 特点

  • 一个线程持有锁,导致其他线程必须挂起
  • 加锁,释放锁会导致比较多的上下文切换,引起性能问题
  • 一个优先级高的线程等待低优先线程释放锁,导致优先级导致

4.1.2 不安全的线程

  • 多个线程同一时刻对同一个全局变量(同一份资源)做写操作 如果跟我们预期的结果一样为线程安全,反之线程不安全

不安全 案例1

//抢票案例
public class demo{
    public static void main(String[] args) {
        BuyTickNum buyTickNum = new BuyTickNum();//需要锁的对象是变化的量
        new Thread(buyTickNum,"小明").start();
        new Thread(buyTickNum,"黄牛").start();
        new Thread(buyTickNum,"小红").start();
    }
}

class BuyTickNum implements Runnable{
    private int ticketNums = 10 ;
    boolean flag = true ;
    @Override
    public void run() {
        //买票
        while(flag){
            buy();
        }
    }
    //需要加上synchronized关键字 同步方法
    private void buy(){
        //判断是否有票
        if(ticketNums <= 0){
            flag = false ;
            return ;
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //抢票
        System.out.println(Thread.currentThread().getName() + ":抢到了"+ticketNums--);
    }
}

不安全 案例2

public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    for (int i = 0; i < 10000; i++) {
        new Thread(()->{
            list.add(Thread.currentThread().getName()) ;
        }).start();
    }
    try {
        Thread.sleep(100);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(list.size());//输出结果不到10000
}

4.1.3 同步方法

  • synchronized 关键字

  • 同步块 synchronized(object){ }

    • object为需要锁的对象。操作变化的对象

同步方法执行的原理

  1. 第一个线程访问,锁定同步监视器,执行其中代码。

  2. 第二个线程访问,发现同步监视器被锁定,无法访问。

  3. 第一个线程访问完毕,解锁同步监视器

  4. 第二个线程访问,发现同步监视器没有锁,然后锁定并访问

synchronized关键字 案例
//
public class demo{
    public static void main(String[] args) {
        BuyTickNum buyTickNum = new BuyTickNum();//需要锁的对象是变化的量
        new Thread(buyTickNum,"小明").start();
        new Thread(buyTickNum,"黄牛").start();
        new Thread(buyTickNum,"小红").start();
    }
}

class BuyTickNum implements Runnable{
    private int ticketNums = 10 ;
    boolean flag = true ;
    @Override
    public void run() {
        //买票
        while(flag){
            buy();
        }
    }
    //加上synchronized关键字 同步方法
    private synchronized void buy(){
        //判断是否有票
        if(ticketNums <= 0){
            flag = false ;
            return ;
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //抢票
        System.out.println(Thread.currentThread().getName() + ":抢到了"+ticketNums--);
    }
}
同步块 案例
public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    for (int i = 0; i < 10000; i++) {
        new Thread(()->{
            synchronized(list){//加了同步块
	            list.add(Thread.currentThread().getName()) ;                
            }            
        }).start();
    }
    try {
        Thread.sleep(100);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(list.size());//输出结果不到10000 ,因为线程还没结束就打印输出了
}

4.1.4 死锁

  • 互斥条件:一个资源只能被一个进程使用
  • 请求与保持条件:一个进程因请求资源而阻塞
  • 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
  • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
死锁 案例
/** 
 * Java线程:并发协作-死锁 
 */  
public class Test {  
    public static void main(String[] args) {  
       DeadlockRisk dead = new DeadlockRisk();  
       MyThread t1 = new MyThread(dead, 1, 2);  
       MyThread t2 = new MyThread(dead, 3, 4);  
       MyThread t3 = new MyThread(dead, 5, 6);  
       MyThread t4 = new MyThread(dead, 7, 8);  
       t1.start();  
       t2.start();  
       t3.start();  
       t4.start();  
    }  
}  
   
class MyThread extends Thread {  
    private DeadlockRisk dead;  
    private int a, b;  
   
    MyThread(DeadlockRisk dead, int a, int b) {  
       this.dead = dead;  
       this.a = a;  
       this.b = b;  
    }  
    @Override  
    public void run() {  
       dead.read();  
       dead.write(a, b);  
    }  
}  
   
class DeadlockRisk {  
    private static class Resource {  
       public int value;  
    }  
    private Resource resourceA = new Resource();  
    private Resource resourceB = new Resource();  
    public int read() {  
       synchronized (resourceA) {  
           System.out.println("read():" + Thread.currentThread().getName()  
                  + "获取了resourceA的锁!");  
           synchronized (resourceB) {  
              System.out.println("read():" + Thread.currentThread().getName()  
                     + "获取了resourceB的锁!");  
              return resourceB.value + resourceA.value;  
           }  
       }  
    }  
    public void write(int a, int b) {  
       synchronized (resourceB) {  
           System.out.println("write():" + Thread.currentThread().getName()  
                  + "获取了resourceA的锁!");  
           synchronized (resourceA) {  
              System.out.println("write():"  
                     + Thread.currentThread().getName() + "获取了resourceB的锁!");  
              resourceA.value = a;  
              resourceB.value = b;  
           }  
       }  
    }  
}

4.1.5 Lock 锁(可重入锁)

  • lock的存储结构:一个int类型状态值(用于锁的状态变更),一个双向链表(用于存储等待中的线程)
  • lock获取锁的过程:本质上是通过CAS来获取状态值修改,如果当场没获取到,会将该线程放在线程等待链表中。
  • lock释放锁的过程:修改状态值,调整等待链表。

可重入锁使用方法

private final ReentrantLock lock = new ReentrantLock() ;
try{
    lock.lock();
    //处理任务
}catch(Exception ex){
     
}finally{
    lock.unlock();   //释放锁
}
抢票 案例
public class demo{
    public static void main(String[] args) {
        BuyTickNum buyTickNum = new BuyTickNum();
        new Thread(buyTickNum,"小明").start();
        new Thread(buyTickNum,"黄牛").start();
        new Thread(buyTickNum,"小红").start();
    }
}

class BuyTickNum implements Runnable{
    private int ticketNums = 10 ;
    //锁
    private final ReentrantLock lock = new ReentrantLock() ;
    boolean flag = true ;
    @Override
    public void run() {
        //买票
        while(flag){
            try{
                lock.lock();//获取锁
                buy();//不安全的方法
            }finally {
                lock.unlock();//释放锁
            }
        }
    }
    private void buy(){
        //判断是否有票
        if(ticketNums <= 0){
            flag = false ;
            return ;
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //抢票
        System.out.println(Thread.currentThread().getName() + ":抢到了"+ticketNums--);
    }
}

4.1.6 synchronized与Lock锁对比

  • Lock是显式锁(手动开启关闭)synchronized是隐式锁出了作用域自动四方
  • Lock只有代码块锁,synchronized有代码块锁和方法锁
  • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并具有更好的扩展性
  • 优先使用顺序
  • Lock > 同步代码块(已经进入方法体,分配了相应的资源)> 同步方法(在方法体之外)

4.2 线程协作

4.2.1 线程协作的核心方法:wait/notify

  • wait 和 notify 操作的是对象内置锁的等待队列
  • wait 类方法用于阻塞当前线程,将当前线程挂载进 Wait Set 队列
  • notify 类方法用于释放一个或多个处于等待队列中的线程
  • 必须在获得对象内置锁的前提下(只能在 synchronized 修饰的代码块内部进行)才能阻塞和释放等待队列上的线程

作用与用法

  • 等待线程
synchronized(lock){
    while(flag){
        lock.wait();
    }
    doAction();
}
  • 唤醒线程:
synchronized(lock){
    flag = false;
    lock.notifyAll();
}
  • 执行流程

技术分享图片

等待线程在 `wait();` 阻塞时释放锁,唤醒线程在执行结束时释放锁

等待线程可能不止一条,被唤醒时不一定能获取锁

如果条件被其他获取锁的等待线程修改,那么后续获得锁的等待线程会继续等待

等待线程与唤醒线程必须使用同一个锁

`notify();` 只能随机唤醒一条线程

4.2.2 生产者消费者模式

并发协作

  1. 生产者仅仅在仓储未满时候生产,仓满则停止生产。
  2. 消费者仅仅在仓储有产品时候才能消费,仓空则等待。
  3. 当消费者发现仓储没产品可消费时候会通知生产者生产。
  4. 生产者在生产出可消费产品时候,应该通知等待的消费者去消费。
管程法 案例
//并发协作模型”生产者/消费者模式”->管程法(利用管道容器来处理)
public class TestPc {
    public static void main(String[] args) {
        SynContiner continer = new SynContiner() ;
        new Thread(new Productor(continer) ).start();
        new Thread(new Cunsumer(continer)).start();
    }
}

//产品
class Chicken{
    public int id ;
    public Chicken(int id){
        this.id = id ;
    }
}

//生产者 只管生产
class Productor implements Runnable{
    private SynContiner continer ;
    public Productor(SynContiner continer ){
        this.continer = continer ;
    }
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            continer.Push(new Chicken(i));
            System.out.println("生产了"+i+"只鸡");
        }
    }
}

//消费者 只管消费
class Cunsumer implements Runnable{
    private SynContiner continer ;
    public Cunsumer(SynContiner continer){
        this.continer = continer ;
    }
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println("消费了"+continer.Pop().id+"只鸡");
        }
    }
}

//管道容器缓冲区 
class SynContiner{
    //容器容量
    Chicken[] chickens = new Chicken[10] ;
    //容器计数器
    int count = 0;
    //生产者 生产鸡
    public synchronized void Push(Chicken chicken){
        //如果容器满了,需要等待生产者消费 
        if(count == chickens.length-1){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果容器没满->丢产品进去
        chickens[count] = chicken;
        count++ ;
        //可以通知消费者消费了
        this.notifyAll();
    }

    //消费者 消费鸡(返回消费了的那只鸡)
    public synchronized Chicken Pop(){
        if(count == 0){
            //没有鸡了,等生产者生产
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        count-- ;
        Chicken chicken = chickens[count];
        //吃完了通知
        this.notifyAll();
        return chicken;
    }
}

信号灯法 案例
/*
*协作模型:生产者消费者实现方式二:信号灯法
 * 借助标志位
 */
public class CooperationTest {
    public static void main(String[] args) {
        Tv tv  =new Tv();
        new Player(tv).start();
        new Watcher(tv).start();
    }
}
//生产者 演员
class Player extends Thread{
    Tv tv;
    public Player(Tv tv) {
        this.tv = tv;
    }

    public void run() {
        for(int i=0;i<20;i++) {
            if(i%2==0) {
                this.tv.play("奇葩说");
            }else {
                this.tv.play("太污了,喝瓶立白洗洗嘴");
            }
        }
    }
}
//消费者 观众
class Watcher extends Thread{
    Tv tv;
    public Watcher(Tv tv) {
        this.tv = tv;
    }

    public void run() {
        for(int i=0;i<20;i++) {
            tv.watch();
        }
    }
}
//同一个资源 电视
class Tv{
    String voice;
    //信号灯
    //T 表示演员表演 观众等待
    //F 表示观众观看 演员等待
    boolean flag = true;

    //表演
    public  synchronized void play(String voice) {
        //演员等待
        if(!flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //表演
        System.out.println("表演了:"+voice);
        this.voice = voice;
        //唤醒
        this.notifyAll();
        //切换标志
        this.flag =!this.flag;
    }
    //观看
    public synchronized  void watch() {
        //观众等待
        if(flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //观看
        System.out.println("听到了:"+voice);
        //唤醒
        this.notifyAll();
        //切换标志
        this.flag =!this.flag;
    }
}

4.3 线程池

  • 提高响应速度(减少了创建新线程的时间)
  • 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
  • 方便线程管理(大小,数量,持续时间)
public class TestPool {
    public static void main(String[] args) {
        //1.创建服务,创建线程池
        //ExecutorService:真正的线程池接口
        //newFixedThreadPool 参数:线程池大小
        ExecutorService service = Executors.newFixedThreadPool(10) ;
        
        //执行:void excute(Runnable command)
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        
        //关闭线程
        service.shutdown();
    }
}
class  MyThread implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"--启动");
    }
}

线程基础

原文:https://www.cnblogs.com/wchaojie/p/14324755.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!