new Thread() //或者 new Thread(new Runnable()) 复制代码
之后用start()来启动线程。跟代码会发现start()会执行start0()这个native方法,虚拟机调用run方法。有Runnable就会调用传入的runnable的run()实现,否则就会执行Thread中的run()方法。
ThreadFactory factory=new ThreadFactory(){ @Override public Thread newThread(Runnable r){ return new Thread(r); } } Runnable runnable =new Runnable(){ //``` } Thread thread=factory.newThread(runnable); thread.start(); Thread thread1=factory.newThread(runnable); thread1.start(); 复制代码
Runnable runnable =new Runnable(){ @Override public void run(){ //``` } } Executor executor=Executors.newCachedThreadPool(); executor.executor(runnable); executor.executor(runnable); (ExecutorService)executor.shutdown(); 复制代码
至少对于我,看起来是很少用,那为什么说最常用呢?
AsyncTask,Cursor,Rxjava其实也是使用Executor进行线程操作的。
可以来看一下Executor,只是一个接口来通过execute()来指定线程工作
public interface Executor { void execute(Runnable command); } 复制代码
对于Executor的扩展:ExecutorServive/Executors Executors.newCachedThreadPool()返回的又是一个ExecutorSevice extends Executor,对Executor做了一些扩展,主要关注的是shutdown()(保守型的结束)/shotdownNow()(立即结束),还有Future相关的submit(),这些后面会说。
newCachedThreadPool() 创建了一个带缓存的线程池,自动的进行线程的创建,缓存,回收操作。
还有几个别的方法:
newSingleThreadExecutor() 单一线程,用途较少。
newFixedThreadPool() 固定大小的线程池。 比如需要创建批量任务。 newScheduledThreadPool() 指定时间表,做个延时或者指定时间执行。
如果你想自定义线程池的时候就可以参考这几个方法在app初始化的时候直接new ThreadPoolExecutor了。 线程完成后结束:就可以直接添加任务后执行shutdown()。关于线程的结束,写在了后面的。
ThreadPoolExecutor()的几个参数
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor( 0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); } public ThreadPoolExecutor( int corePoolSize,//初始线程池的大小/创建线程执行结束后回收到多少线程后不再回收 int maximumPoolSize,//线程上线 long keepAliveTime,//保持线程不被回收的等待时间 TimeUnit unit, BlockingQueue<Runnable> workQueue,//阻塞队列 ThreadFactory threadFactory, RejectedExecutionHandler handler) { //``` } 复制代码
写到这里的时候,maximumPoolSize这个参数,想起之前在自己写一些图片加载、缓存的时候,开的线程总是会用CPU核心数来限制一下,比如2*CPU_CORE,以前不懂,会觉得大概是每个核分一个? 现在学到的:首先肯定不是为了一个核心来一个线程,毕竟一个cpu跑N多个线程,哪能就那么刚刚好一个核心一个。
大概是可以保证代码在不同的机器上的CPU调度积极性差不多,比如单核的,就创建两个线程,8核心的,就是16个线程。不然写8个线程,单核心机器运行可能就会比较卡,8核心机器运行又会太少。
4. callable 可以简单描述为有返回值的后台线程,安卓端比较少用,就简单记录下,毕竟AsyncTask、Handler、RxJava都比这个好用
Callable callable = new Callable<String>() { @Override public String call() throws Exception { try { Thread.sleep(1500); } catch (InterruptedException e) { e.printStackTrace(); } return "找瓶子"; } }; ExecutorService executor = Executors.newCachedThreadPool(); Future<String> future = executor.submit(callable); try { String result = future.get(); } catch (ExecutionException | InterruptedException e) { e.printStackTrace(); } 复制代码
是不是看起来也不是很麻烦?甚至还有一丢丢好用? 但是这个Future会阻塞线程,如果在主线程中使用的话,就需要不停地来查看后台是否执行结束
while (true) { if (future.isDone()){ try { String result = future.get(); } catch (ExecutionException | InterruptedException e) { e.printStackTrace(); } try{ Thread.sleep(1000);//模拟主线程的任务,过一秒来看一看 } catch (InterruptedException e) { e.printStackTrace(); } } } 复制代码
关于线程的结束,上面只是简单说了下使用shutdown(),其实
大致的产生原因:操作系统对于cpu使用的时间片机制,导致某段代码某个线程在执行中会被暂停,其他线程继续执行同一段代码/操作同一个数据(资源)的时候,可能带来的数据错误。
eg:
class Test{ private int x=0; private int y=0; private void ifEquals(int val){ x=val; y=val if(x!=y){ System.out.println("x: "+x+",y: "+y); } } public void testThread(){ new Thread(){ public void run(){ for(int i=0;i<1_000_000_000;i++){ ifEquals(i); } } }.start(); new Thread(){ public void run(){ for(int i=0;i<1_000_000_000;i++){ ifEquals(i); } } }.start(); } } 复制代码
这还能不相等?
产生的原因其实简单,上面也简述过。就是在执行testThread的时候,两个线程同时操作ifEquals方法:
那么知道了原因,解决的思路就变得简单,x,y这两步操作应该是变成一步操作即可。或者说一次ifEquals方法变成一步操作。所以JAVA提供了synchronized关键字,把这个关键字加在ifEquals这个方法,就使得其变成原子操作。 会对这个方法添加一个监视器Monitor这样在线程1未执行完毕的时候,monitor不会被释放,即使线程切换,线程2访问到这个方法的时候,由于monitor未被释放就会进入排队等待,不会执行这个方法。
关于 Synchronized 关键字:现在给ifEquals增加Synchronized关键以后,上述代码再增加下面这个delVal()方法在线程中调用,x,y的值会出现问题吗?依然会。
所以,看起来是对于方法的保护,实际上是对资源的保护,比如上面的例子,我们希望的其实并不是保护ifEquals方法,而是x,y的资源。
private void delVal(int val){ x=val-1; y=val-1; } String name; private Synchronized void setName(String val){ name=val; } 复制代码
另一个问题就是在保护x/y的时候,同时也需要保护name的时候,如上对于两个方法都加上Synchronized的时候也不方便,此时会导致当一个线程只是访问到ifEquals方法的时候,另一个线程不能访问setName。这就与我们一个线程操作x、y,另一个线程同步操作name的预期不符。原因是对方法添加synchronized会把整个Test类对象当做Monitor进行监视,也就是这些方法都会被同一个monitor进行监视。
那为什么两个方法操作的不是同一个资源,还会被保护呢?因为monitor不会对方法进行检查,实际上我们synchronize方法的原因也不是为了让方法不能被另一个线程调用,而是为了保护资源。这时候,synchronize方法就不符合我们多线程操作的预期,就需要我们自己手动来进行操作。所以就需要引入Synchronized代码块。
final Object numMonitor=new Object(); final Object nameMonitor=new Object(); //代码块 private void ifEquals(int val){ synchronize(this){ x=val; y=val if(x!=y){ System.out.println("x: "+x+",y: "+y); } } } //代码块 private void delVal(int val){ synchronize(this){ x=val-1; y=val-1; } } //指定monitor private Synchronized void setName(String val){ synchronize(nameMonitor){ name=val; } } public void testThread(){ //`````` } 复制代码
synchronize(Object object),允许你指定object作为monitor来进行监视,比如我们可以把上面的this,换成numMonitor,这个时候,name和x、y就是两个monitor进行,互不影响了。
synchronize的另一个作用:线程之间对于监视资源的数据同步
先解释下java虚拟机下面的数据操作:比如ifEquals这个方法,x、y读到内存中,并不是cpu直接操作内存中的数据,而是由cpu单独给一个存储空间进行操作。我们都知道,现在用的内存条速度,比起cpu的操作速度有着极大的速度差,就像硬盘和内存的巨大速度差一样,如果代码都是从硬盘执行,然后操作数据再写回硬盘,肯定无法忍受。对于cpu也是一样,ram的读写实在是太慢了,这时候就像使用内存来弥补速度差一样,使用cpu的高速cache,来弥补内存和cpu总线之间的速度差。 基于以上的描述
开始操作的时候,
synchronize关键字,就会保证cpu读取赋值以后,再写回内存中。来保证数据的正确 复制代码
但是带来的结果就是,如果没有cpu缓存操作,这个x=5,y=5的操作会变得很慢。其实从上面的代码运行时间也能很明显的看出区别,testThread()方法的执行时间,在有没有对方法添加synchronize时会相差非常明显。所以虽然会带来线程之间的数据同步问题,当前的cache还是很有必要的。
Safety 保证不会被改错,改坏的安全,比如Thread Safety Security 不被侵犯安全 https 的S 复制代码
private Synchronized void setName(String val){ synchronize(nameMonitor){ name=val; synchronize(numMonitor){ x=val; y=val if(x!=y){ System.out.println("x: "+x+",y: "+y); } } } } } private void ifEquals(int val){ synchronize(numMonitor){ x=val; y=val if(x!=y){ System.out.println("x: "+x+",y: "+y); synchronize(nameMonitor){ name="haha"; } } } } 复制代码
Thread1 执行 ifEquals,numMonitor,然后cpu进行线程进行切换到Thread2 执行setName,持有nameMonitor,然后往下执行的时候,发现numMonitor被持有,Thread2进行等待,切换回Thread1,Thread1发现继续执行,但是nameMonitor被持有,进入等待,这样两个线程就变成了互相持有对方需要的monitor进入互相等待,也就是死锁。
跟线程安全不是很相关的锁,更多的是,数据库相关,并不是线程相关。
比如数据库进行数据修改,需要先取出数据进行操作,再往里写,就会出现A操作写数据,B操作也写同一个数据,比如小明给我转账100,A操作出我的余额X+100,正要写入数据库:余额X+100,小王给我转账1并且先一步写入了X+1,此时如果A操作继续写入余额X+100就很明显是错误的了。
解决这个问题的方式两种:
1.给方法加,默认的monitor就是当前的Test.Class,这个类,不是这个对象 2.代码块内部无法使用synchronize(this),应为这是静态方法并不存在这个this。可以使用synchronize(Test.Class)
private volatile int a;
AtomicInteger a=new ActomicInteger(0); a.getAndIncrement(); 复制代码
比如Test中,增加一个方法
private Lock lock=new ReentrantLock(); private void reset(){ lock.lock(); //``` lock.unlock(); } 复制代码
但是如果在中间的方法中,出现了异常,后面的lock.unlock()无法执行,那就会导致一直被锁(Monitor遇到异常会自动解开),所以需要手动处理:
private void reset(){ lock.lock(); try{ //``` }finally{ lock.unlock(); } } 复制代码
看起来功能跟synchronized差不多?但是这么麻烦用你干啥?但是其实想一下,之前说线程同步的时候,都是在写数据的时候出现问题,单纯的读取数据并不会出现问题,只有在写入的时候,别人读写会导致出现问题,如果线程1读取数据中切换线程,线程2也不能读取,就会有性能的浪费,读写锁就可以解决这个问题:
private ReentrantReadWriteLock lock=new ReentrantReadWriteLock(); private ReentrantReadWriteLock.ReadLock readLock=lock.readLock(); private ReentrantReadWriteLock.WriteLock writeLock=lock.writeLock(); private void ifEquals(int val){ writeLock.lock(); try{ x=val; y=val; }finally{ writeLock.unlock() } } private readData(){ readLock.lock(); try{ System.out.println("x: "+x+",y: "+y); }finally{ readLock.unlock(); } } 复制代码
这样,有线程在调用ifEquals()的时候,别人不能读也不能写,readData()的时候,别人能跟我一起读取数据。就有利于性能的提升。
Thread thread = new Thread() { @Override public void run() { for (int i = 0; i < 1_000_000_000; i++) {// 模拟一段耗时操作 Log.d("========>", "找汤圆"); } } }; thread.start(); Thread.sleep(100); thread.stop(); 复制代码
这样就在主线程完成了子线程的开始和终止,就基本的交互就是这样。但是用的时候会发现, stop()
。喵喵喵?不是很好用吗?为什么划线不建议用了呢?
因为不可控,Thread.stop会直接结束,而不管你内部正在进行什么操作(实际上主线程也确实不知道子线程在做什么),这样就带来了不可控性。
但是比如我明知道进行A操作以后,后面的线程做的工作已经无意义了,需要节省资源终止线程要怎么做呢? 用thread.interrupt(),但是这个interrupt只是做了一个标记,如果仅仅使用interrupt是没有任何作用的,还需要子线程自己根据这个中断状态进行操作:
Thread thread = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 1_000_000_000; i++) { if (isInterrupted()) {//不改变interrupt状态 // if(Thread.interrupted()) //这个方法会在使用之后重置interrupt的状态 //做线程结束的收尾工作 return; } Log.d("========>", "找汤圆"+i); } } }); thread.start(); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } thread.interrupt(); 复制代码
看到这个interrupt是不是会觉得这个词在线程操作中有点熟悉?哎(二声),这不是刚好是上面两行sleep的时候要catch的InterruptedException么?为什么我就想让线程sleep一下,要加入个try/catch呢?
原因有两个:
//对于Thread.interrupt()方法的部分注释 * If this thread is blocked in an invocation of the wait() or join() or sleep(), * methods of this class, then its interrupt status will be cleared and it * will receive an {@link InterruptedException}. 复制代码
所以中断线程的时候需要考虑一下进行处理:
Thread thread = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 1_000_000_000; i++) { if (Thread.interrupted()) {//false,100毫秒后才会变成ture //进行自己的interrupt处理 return; } try { Thread.sleep(2000);//睡得时候被执行interrupt,会直接唤醒进入中断异常, //如果不在下面catch进行处理,interrupt的值又会被重置,导致外部调用的interrupt相当于没有发生 } catch (InterruptedException e) { e.printStackTrace(); //收尾工作 return; } Log.d("========>", "找汤圆"+i); } } }); thread.start(); try { Thread.sleep(100);//这里是主线程睡了, //跟上面的子线程的sleep不一样哦,只是为了模拟start()和interrupt之间中间有个时间差 } catch (InterruptedException e) { e.printStackTrace(); } thread.interrupt(); 复制代码
那所以安卓中我只是想单纯的让线程sleep一下,不需要外部interrupt时候提供支持,不想写try/catch不行吗?
SystemClock.sleep(100);//小哥哥,了解一下这个 复制代码
String name=null; private synchronized void setName(){ name="汤圆"; } private synchronized String getName(){ return name; } private void main(){ new Thread() { @Override public void run() { //一些操作 setName(); } }.start(); new Thread() { @Override public void run() { //一些操作 getname(); } }.start(); } 复制代码
由于两个Thread不知道谁先执行完,所以可能出现getName先执行,但是getName获取空的话又没法进行操作,这时候怎么办呢?
private synchronized String getName(){ while(name==null){}//一直干等着,直到name不为空 return name; } 复制代码
但是这是个synchronized方法又会持有跟setname一样的monitor,setName也被锁住了成了死锁,那怎么做?
private synchronized String getName(){ while(name==null){//使用wait的标准配套就是while判断,而不是if,因为wait会被interrupt唤醒 try{ wait();//object方法 }catch(InterruptedException e){ // e.printStackTrace(); } }//干等着,直到不为空 return name; } private synchronized void setName(){ name="汤圆"; notifyAll();//object方法,把monitor上的所有在等待的线程全部唤醒去看一下是否满足执行条件了 } 复制代码
或者是进入页面,需要请求多个接口,根据接口的数据来设置页面设置数据,也是类似的情况。不过实际上一个Rxjava的zip操作就能解决大多数问题了。 实际上写了这么多,这种需求Rxjava的zip操作就解决了...
private void main(){ new Thread() { @Override public void run() { //一些操作 try{ thread0.join();//相当于自动notify的wait //Thread.yield(); }catch(InterruptedException e){ e.printStackTrace(); } //一些操作 getname(); } }; } 复制代码
感谢&参考:扔物线