Java线程基础整理

多线程编程(Thread–多线程)

进程:正在进行的程序。

线程:指的是 进程当中某一个子任务或者子功能。

线程作用

  • 解决阻塞,提高程序的运行效率,充分利用硬件资源。

并发与并行

并发并行
并发是指两个或多个事件在同一时间间隔发生并行是指两个或者多个事件在同一时刻发生
并发是在同一实体上的多个事件并行是在不同实体上的多个事件
发是在多台处理器上同时处理多个任务并行是在一台处理器上“同时”处理多个任务

更多内容参考: https://www.jianshu.com/p/cbf9588b2afb

线程的两种创建方式

  • 继承Thread类 重写run()方法
package com.qianfeng.thread;
public class MyThread extends Thread{
	public void run(){
		for (int i = 0; i < 1000; i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
	}
}

  • 实现Runnable接口 实现run() 方法
package com.qianfeng.thread;
public class MyThread1 implements Runnable{	
	public void run() {
		for (char i = 'a'; i < 'z'; i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
	}
}

开启线程的方法

  • 继承方式的 通过调用自定义线程类的start()方法 开启一个线程
		MyThread t1=new MyThread();
		MyThread t2=new MyThread();
		t1.start();
		t2.start();
  • 实现方式的
	@Test
	public void test() {
		MyThread1 m1=new MyThread1();
		Thread t1=new Thread(m1);
		Thread t2=new Thread(m1);
		t1.start();
		t2.start();
	}
  • 注意
    • 启动线程类的方法是start() ,使线程进入就绪状态
    • 启动之后不是直接运行run方法 而是等待cpu分配时间片 当cpu分配了时间片之后 就会运行run方法。

线程的基本状态

线程基本运行状态
四个基本状态
  • 初始状态----->相当于是新创建了线程对象 Thread t1=new Thread(task);

  • 就绪状态------>调用了线程类的start方法。 t1.start();注:cpu还没有分配时间片

  • 运行状态------>cpu分配了时间片给当前线程 run方法被执行 。 run()…

  • 结束状态------>当线程 跑完run方法之后。

线程的常用方法

方法含义
Thread.currentThread().getName();获得当前线程的名称
MyTread t1=new Mythread();
t1.setName(“线程一”);
给某个线程设置名称
t1.isDaemon();判断当前线程是否是守护线程(类型于gc)
t1.getPriority();获得当前线程的优先级
t1.setPriority();设置当前线程的优先级
Thread.sleep(long millis)当前的线程 等待 一个毫秒值
t1.join();在当前的线程中 强势加入另一个线程
需要使加入进来的这线程 先运行完
我们的当前线程才可以继续运行。
Thread.currentThread().yield();当前线程让出cpu时间片 重新使当前线程处于就绪状态 ,
再次获得时间片时,继续执行礼让后的代码

线程的优先级

  • 默认的线程优先级是 5 main方法的优先级也是5
  • 一共有10 1-10 数字越大 优先级越高 约容易被cpu调度
  • 注意了!不绝对 就先调用优先级高的!有很小的概率也可能会调度到优先级的线程。

线程常用的方法案例–sleep,join,yield

  1. 当前的线程 等待 一个毫秒值

    Thread.sleep(long millis)  
    
  2. 在当前的线程中 强势加入另一个线程

    • 需要使加入进来的这线程 先运行完 我们的当前线程才可以继续运行。
    public static void main(String[] args) throws InterruptedException {
    		
    		MyThread t1=new MyThread();
    		t1.start();
    		
    		MyThread t2=new MyThread();
    		t2.start();
    		
    		for (int i = 0; i < 1000; i++) {
    			if(i==50){
    				t1.join();
    			}
    			System.out.println("main:"+i);
    		}
    	}
    
  3. 线程礼让方法 yield();

    • 当前线程让出cpu时间片 重新使当前线程处于就绪状态 ,再次获得时间片时,继续执行礼让后的代码
      • 相当于是所有的线程重新回到了竞争的状态 有可能当前线程继续获得到cpu时间片 也有可能是别的线程获得到cpu时间片。不确定性。
    @Test
    	public void testYield(){
    		
    		new Thread(new Runnable() {
    			@Override
    			public void run() {
    				Thread.currentThread().yield();
    				for (int i = 0; i < 100; i++) {
    					System.out.println(Thread.currentThread().getName()+":"+i);
    				}
    			}
    		}).start();
    		
    		
    		new Thread(new Runnable() {
    			@Override
    			public void run() {
    				Thread.currentThread().yield();
    				for (int i = 0; i < 100; i++) {
    					System.out.println(Thread.currentThread().getName()+":"+i);
    				}
    			}
    		}).start();
    	}
    

等待状态

在这里插入图片描述

阻塞状态

在这里插入图片描述

线程状态(6/7)

  • JDK5之后,把线程的状态归纳为6个状态,其中就绪状态和运行状态都属性 启动状态。

    • 线程状态:7种(jdk1.5之前)

      • 初始状态:new Thread();

