JAVA线程相关总结

来源:互联网 发布:电话座机迁走了没网络 编辑:程序博客网 时间:2024/05/29 09:59

目录:

1.创建线程的3种方式。 
2.什么是线程安全。 
3.Runnable接口和Callable接口的区别。 
4.wait方法和sleep方法的区别。 
5.synchronized、Lock、ReentrantLock、ReadWriteLock。 
6.介绍下CAS(无锁技术)。 
7.什么是ThreadLocal。 
8.创建线程池的4种方式。 
9.ThreadPoolExecutor的内部工作原理。 
正文:

一.创建线程的3种方式。
1.继承Thread类创建线程类
(1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。
(2)创建Thread子类的实例,即创建了线程对象。
(3)调用线程对象的start()方法来启动该线程。
  1. package com.thread;  
  2.   
  3. public class FirstThreadTest extends Thread{  
  4.     int i = 0;  
  5.     //重写run方法,run方法的方法体就是现场执行体  
  6.     public void run()  
  7.     {  
  8.         for(;i<100;i++){  
  9.         System.out.println(getName()+"  "+i);  
  10.           
  11.         }  
  12.     }  
  13.     public static void main(String[] args)  
  14.     {  
  15.         for(int i = 0;i< 100;i++)  
  16.         {  
  17.             System.out.println(Thread.currentThread().getName()+"  : "+i);  
  18.             if(i==20)  
  19.             {  
  20.                 new FirstThreadTest().start();  
  21.                 new FirstThreadTest().start();  
  22.             }  
  23.         }  
  24.     }  
  25.   
  26. }  

上述代码中Thread.currentThread()方法返回当前正在执行的线程对象。GetName()方法返回调用该方法的线程的名字。

2.通过Runnable接口创建线程类
(1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
(2)创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
(3)调用线程对象的start()方法来启动该线程。
示例代码为:
  1. package com.thread;  
  2.   
  3. public class RunnableThreadTest implements Runnable  
  4. {  
  5.   
  6.     private int i;  
  7.     public void run()  
  8.     {  
  9.         for(i = 0;i <100;i++)  
  10.         {  
  11.             System.out.println(Thread.currentThread().getName()+" "+i);  
  12.         }  
  13.     }  
  14.     public static void main(String[] args)  
  15.     {  
  16.         for(int i = 0;i < 100;i++)  
  17.         {  
  18.             System.out.println(Thread.currentThread().getName()+" "+i);  
  19.             if(i==20)  
  20.             {  
  21.                 RunnableThreadTest rtt = new RunnableThreadTest();  
  22.                 new Thread(rtt,"新线程1").start();  
  23.                 new Thread(rtt,"新线程2").start();  
  24.             }  
  25.         }  
  26.   
  27.     }  
  28.   
  29. }  
3.通过Callable和Future创建线程
(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
(3)使用FutureTask对象作为Thread对象的target创建并启动新线程。
(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
实例代码:
  1. package com.thread;  
  2.   
  3. import java.util.concurrent.Callable;  
  4. import java.util.concurrent.ExecutionException;  
  5. import java.util.concurrent.FutureTask;  
  6.   
  7. public class CallableThreadTest implements Callable<Integer>  
  8. {  
  9.   
  10.     public static void main(String[] args)  
  11.     {  
  12.         CallableThreadTest ctt = new CallableThreadTest();  
  13.         FutureTask<Integer> ft = new FutureTask<>(ctt);  
  14.         for(int i = 0;i < 100;i++)  
  15.         {  
  16.             System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);  
  17.             if(i==20)  
  18.             {  
  19.                 new Thread(ft,"有返回值的线程").start();  
  20.             }  
  21.         }  
  22.         try  
  23.         {  
  24.             System.out.println("子线程的返回值:"+ft.get());  
  25.         } catch (InterruptedException e)  
  26.         {  
  27.             e.printStackTrace();  
  28.         } catch (ExecutionException e)  
  29.         {  
  30.             e.printStackTrace();  
  31.         }  
  32.   
  33.     }  
  34.   
  35.     @Override  
  36.     public Integer call() throws Exception  
  37.     {  
  38.         int i = 0;  
  39.         for(;i<100;i++)  
  40.         {  
  41.             System.out.println(Thread.currentThread().getName()+" "+i);  
  42.         }  
  43.         return i;  
  44.     }  
  45.   
  46. }  


二.线程安全。
线程安全就是说多线程访问同一代码,不会产生不确定的结果。编写线程安全的代码是低依靠线程同步。
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

三.wait方法和sleep方法的区别。
sleep 是线程类(Thread)的方法,导致此线程暂停执行指定时间,给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep 不会释放对象锁。
wait 是Object 类的方法,对此对象调用wait 方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify 方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。
1、这两个方法来自不同的类分别是Thread和Object
2、最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
3、wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用(使用范围)
  1. synchronized(x){
  2.   x.notify()
  3.   //或者wait()
  4.   }
4、sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常。

四.同步中的四种锁synchronized、StampedLock戳锁、ReentrantLock、ReadWriteLock。
synchronized:(1)修饰普通方法时,对对象加锁。
(2)修饰静态方法时,对类加锁。
(3)修饰代码块时,实质是对对象加锁。


ReentrantLock:(1)实现了Lock接口,内部有三个内部类,Sync、NonfairSync、FairSync。
方法名称描述lock获取锁,如果锁无法获取,那么当前的线程就变为不可被调度,直到锁被获取到lockInterruptibly获取锁,除非当前线程被中断。如果获取到了锁,那么立即返回,如果获取不到,那么当前线程变得不可被调度,一直休眠直到下面两件事情发生:
1、当前线程获取到了锁
2、其他的线程中断了当前的线程tryLock如果调用的时候能够获取锁,那么就获取锁并且返回true,如果当前的锁无法获取到,那么这个方法会立刻返回falsetryLcok(long time,TimeUnit unit)在指定时间内尝试获取锁如果可以获取锁,那么获取锁并且返回true,如果当前的锁无法获取,那么当前的线程变得不可被调度,直到下面三件事之一发生:
1、当前线程获取到了锁
2、当前线程被其他线程中断
3、指定的等待时间到了
 unlock释放当前线程占用的锁newCondition返回一个与当前的锁关联的条件变量。在使用这个条件变量之前,当前线程必须占用锁。调用Condition的await方法,会在等待之前原子地释放锁,并在等待被唤醒后原子的获取锁


ReadWriteLock:ReadWriteLock接口的核心方法是readLock(),writeLock()。实现了并发读、互斥写。但读锁会阻塞写锁,是悲观锁的策略。

JDK1.8下,如图ReentrantReadWriteLock有5个静态方法:
  • Sync:继承于经典的AbstractQueuedSynchronizer(传说中的AQS),是一个抽象类,包含2个抽象方法readerShouldBlock();writerShouldBlock()
  • FairSync和NonfairSync:继承于Sync,分别实现了公平/非公平锁。
  • ReadLock和WriteLock:都是Lock实现类,分别实现了读、写锁。ReadLock是共享的,而WriteLock是独占的。于是Sync类覆盖了AQS中独占和共享模式的抽象方法(tryAcquire/tryAcquireShared等),用同一个等待队列来维护读/写排队线程,而用一个32位int state标示和记录读/写锁重入次数--Doug Lea把状态的高16位用作读锁,记录所有读锁重入次数之和,低16位用作写锁,记录写锁重入次数。所以无论是读锁还是写锁最多只能被持有65535次。
性能和建议:适用于读多写少的情况。性能较高。
  • 公平性
  1. 非公平锁(默认),为了防止写线程饿死,规则是:当等待队列头部结点是独占模式(即要获取写锁的线程)时,只有获取独占锁线程可以抢占,而试图获取共享锁的线程必须进入队列阻塞;当队列头部结点是共享模式(即要获取读锁的线程)时,试图获取独占和共享锁的线程都可以抢占。
  2. 公平锁,利用AQS的等待队列,线程按照FIFO的顺序获取锁,因此不存在写线程一直等待的问题。
  • 重入性:读写锁均是可重入的,读/写锁重入次数保存在了32位int state的高/低16位中。而单个读线程的重入次数,则记录在ThreadLocalHoldCounter类型的readHolds里。
  • 锁降级:写线程获取写入锁后可以获取读取锁,然后释放写入锁,这样就从写入锁变成了读取锁,从而实现锁降级。
  • 锁获取中断:读取锁和写入锁都支持获取锁期间被中断。
  • 条件变量:写锁提供了条件变量(Condition)的支持,这个和独占锁ReentrantLock一致,但是读锁却不允许,调用readLock().newCondition()会抛出UnsupportedOperationException异常。


StampedLock:StampedLock控制锁有三种模式(排它写,悲观读,乐观读),一个StampedLock状态是由版本和模式两个部分组成,锁获取方法返回一个数字作为票据stamp,它用相应的锁状态表示并控制访问。
总结:
4种锁,最稳定是内置synchronized锁(并不是完全被替代),当并发量大且读远大于写的情况下最快的的是StampedLock锁(乐观读。近似于无锁)。建议大家采用。
四.CAS(无锁操作)
CAS原理:
CAS原语有三个参数,内存地址,期望值,新值。如果内存地址的值==期望值,表示该值未修改,此时可以修改成新值。否则表示修改失败,返回false,由用户决定后续操作。
缺点:
使用CAS会造成ABA问题
ABA 问题
thread1意图对val=1进行操作变成2,cas(*val,1,2)。
thread1先读取val=1;thread1被抢占,让thread2运行。
thread2 修改val=3,又修改回1。
thread1继续执行,发现期望值与“原值”(其实被修改过了)相同,完成CAS操作。

解决方案
ABAʹ:添加额外的标记用来指示是否被修改。

CAS 
实际上虚拟机采用CAS配合上失败重试的方式保证更新操作的原子性,原理和上面讲的一样。
TLAB 
如果使用CAS其实对性能还是会有影响的,所以JVM又提出了一种更高级的优化策略:每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲区(TLAB),线程内部需要分配内存时直接在TLAB上分配就行,避免了线程冲突。只有当缓冲区的内存用光需要重新分配内存的时候才会进行CAS操作分配更大的内存空间。 
虚拟机是否使用TLAB,可以通过-XX:+/-UseTLAB参数来进行配置(jdk5及以后的版本默认是启用TLAB的).
五.什么是TreadLocal
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
  从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。ThreadLocal类接口很简单,只有4个方法,我们先来了解一下:
  • void set(Object value)设置当前线程的线程局部变量的值。
  • public Object get()该方法返回当前线程所对应的线程局部变量。
  • public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
  • protected Object initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。
六.创建线程池的四种方式。
Java通过Executors提供四种线程池,分别为:
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();  
 ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);


ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5); 
表示延迟3秒执行。
定期执行示例代码如下:
Java代码  
  1. package test;  
  2. import java.util.concurrent.Executors;  
  3. import java.util.concurrent.ScheduledExecutorService;  
  4. import java.util.concurrent.TimeUnit;  
  5. public class ThreadPoolExecutorTest {  
  6.  public static void main(String[] args) {  
  7.   ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);  
  8.   scheduledThreadPool.scheduleAtFixedRate(new Runnable() {  
  9.    public void run() {  
  10.     System.out.println("delay 1 seconds, and excute every 3 seconds");  
  11.    }  
  12.   }, 13, TimeUnit.SECONDS);  
  13.  }  
 ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();  

七.ThreadPoolExecutor的内部工作原理。 
ThreadPoolExecutor是java.util.concurrent包提供的基础线程池,使用非常广泛
让我们来看一下线程池的使用和内部实现原理
下面是ThreadPoolExecutor的一个构造方法,最终所有其他构造方法都要调用这个构造方法,来看一下构造方法中的参数的作用
corePoolSize:核心线程池的大小,当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。调用perstartAllCoreThreads()方法,线程池会提前创建并启动所有基本线程
maximumPoolSize:线程池允许创建的最大线程数。如果队列满了,并且已经创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。如果是无界队列这个参数不起作用
keepAliveTime:当线程池中的线程数大于corePoolSize时,keepAliveTime为多余的空闲线程等待新任务的最长时间,超过这个时间后多余的线程将被终止。这里把keepAliveTime设置为0L,意味着多余的空闲线程会被立即终止。
unit:时间单元
workQueue:线程池的工作队列,工作队列最常用的有如下几种
    ArrayBlockingQueue:是一个基于数组结构的游街阻塞队列
    LinkedBlockingQueue:基于链表结构的阻塞队列
    SynchronousQueue:不存储元素的阻塞队列,插入元素后必须等待另一个线程调用移除操作
threadFactory:创建线程的工厂
handler:饱和策略。当队列和线程都满了,必须采取一种策略处理提交的新任务。默认策略是AbortPolicy,JDK1.5提供了如下4种策略,除了这四种策略外还可以自己来实现RejectedExecutionHandler接口来实现自定义的策略
    AbortPolicy:直接抛出异常
    CallerRunsPolicy:只用调用者所在线程来运行任务
    DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务
    DiscardPolicy:不处理,丢弃掉
线程池的实现原理
当向线程池提交一个任务之后,线程池的处理流程如下:
1)线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务(使用threadFactory来创建)。如果核心线程池里的线程都在执行任务,则进入下个流程
2)线程池判断工作队列是否已经满了。如果工作队列没有满,则将新提交的任务存储在这个工作队列里(对应构造函数中的workQueue),则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程
3)线程池判断线程池是否已经满了(线程达到了最大数,且任务队列都满了)。如果没有,创建一个新的工作线程来执行任务。如果已经满了,任务将被拒绝并交给饱和策略来处理这个任务
八.分布式环境下,怎么保证线程安全。
在分布式环境中,处理并发问题就没办法通过操作系统和JVM的工具来解决,那么在分布式环境中,可以采取一下策略和方式来处理:
  • 避免并发
  • 时间戳
  • 串行化
  • 数据库
  • 行锁
  • 统一触发途径

阅读全文
'); })();
0 0
原创粉丝点击
热门IT博客
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 怎样钩宝宝鞋 毛线钩宝宝鞋 钩宝宝鞋 手钩杯垫 手钩包包 拖鞋底怎么钩 手工钩凉拖鞋 钩织披肩 钩桌布 钩包包 钩毛衣 手工钩包 钩手提包 钩鞋子 宝宝鞋的钩法 钩织毛衣 钩宝宝帽子 钩拖鞋线 怎样钩手提包 钩边花边 怎样钩织宝宝鞋 手甲钩 手工钩鞋 钩织包包 手工钩织拖鞋 包包的钩法 拖鞋的钩法 钩袜子 毛线钩包 手提包的钩法 拖鞋钩法 钩儿童鞋 钩织拖鞋 钩围巾 手工钩拖鞋 钩沙发巾 钩织花样 钩花朵 花兰钩 手工钩织 怎样钩毛线拖鞋