线程、线程池、corejava进阶面试

线程

一.基本概念

​ 1.进程:进程是指操作系统(OS)中,并发执行的任务

​ 并行原理:微观串行 宏观上并行

​ 2.线程:线程是在一个进程中,并发执行的多个程序逻辑。线程是进程执行的单位。

​ 一个进程中至少有一个线程,而这个线程被称为主线程,主线程是一个程序的入口,main就是由主线程来执行的

二.线程的组成

​ 1.组成部分

a).CPU: 程序真正的执行者  哪个线程获得cpu时间片 哪个线程才可以使用CPU 
b).数据:+堆
	栈空间独立:每个线程存放在栈中数据 是独享的线程间不可相互使用
	堆空间共享:每个线程存放在堆中数据 都被放置在同一个堆中 这些数据线程之间可以共享

​ 2.如何实现一个线程

开启线程的动作需要依附于主线程 所以创建线程开启线程的代码写在主函数中
a)先创建任务 再将任务放置在线程对象中
	先写任务类
    class 任务类类名 implements Runnable{
    	public void run(){
    		//具体执行的任务
   		}
    }
    先创建任务对象
    MyTask task = new MyTask();
	在创建线程对象并把任务交给该线程
	Thread th = new Thread(task);
	开启线程
	th.start();
b)创建一个自带任务的线程
	class 自带任务的线程名 extends Thread{
 		public void run(){
    		//具体执行的任务
   		}
	}
	//直接创建自带任务的线程对象
	MyThread th = new MyThread();
	开启线程
	th.start();
三.线程的状态

1.线程的七种状态

1.初始状态 New:当创建了一个线程时 会进入该状态
2.就绪状态 Ready:当初始状态的线程调用了start()方法时会进入该状态
3.运行状态 Running:当就绪状态的线程获得时间片时则进入该状态  如果该状态下时间片结束但线程没有执行完毕 则返回到 就绪状态 
4.终止状态 Terminated:当线程的run方法中的代码 或 主线程main方法的代码执行完毕时 该线程会进入终止状态

5.有限期等待:Timed Waiting 放弃当前时间片 进入等待状态 当被设置等待时间到时 则回到 就绪状态 重新可以获得时间片
6.无限期等待:Waiting 放弃当前时间片 进入等待状态 等到加队的线程执行完毕时 才会进入就绪状态
7.阻塞状态:Blocked 当一个线程执行同步代码块 或 同步方法时 如果没有拿到该临界资源互斥锁标记 则会进入到阻塞状态

2.常用改变线程状态的方法

sleep(long类型的毫秒数); 当一个线程在运行时执行到了Thread.sleep(long类型的毫秒数);时 当前这个线程会进入有限期等待状态 等待时间为参数的毫秒 在等待期间 线程不会获得时间片    sleep会放弃时间片而不放弃互斥锁标记

join(); 我们可以在一个线程1中 使用另一个线程2对象来调用join方法 线程则会加入到线程1中 直到线程2执行完毕 线程1才会结束等待状态 
四.线程安全问题

1.名词解释

a)原子操作:不可分割的多步操作,被视为一个整体,其执行的顺序和步骤不可被打破
b)临界资源:多线程并发时,共享的同一个对象。
c)线程同步:多线程并发时,为了保证临界资源的正确性,而不能破坏程序中的原子操作。
d)互斥锁标记:任何对象都具备的一个标记,可以分配给线程,只有获得该标记的线程进行操作,其他线程进入阻塞状态。
e)死锁:两个线程相互占用着对方需要使用的互斥锁标记 从而相互等待 程序停滞

2.如何保证线程的同步

synchronized 同步锁标记
a)synchronized修饰代码块  同步代码块
	synchronized(临界资源){
		原子操作
	}
	如果一个线程 想执行原子操作 则必须先获取这个临界资源的互斥锁标记
b)synchronized修饰方法 同步方法
	当一个方法中所有的代码都为原子操作时 我们可以将该方法声明为 同步方法
	public synchronized void add(String str){
			arr[size] = str;
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			size++;
	}
	相当于给当前对象进行加锁
	public void add(String str){
		synchronized(this){
            arr[size] = str;
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            size++;
		}
	}
同步代码块: 一般写在线程或线程任务的 的run方法中
同步方法:一般写在临界资源的方法中 一般一个同步代码块如果写在了临界资源中 则临界资源处用this

3.线程间的通信(了解)

线程间的通信 需要临界资源来调用相应的方法 因为互斥锁标记是临界资源所拥有的
a)wait();//当临界资源调用wait()方法时 当前线程 放弃时间片 放弃互斥锁标记 进入等待区(进入阻塞状态)
b)notify();//当临界资源调用notify()时会根据一定策略 唤醒等待区中的某个线程 该线程重新回归就绪状态
c)notifyAll();//当临界资源调用notifyAll()时 唤醒等待区中的所有线程  所有线程进入就绪状态

4.生产者与消费者模型

现实生活中的生产者与消费者模型:

