多线程


title: 多线程
cover:

多线程

    • synchronized关键字
  • synchronized修饰静态方法以及同步代码块的synchronized(类.class)用法锁的是类,线程想要执行对应的同步代码,需要获得类锁
  • synchronized修饰成员方法,线程获取的是当前调用该方法的对象实例的对象锁
    • volatile关键字
  • volatile关键字是用来保证有序性和关键性。
  • volatile变量规则:
    • 有序性:对一个变量的写操作先行发生于后面对这个变量的读操作;有序性实现的是通过插入内存屏障来保证的
    • 可见性:java内存模型分为(主内存和工作内存)。比如线程A从主存把变量读到了自己的工作内存中,做了加1的操作,但是没有及时的将变量的值刷新,线程B读到的依旧是变量之前的值,加了volatile关键字的代码生成的汇编代码,会多出一个Lock前缀指令,Lock指令对Intel平台的cpu早期是锁总线,代价比较高,后面题出了缓存一致协议MESI,保证数据之间不一致问题
    • Synchronized和Lock
  • synchronized是java的关键字,用来修饰方法和一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。JDK1.5以后引入了自旋锁,锁粗化,轻量级锁,偏向锁来优化关键字的性能。

    • Lock是一个接口,synchronized是Java中的关键字,synchronized是内置的语言实现;synchronized在发生异常时,会自动释放线程占有的锁,因而不会导致死锁现象发生;而Lock在发生异常时,若果没有主动通过unlock()方法去释放锁,很有可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;

    • Lock可以让等待的锁的线程响应中断,而synchronized不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;通过Lock可以知道有没有成功获取锁,而synchronized却无法办到

  1. synchronized修饰方法和代码块的区别

修饰方法:一次只能有一个线程进入这个方法

修饰代码块:代码块只能写在方法内部,可以有多个线程进入方法,但是只能有一个线程执行这个代码块中的代码

进程

  1. 概念: 操作系统中并发执行的多个任务(只有正在运行的程序,才能称为进程)

线程

  1. 概念: 进程中的多个程序逻辑,则称为线程,也称为轻量级进程

  2. 特点: 线程是进程的基本组成单元

    • java中只有多线程,没有多进程
  3. 线程的组成

    • CPU时间片 由OS负责调度分配
    • 数据
      • 堆空间 堆空间由多个线程共享
      • 栈空间 每个线程都有自己独立的栈空间

创建线程

  1. 实现Runnable接口,实现run()方法
  2. 继承Thread类,覆盖run()方法

线程的基本状态

  1. 图解

image-20200826113541325

  1. 等待状态
    • sleep(),让当前线程进入有限期等待状态,线程并没有放弃锁标记,进入就绪状态
    • wait(),让当前线程进入无限期等待状态,线程放弃锁标记,等待被其他线程唤醒
    • t.join(),让t线程加入当前线程,优先执行t线程,在执行当前线程,当t线程执行完后,再执行当前线程

线程池

  1. Executor 线程池的总接口

    • 所有线程池类与接口都在java.util.concurrent包下
  2. ExecutorService Executor的子接口

    • 通过submit()提交任务
    • 通过shutdown(),关闭线程池
  3. Executors 线程池工厂,提供线程池对象

    • newFixedThreadPool(int n),创建线程池,里面有固定数量的线程吃对象,需要自己来传入参数
    • newCachedThreadPool(),创建线程池,池中数量没有上线,需要几个就创建几个

Callable

  1. 类似于Runnable的接口,Callable实现类对象类似于Runnable实现类对象,是一个任务对象

    • V call(); 类似于Runnable中的void run();
  2. Future接口:获取线程的返回值或异常

    • V get(): 获取返回值 若线程尚未结束,获取不到值;则等待线程执行结束,再获取值

      run()弊端:没有返回值、不能抛异常
      
      call()优点:有返回值,可以抛任意异常
      

线程安全问题

  1. 线程不安全:当多个线程访问同一对象(临界资源),可能破坏了不可分割操作(原子操作),

    导致数据不一致

  2. 临界资源:多个线程访问的同一对象

  3. 原子操作: 不可分割的操作,多个操作要么都执行成功,要么都执行失败

