线程冠军宝座

线程和进程

进程:操作系统中资源分配的基本单位.
线程:是在进程中任务调度和执行的基本单位.

线程的创建

继承Thread(重写run方法,启动线程)
实现Runnable

线程的辅助工具

 线程池Executors :      1.    newFixedThreadPools()创建最大线程数为5
                       2.newCacheThreadpool()足够多的线程 有空闲任务就会创建
                       3.newSingleThreadExecutor()只创建一个线程
                    线程池pool配合 execute()将数据丢进线程池 没有返回值
 Callable/future:  future 凭证小票
     Future与submit()方法进行配和使用,也是将数据丢进线程池返回一个凭证小票.
     如果是实现Runnable则返回的小票没有结果,执行future.get()方法只是进行线程暂停进行下一项,
     如果是实现了callable 则有返回值 并且可以获取返回值.

Callable与Runnable的区别:Runnable 不能抛异常,不能有返回值
Callable是在Runnable上可以抛出异常,可以有返回值

线程的状态

线程的5种状态: 新建,可执行,执行,阻塞,销毁
线程冠军宝座_第1张图片
当新建的线程执行了start()方法的时候 进入可运行线程池中,变的可运行,等待获取CPU的使用权,执行状态就是线程获取了CPU的使用权.阻塞状态就是线程因为某种原因放弃了CPU的使用权,暂时停止了运行,直到线程重新就绪,才可以继续执行 销毁状态就是 正常结束 或者 出现异常 return
阻塞的情况有三种:
1. 等待阻塞:运行的线程执行了 wait()方法 进入了等待池,只有等到 notify() notifyAll()才重新获取锁 运行
2.同步阻塞:运行的线程在获取对象的锁的时候,若该对象的同步锁被别的线程占用,会将该线程放进锁池中
3.其他的阻塞:运行的线程执行了sleep()或者join()方法或者发出了IO请求时

线程的方法

 Thread.currentThread() 获取当前线程的实例
 Thread.sleep()让线程暂停
 Thread.yield() 放弃CPU时间
       GetName() setName() 获取线程的名字和设置
       Interrupt() 打断线程的执行
       Join() 一个线程等待另一个线程结束
       SetDaemon()后台线程

线程的同步

     Synchronized的三种方式:     
       1.   synchronized (对象){}   共享代码
       2.  Synchronized void f(){}   抢当前实例的锁
       3.  Static synchronized void f(){} 抢当前类的锁

乐观锁与悲观锁

     Synchronized 与lock

线程冠军宝座_第2张图片

Synchronized (悲观锁)
Lock(乐观锁)
|=>>ReentrantLock 重入锁
|=>>>ReentrantReadWriteLock 重入读写锁
方法: Lock()
unLock()

Volatile与ThreadLocal

JVM 指令重排序    instance=new singleton04() ;
           系统底层会执行的指令

1)分配空间
2)对象属性的初始化
3)执行构造方法
4)将对象赋值给instance变量
JVM 默认会将性能优化的顺序为 1 4 2 3 也就是多线程访问时 对象的属性可能还没有初始化 出现 出现高并发时线程不安全的问题
解决方法有两种 1.关键字 volatile 的作用 :
1.禁止指令的重排序 (JVM 对java 程序进行编译时会有指令重排)
2.保证内存可见性(多CPU 一个线程对共享变量的修改,另一个线程是可见的)
3.但不保证原子性(synchronized 可以保证原子性)
线程冠军宝座_第3张图片
两种说法:一种是 volatile 不使用 cache 直接修改了 内存中的数据
另一种说法是volatile可以实现两个线程之间的检测功能 cpu2会检测CPU1 如果它修改数据的时候

2.ThreadLocal作用: 提供了一种线程绑定机制 能够将某个对象绑定到当前线程也可以从当前线程获取某一对象,借助此对象可以实现线程内部的单例
应用场景:线程内部单例 取消线程共享
例如存在的问题:SimpleDateFarmat 是一个线程不安全 放在方法外实现共享 线程不安全 放在方法中 会出现内存问题 ,每次调用时都会new 一个新的 对象
在阿里的开发手册中写到 使用时需要加锁 加锁会影响性能 或使用 DateTimeFormetter代替 SimpleDateFarmat 会使用ThreadLocal 泛型 的方式
Private static ThreadLocal td =new ThreadLocal (){
@override
Initiatvalue(){
return new SimpleDateFarmat (“yyyy—”)
}
};
常用方法:get /set
应用的原理:
线程冠军宝座_第4张图片
ThreadLocal的原理 :是将当前的线程作为key值 将对象作为value值 实现线程的绑定 (只能存一个Entry) 并且 Entry 还是一个弱引用 具体的实现如下:
线程冠军宝座_第5张图片

Sleep与wait的区别