2019-03-29_163608

代码中的生产者与消费者模型:

​ 生产者由一些线程构成 每一个线程代表一个生产者

​ 消费者也由一些线程构成 每一个线程代表一个消费者

​ 市场是一块数据的缓冲区: 数组 集合 栈…

2019-03-29_164756

​ 保证数据缓冲区的平衡:保证生产者在添加元素时不超过缓冲区容量 保证消费者在缓冲区容量为0时不能取出元素

​ 当生产者不能再生产时则进入等待 消费者消费一次后 则唤醒所有生产者

​ 当消费者不能在消费时则进入等待 生产者生产一次后 则唤醒所有消费者

public class TestCustomer {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//先有一个数据的缓冲区
		MyStack stack = new MyStack();
		Customer c1 = new Customer(stack);
		Customer c2 = new Customer(stack);
		Productor p1 = new Productor(stack);
		Productor p2 = new Productor(stack);
		c1.start();
		c2.start();
		p1.start();
		p2.start();
	}

}
class MyStack{
	String[] str = {"","","","",""};
	int index = -1;
	public synchronized void push(String s){
		while(index==str.length-1){//当不能再入栈时 则等待
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		index++;
		str[index] = s;
		System.out.println(s+"进栈了");
		System.out.print("此时栈的内容为");
		show();
		//当生产了一个之后 消费者就又可以进行消费了 所以要进行唤醒
		this.notifyAll();
	}
	public synchronized void pop(){
		while(index==-1){
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		String s = str[index];
		str[index]="";
		index--;
		System.out.println(s+"出栈了");
		System.out.print("此时栈的内容为");
		show();
		this.notifyAll();
	}
	public void show(){
		for(String s:str){
			System.out.print(s+" ");
		}
		System.out.println();
	}
}
class Customer extends Thread{
	MyStack st;
	public void run(){
		for(int i=0;;i++){
			st.pop();
		}
	}
	public Customer(MyStack st) {
		super();
		this.st = st;
	}
	public Customer() {
		super();
		// TODO Auto-generated constructor stub
	}
	
}
class Productor extends Thread{
	MyStack st;
	public void run(){
		for (char i = 'A'; ; i++) {
			st.push(i+"");
		}
	}
	public Productor(MyStack st) {
		super();
		this.st = st;
	}
	public Productor() {
		super();
		// TODO Auto-generated constructor stub
	}
	
}
五.线程池

​ 1.线程池:线程容器,可设定线程分配的数量上限,将预先创建线程对象存入池中,并重用

​ 线程池中的线程

​ 2.好处:减少创建和销毁线程的次数,每个工作线程可用于执行多个任务,只需将任务提交给线程,即可重复利用

​ 3.线程池的使用

java.util.concurrent 
a).线程池的基本接口:Executor
b).线程池接口:ExecutorService
c).Executors:线程池工具类 用来创建线程池对象
	newCachedThreadPool():当一个任务被提交到该线程池时 如果该线程池中没有线程或没有空闲线程 则创建一个线程来完成任务  如果该线程池中一个线程空闲时间超过60秒时则会被销毁  任务不会有等待的状态 
	newFixedThreadPool(参数):参数代表该线程池有几个线程 当所有线程均处于运行时 则新任务会进入等待 线程池不会自动创建新线程   任务会有等待的状态 

​ 4.Callable接口 JDK5加入,与Runnable接口类似,实现之后也代表一个线程任务

java.util.concurrent 
a)语法
class 类名 implements Callable<V>{
	public v call()throws 异常名{
		return 相应任务结果;
	}
}
V这个泛型 实际上就是 任务结果的类型

b)一个Callable类型的任务对象 可以提交给线程池
	线程池对象.submit(Callable类型对象);
	该方法 会返回一个Future类型的对象     Future类型的对象里包含了任务结果 意为任务的结果会在未来产生
	Future f = 线程池对象.submit(Callable类型对象);
	我们可以通过 Future类型的对象 调用get方法来获取任务的结果  但因为该结果还可能未产生 所以使用Future类型的对象get方法的线程 会进入阻塞/等待   直到该结果被返回时 状态才会恢复
	System.out.println("主线程使用未来对象获取结果 等待中.....");
    Integer result1 = f1.get();
    Integer result2 = f2.get();
    System.out.println("结果是"+(result1+result2));:
Callable<Integer> task1 = new Task1();
Callable<Integer> task2 = new Callable<Integer>() {

    @Override
    public Integer call() throws Exception {
        // TODO Auto-generated method stub
        int result = 0;
        for(int i=100;i<=200;i+=2){
            result += i;
        }
        Thread.sleep(5000);
        return result;
    }

};
ExecutorService pool = Executors.newCachedThreadPool();
//将任务放入线程池
Future<Integer> f1 = pool.submit(task1);
Future<Integer> f2 = pool.submit(task2);
//从未来对象中获取结果
System.out.println("主线程使用未来对象获取结果 等待中.....");
Integer result1 = f1.get();
Integer result2 = f2.get();
System.out.println("结果是"+(result1+result2));
六.锁