      • 就绪状态:调用start方法

      • 运行状态:获取时间片

      • 有限期等待:调用Thread.sleep(毫秒值),等待结束之后,进入就绪状态,等待时间片的分配

      • 无限期等待:在当前线程中,调用其它线程的join方法,等其它线程执行完毕之后,当前线程进入就绪状态,等待时间片的分配

      • 阻塞状态:当线程去获取对象的锁对象时,如果此时对象上没有锁标记,则该线程就会阻塞。

      • 结束状态:线程执行完毕

  • 这些状态定义在枚举Thread.State中:

在这里插入图片描述

线程安全问题

  • 产生的原因:

    • 多个线程同时操作1个对象,这个对象就是临界资源(共享资源),必须保证一些连续的操作全部执行完毕,才能保证线程是安全的。
  • 多个密不可分的操作称为“原子操作”,即不可再分!

在上述的例子中,两个线程都可以访问的那个数组,就是临界资源,判断是否为null位和将值存入到指定的位置,就是一个“原子操作”

  • 如果没法保证“原子操作”,就无法保证线程安全,也就无法保证数据的完整性!

线程同步

临界资源: 多个线程同时操作1个对象,这个对象就是临界资源(共享资源)

方式1:同步代码块

//临界资源最好为类对象(例this),如果换成非引用对象(eg:Integer对象),有时会失效
synchronized ( 临界资源 ) {
    // 原子操作
}
  • 原理

在这里插入图片描述

方式2:同步方法

同步方法:临界资源是this
public synchronized void xxx() {
	// 原子操作
}
  • 何时释放锁:线程运行完毕之后,释放临界资源的锁。

线程死锁

  • 死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象

在这里插入图片描述

线程通信–wait() ,notify(), notifyAll()

  • wait() ,notify(), notifyAll()都源自Object–所有的类都有这三个方法

  • wait()

    • 会使当前线程进入等待状态
    • 并且会释放当前线程拥有得互斥锁标记
      • (如果wait()方法调用得时候 没有锁标记 则会抛出异常)
  • notify();

    • 会唤醒一个等待的线程
  • notifyAll();

    • 会唤醒所有的等待的线程
  • notify和notifyAll() 注意点

    • 被唤醒的线程 唤醒之后 处于一个就绪状态 也就是说 不会立马被执行
    • ​ 被唤醒的线程 并不会持有对象锁 而是有资格去竞争
    • ​ 执行了唤醒操作 当前的线程并不会立即交出锁标记

生产者和消费者案例–注意假唤醒–if(条件变量与量冲突)

  • 说明
    • 用while代替if防止出现假唤醒的情况
    • 解决了刚唤醒后出现超出临界情况的现象
      • 循环判断,只要被唤醒,再次执行循环条件,判断是否超出临界条件
      • 唤醒前因超出临界条件被挂起线程进入等待状态,唤醒后因其他线程抢占资源而依旧超出临界条件
    • 线程传值–用构造方法
    • 线程同步–临界资源最好为类对象(例this),如果换成其他对象(eg:Integer对象),有时会失效
package com.qianfeng.Thread;
import java.util.*;
public class KFC {//实体列--KFC
	private LinkedList<String> items=new LinkedList<String>();
    