线程同步

  1. 同步代码块,只能写在方法内部

    synchronized(Object o){
        //原子操作
    }
    对o对象加锁的同步代码块
    
    • Java中任何一个Object对象,都有一个互斥锁标记,用来分配给线程:只有具有锁标记的线程才能进入同步代码块中执行代码,代码执行完,线程会释放锁标记;没有拿到锁标记的线程,进入阻塞状态,直到拿到锁标记,在进入就绪状态,等待分配时间片执行同步代码块中的代码。
  2. 同步方法

    访问修饰符 synchronized  返回值类型 方法名(形参列表){
        //原子操作
    }
    等价于
    访问修饰符  返回值类型 方法名(形参列表){
        synchronized(this){
       		 //原子操作  
    	}
    }    
    
    • 同步方法对当前对象加锁,只有拿到当前对象锁标记的线程,才能执行同步方法
  3. 死锁

    • o.wait(),在当前线程调用o的wait方法,会使当前线程释放锁标记,自身线程进入o的等待队列,进入阻塞状态,等待被其他线程唤醒

    • o.notify()/o.notifyAll(),从o的等待队列中随机唤醒一个线程/所有线程,对此线程没有任何影响

    • 必须用在对o枷锁的同步代码块中

      面试问题:wait()与sleep()的区别
      
      wait():  无限期阻塞   会释放CPU时间片及锁标记,必须等待唤醒,才能再次争抢锁标记及CPU时					 间片
      
      sleep(): 有限期等待   只会释放CPU时间片,不会释放锁标记
      
    • 如何避免临界资源数据不一致?

      • 尽量避免使用临界资源对象,多使用局部变量(局部变量存在栈中,栈空间是每个线程独立的)

      • 多使用线程安全并且可以高并发的对象

      • 使用synchronized

线程安全的集合类以及队列

  1. ConcurrentHashMap 线程安全并且可以高并发的集合

    1. JDK1.7 分段锁(16), 控制锁的粒度,将Map集合分成16段,每段都加一个不同锁
    2. JDK1.8 CAS算法(无锁算法)+synchronized
  2. CopyOnWriteArrayList 应用于读操作远远多于写操作的场景下;牺牲写操作的效率,提升读操作的效率

    • 读操作:获取集合中的元素 不加锁

    • 写操作:改变集合中的元素 加锁,每次写操作都会复制出一个新集合,在新集合的基础上进行写操作

  3. CopyOnWriteArraySet 同上

  4. Queue 队列,FIFO(先进先出)

    • 实现类
      1. ConcurrentLinkedQueue
    • Queue的常用方法
      1. 添加
        1. add(E e) 当队列满了,报异常
        2. offer(E e) 当队列满了,不报异常,只返回false
      2. 获取
        1. element() 获取头元素,当获取不到,报异常
        2. peek() 获取头元素,当获取不到,返回null
      3. 删除
        1. remove() 删除并返回头元素,当队列为空,没有头元素,报异常
        2. poll() 删除并返回头元素,当队列为空,没有头元素,不报异常,返回null
    • BlockingQueue Queue的子接口 阻塞队列 当线程从队列中获取元素时,若队列为空,没有元素,会使获取元素的线程进入阻塞状态
      1. ArrayBlockingQueue 有界队列 当线程往队列中添加元素,若队列满了,添加不进去,会使添加元素的线程进入阻塞状态
      2. LinkedBlockingQueue 无界队列

image-20200828081437393

线程安全之生产者消费者问题

  1. 生产和消费不能同时进行,放入或取出,会有一个信号发送给对方

    //生产者消费者问题
    public class TestProductConsumer {
    	public static void main(String[] args) {
    		MyStack stack = new MyStack();
            //创建线程进行入栈操作
    		Runnable r1 = new Runnable() {
    			public void run() {
    				for(char c='A';c<='Z';c++ ) {
    					stack.push(c+"");
    				}
    			}
    		};
            //创建线程进行出栈操作
    		Runnable r2 = new Runnable() {
    			public void run() {
    				for(int i = 0;i<26;i++) {
    					stack.pop();
    				}
    			}
    		};
            //启动线程
    		new Thread(r1).start();
    		new Thread(r1).start();
    		new Thread(r2).start();
    		new Thread(r2).start();
    	}
    }
    
    //数组实现栈
    class MyStack{
    	private String[] str = {"","","","","",""};
    	private int index;
    	//入栈
    	public synchronized void push(String s) {
            //当数组满的时候,让入栈的操作等待
            //当有多个入栈线程时,采用while判断,不能用if
    		while(index == str.length) {
    			try {
    				this.wait();
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    		System.out.print(s+"  pushed  ");
    		str[index++] = s;
    		print();
            //当数组中有元素的时候,唤醒出栈操作
    		this.notifyAll();
    		
    	}
    	//出栈
    	public synchronized void pop() {
            //数组为空的时候,出栈的操作等待
            //采用多个出栈线程时,不能用if
    		while(index == 0) {
    			try {
    				this.wait();
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    		index--;
    		System.out.print(str[index]+"  poped  ");
    		str[index] = "";
    		print();
            //数组中无元素的时候,唤醒入栈操作
    		this.notifyAll();
    	}
    	//打印栈中数据
    	public void print() {
    		System.out.println(Arrays.toString(str));
    	}
    }
    

ndex–;
System.out.print(str[index]+" poped ");
str[index] = “”;
print();
//数组中无元素的时候,唤醒入栈操作
this.notifyAll();
}
//打印栈中数据
public void print() {
System.out.println(Arrays.toString(str));
}
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值