​ java.util.concurrent.locks

​ JDK 1.5所出现的

​ 1.lock锁

lock锁以自身为标记 代替了临界资源的互斥锁标记
synchronized(临界资源){

}
---->通过lock锁代替同步代码块
lock锁对象.lock();
中间为同步代码
lock锁对象.unlock();
tryLock();尝试获取lock锁 如果锁被占用则返回false 没被占用返回true


实现类:ReentrantLock
好处:synchronized相比逻辑控制更灵活、直观,但必须手动释放锁

​ 2.读写锁:ReadWriteLock

ReadWriteLock也以对象的形式存在 
先获创建读写锁对象 再分别获取读锁与写锁对象
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock read = lock.readLock();
ReentrantReadWriteLock.WriteLock write = lock.writeLock();
与lock的语法相近  读锁给读的操作加锁  写锁给写的操作加锁

作用:
	多个线程可以同时执行读锁中代码
	当有一个线程在使用写锁中的代码时  所有需要使用读锁与写锁中代码的线程 都会进入阻塞

读操作写操作
读锁不阻塞阻塞
写锁阻塞阻塞

读与读同行不会出错

读与写同行可能出错

写与写同行可能出错

​ 3.使用lock锁时的线程间通信

使用lock进行线程间通信时 需要使用Condition类型对象----synchronized的等待队列
Lock lock = new ReentrantLock();
Condition empty = lock.newCondition();//消费者队列
Condition full = lock.newCondition();//生产者队列

Condition类型对象.await(); 让当期啊线程进入到该队列中
Condition类型对象.singalAll();唤醒当前队列中所有的线程

生产者与消费者模型的替换见代码
七.线程安全的集合
a)CopyOnWriteArrayList:所在包java.util.concurrent;读操作远远大于写操作时,效率高。在写时会先创建副本将写操作进行完毕时 再替换原容器 保证读的正确性 但写的效率很低
b)ConcurrentHashMap:所在包java.util.concurrent;提高了HashMap的并发效率 使用了分级锁 将原本的hash表分成了16段    一个hash表时 有一个线程操作任意一个位置 都会导致hash被锁     但分成了16段加锁时 一个线程访问一部分时 其他线程可以访问其他部分
c)Queue 队列:先入先出
	add(E e);将指定元素添加到此列表的结尾,当达到容量上限时,不自动扩容。
	offer(E e);将指定元素添加到此列表的末尾(最后一个元素)
	E poll();获得第一个元素并移除
	LinkedList
	ConcurrentLinkedQueue:线程安全 没有锁 使用CAS无锁算法完成线程安全
	BlockingQueue接口下:  阻塞队列 
		ArrayBlockingQueue:以数组实现 
		LinkedBlockingQueue:以链表实现
		二者在底层都使用了生产者与消费者模型 用来保证数据的安全性 
		
d)如何将一个线程不安全集合转换为线程安全的集合
	Collections下的这三个静态方法
	list集合:
	public static <T> List<T> synchronizedList(List<T> list)
	set集合:
	public static <T> Set<T> synchronizedSet(Set<T> s)
	map集合:
	public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)
        
    ArrayList<String> list = new ArrayList<String>();
    HashSet<String> set = new HashSet<String>();
    HashMap<String,String> map = new HashMap<String,String>();
    List<String> list2 = Collections.synchronizedList(list);//得到了线程安全的List集合
    Set<String> set2 = Collections.synchronizedSet(set);//得到了线程安全的Set集合
    Map<String, String> map2 = Collections.synchronizedMap(map);//得到了线程安全的Map集合

总结:

Colloction
	List
		线程不安全 效率高
		ArrayList
		LinkedList
		线程安全 效率低
		Vector
		CopyOnWriteArrayList 所在包java.util.concurrent;读操作远远大于写操作时,效率高。在写时会先创建副本将写操作进行完毕时 再替换原容器 保证读的正确性 但写的效率很低
	Set
		线程都不安全 效率高
		HashSet
		LinkedHashSet
		SortedSet接口
			TreeSet
	Queue
		LinkedList
		ConcurrentLinkedQueue 线程安全 没有锁 使用CAS无锁算法完成线程安全
		BlockingQueue 阻塞队列接口下
            ArrayBlockingQueue:以数组实现 
            LinkedBlockingQueue:以链表实现
            二者在底层都使用了生产者与消费者模型 用来保证数据的安全性 
Map
	线程不安全 效率高
	HashMap
	LinkedHashMap
	线程安全 效率低
	Hashtable
		Properties
	SortedMap接口
		TreeMap
	ConcurrentHashMap:所在包java.util.concurrent;提高了HashMap的并发效率 使用了分级锁 将原本的hash表分成了16段    一个hash表时 有一个线程操作任意一个位置 都会导致hash被锁     但分成了16段加锁时 一个线程访问一部分时 其他线程可以访问其他部分
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值