	public synchronized void put() throws InterruptedException {
		
		while(items.size()>=5) {this.wait();
		}
		items.add("汉堡");
		System.out.println(Thread.currentThread().getName()+":生产一个汉堡,剩余:"+items.size());
		this.notifyAll();
	}
	public synchronized void get() throws InterruptedException {
		while(items.size()==0) {
			this.wait();
		}
		items.remove(0);
		System.out.println(Thread.currentThread().getName()+":消费一个汉堡剩余:"+items.size());	
		this.notifyAll();
	}
}

//生产者
package com.qianfeng.Thread;
public class Produce implements Runnable{
	private KFC kfc;
	public Produce(KFC kfc) {
		this.kfc = kfc;
	}
	@Override
	public void run() {
		while(true) {
			try {
				kfc.put();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

//消费者
package com.qianfeng.Thread;
public class Consume implements Runnable{
	private KFC kfc;
	public Consume(KFC kfc) {
		this.kfc = kfc;
	}
	@Override
	public void run() {
		while(true) {
			try {
				kfc.get();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}
//测试Demo
package com.qianfeng.Thread;
public class Demo {
	public static void main(String[] args) {
		KFC kfc=new KFC();//创建KFC类对象
		Produce p=new Produce(kfc);//创建生产者线程,赋予同一个类属性方法
		Consume c=new Consume(kfc);//创建消费者线程,赋予同一个类属性方法
		Thread t1=new Thread(p,"生产者1");
		Thread t2=new Thread(c,"消费者1");
		Thread t3=new Thread(p,"生产者2");
        Thread t4=new Thread(c,"消费者2");
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}

线程池

  • 背景(常见问题)
    • 线程是宝贵的内存资源、单个线程约占1MB空间,,过多的分配易造成内存溢出
    • 频繁的创建及销毁线程会证件虚拟机的回收频率,资源开销,降低程序性能
  • 概念
    • 线程容器,可设定线程的分配的数量上限
    • 将预先创建的线程对象存入池中,并重用线程池中 线程对象
    • 避免频繁的创建和销毁,提高程序的响应速度
  • 原理

在这里插入图片描述

线程池的使用方法

  • 创建线程池–Executor:线程池的顶级接口。

    • Executors.newFixedThreadPool(int n)//创建固定大小的线程池
    • Executors.newCachedThreadPool();//创建动态线程池案例
  • 创建案例

    // 创建线程的个数需要根据系统规模及内存作为参考
    ExecutorService pool1 = Executors.newFixedThreadPool(5);
    
    // 该方式创建的线程池中的线程数是动态维护的:不够则创建,空闲则销毁,不适用于访问量激增的程序。
    ExecutorService pool2 = Executors.newCachedThreadPool();
    
  • 线程池–创建任务,开启任务

    • 实现Runnable接口==》线程的任务 ==>无返回值,
    • 实现Callable接口==》线程的任务 ==>有返回值,返回值存放在Future<返回值类型>中
      • 实现的方法需抛出异常
      • Future 线程池对象.submit()方法的返回值类型
        • 可以通过get方法获得 返回值
        • get方法是同步方法
        • 会等真正产生返回值再去运行
    • ExecutorService 的对象es
      • es.submit(上述的实现接口);
  • 接口实现案例–匿名内部类

    ExecutorService es = Executors.newCachedThreadPool();//这是一个动态的线程池
    //Runnable
    es.submit(new Runnable() {//Rannable
        @Override
        public void run() {
            for (int i = 0; i < 1000; i++) {
                System.out.println(i);
            }
        }
    });
    //Callable
    Future <T> f =es.submit(new Callable<T>()  {//Callable
        @Override
        public T call()throws Exception {
            int sum=0;
            for (int i = 0; i < 1000; i++) 
                sum+=i;
            return sum;//决定了了泛型的类型--T :Integer
       }
    });
    //获取值
    //f.get()-主线程调用时会等线程真正产生返回值后再去运行
    

Lock ReentrantLock(重入锁)–类似于synchronize,但有不同

多线程使用synchronized来保持线程之间同步互斥,jdk1.5中加入了Lock对象也能实现同步效果
Re entra nt Lock(rɪ’entrənt)类的使用,ReentrantReadWriteLock类的使用

  • ReentrantLock 概述

    • java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。
  • ReentrantLock与synchonized的异同

    • 同:它拥有与 synchronized 相同的并发性和内存语义
    • 异: ReentrantLock 类实现了 Lock ,但是添加了类似轮询锁、定时锁等候和可中断锁等候的一些特性。
  • 优势

    • 这就为 Lock 的多种实现留下了空间,各种实现可能有不同的调度算法、性能特性或者锁定语义。
    • 它还提供了在激烈争用情况下更佳的性能。
    • 换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上
  • reentrant 锁意味着什么呢?

简单来说,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放。这模仿了 synchronized 的语义;如果线程进入由线程已经拥有的监控器保护的 synchronized 块,就允许线程继续进行,当线程退出第二个(或者后续) synchronized 块的时候,不释放锁,只有线程退出它进入的监控器保护的第一个 synchronized 块时,才释放锁。

ReentrantLock 的使用–lock与unlock,await,signal

  • lock与unlock

    • lock.lock()+lock.unlock();包起来的代码 == synchronized包起来的代码。
    • lock.unlock最好还是放在finally中,这样才能安全的解锁,不然lock住的线程会一直阻塞。
      • 同一套lock与unlock配对,如果其中lock执行完抛出异常,
      • 如果无法执行对应的unlock,lock住的线程会一致阻塞
  • await(),signal() 线程通信–类似于wait(),notify()/notifyAll()

    • 一个lock可以有多个condition(lock 可以拥有多个Condition)
    • 每个Condition可以独自拥有自己的await,signal。
  • 案例代码–与sychronized+wait()+notify()的一致的代码

    package cn.thread.lock.reentrant;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    class MyService {
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    public void serviceA() {
            try {
                //Thread.sleep(100);
                lock.lock();// lock是一把锁
                   code.....
                 condition.await();  //线程挂起,进入等待状态
                System.out.println(Thread.currentThread().getName() + "----Ai=" + i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally{
                lock.unlock();
             //condition.signal();在其他的方法中使用该段代码,唤醒被await挂起的servuceA线程
            }   
        
    }
    

公平锁与非公平锁–Lock lock [new ReentrantLock(true)]

  • 概述

    Lock锁分为公平锁和非公平锁

  • 公平锁表示线程获取锁的顺序是按照线程加锁的顺序来分配的,即先来先得FIFO。

    • private Lock lock = new ReentrantLock(true);公平锁。
  • 非公平锁就是一种获取锁的抢占机制,随机获得锁。

    • private Lock lock = new ReentrantLock(); 默认是非公平锁

待续~~~~~

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值