Sleep()是线程(Thread)类的方法,导致此线程的暂停执行指定时间,把执行机会给其他的线程,但是监控状态依然保持,到时候会自动回复.调用sleep()不会释放对象锁.
Wait()是Object类的方法,对此对象调用的wait()方法导致本线程放弃了对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify()方法(或notifyAll())后线程才进入对象锁定池准备获得对象锁进入运行状态.

死锁

  • 什么是死锁

  • 产生死锁的四个必要条件

  • 解决死锁的办法

  • 如何避免死锁

  • 什么是线程死锁?
    一个对象中多把锁,多线程并发时多线程同时被阻塞,它们中有一个或多个都在等待某一个资源的释放,因为线程无限期的阻塞,因此程序不可能终止
    如图所示:

线程冠军宝座_第6张图片
下面通过一个例子来说明一下线程死锁(模拟上面的死锁的情况)

/**
 * @author renjiaxing
 *测试死锁的情况
 */
public class TestThreadDeadLock {
       private static Object resource1=new Object();
       private static Object resource2=new Object();
       public static void main(String[] args) {
		       new Thread(()->{
		    	       synchronized(resource1){
		    	    	   System.out.println(Thread.currentThread()+"我突然想喝娃哈哈");
		    	           try {
							Thread.sleep(1000);
						} catch (Exception e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
		    	        System.out.println(Thread.currentThread()+"等待你给我买娃哈哈的时间");
		    	        synchronized (resource2) {
							System.out.println(Thread.currentThread()+"其实营养快线也不是不可以");
						}
		    	       }
		       },"线程A").start();
		       new Thread(()->{
	    	       synchronized(resource2){
	    	    	   System.out.println(Thread.currentThread()+"其实营养快线也不是不可以");
	    	           try {
						Thread.sleep(1000);
					} catch (Exception e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
	    	        System.out.println(Thread.currentThread()+"等待你给我买娃哈哈的时间");
	    	        synchronized (resource1) {
						System.out.println(Thread.currentThread()+"我一定要最少喝到一样,哼!!!");
					}
	    	       }
	       },"线程B").start(); 
	}
}

输出的结果:
线程冠军宝座_第7张图片
线程A获得资源1的锁,线程B获得资源2的锁(线程休眠是为了让两个线程都可以获取到一个锁).当休眠结束后,都开始获取对方的资源,却都被占用,陷入等待模式,产生了死锁
如何避免死锁?
1.破坏互斥的条件:这个条件我们没法达到,因为我们使用锁,就是想让它们互斥(需要互斥访问)
2.破坏循环等待的条件:按某一个固定的顺序去执行申请资源
3.占用资源的线程进一步申请其他的资源时,如果申请不到,可以主动的释放自己的资源
4.一次性申请所有的资源
我们将线程B修改为:

new Thread(()->{
	    	       synchronized(resource1){
	    	    	   System.out.println(Thread.currentThread()+"我突然想喝娃哈哈");
	    	           try {
						Thread.sleep(1000);
					} catch (Exception e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
	    	        System.out.println(Thread.currentThread()+"等待你给我买娃哈哈的时间");
	    	        synchronized (resource2) {
						System.out.println(Thread.currentThread()+"其实营养快线也不是不可以");
					}
	    	       }
	       },"线程B").start();    

输出结果为:
线程冠军宝座_第8张图片
大家可以看到程序已经执行完了.
再举一个关于项目中死锁的情况:

/**
 * @author renjiaxing
 *测试工作中的死锁的案例
 */
public class TestLock {
	  //假如这个方法上有一个事物注解
      public void updateBusiness(List<Long> goodIds){
    	  if (goodIds==null||goodIds.isEmpty()) {
			   return;
		}
    	  IGoodsDao goodDao =null;
    	  for(Long gId: goodIds){
    		    goodDao.updatedateGoods(gId,1) ;
    	  }
      }
}
interface IGoodsDao{
	void updatedateGoods(Long gId, int nums);
}

这是一个商品入库的操作,如果这是有一个客户购买id为1和3的商品,而另一位客户需要购买3和1 的商品 此时就会同时调用updateBusiness方法:
线程冠军宝座_第9张图片
解决的方法:
在入库是将商品id进行排序,使得按顺序去执行如一下代码:

 //假如这个方法上有一个事物注解
      public void updateBusiness(List<Long> goodIds){
    	  if (goodIds==null||goodIds.isEmpty()) {
			   return;
		}
    	  IGoodsDao goodDao =null;
    	  Collections.sort(goodIds);
    	  for(Long gId: goodIds){
    		    goodDao.updatedateGoods(gId,1) ;
    	  }
      }
} 

线程冠军宝座_第10张图片
这样就解决了死锁的情况.

你可能感兴趣的:(java)