1.当从Runnable导出一个类时,它必须具有run()方法,但是这个方法并无特殊之处 -- 它不会产生任何内在的线程能力,要实现线程能力,必须显示的将一个任务附着到线程上。
2.使用Executor替代显示的Thread对象,在任何线程池中,现有线程在可能的情况下,都会被自动复用
3.线程-Thread 任务-Runnable
4.线程池可以自定义线程工厂,这个很重要,后面比如批量设置thread.setDeamo(true),或者设置捕获Exception,都需要,记住线程池就是定义一个线程工厂,然后excute或者submit Runnable.可以使用默认工厂或者自己实现。下面是自定义的
Excutors.newCacheThreadPool(new ThreadFatory(){
@Override
public Thread newThread(Runnable r){
Thread t = new Thread(r);
return t;
}
});
cachedThreadPool(缓存,线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程)
FixedThreadPool(创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待)
除了ScheduledThreadPool和返回参数的线程池,其他添加都是executorService.execute(new Runnable());
ScheduledThreadPool(创建一个定长线程池,支持定时及周期性任务执行)
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool.schedule(new Runnable() {
@Override
public void run() {
System.out.println("delay 3 seconds");
}
}, 3, TimeUnit.SECONDS);
newSingleThreadExecutor(创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行)其实就是数量为1的fixedThreadPool,适用于比如文件IO操作,避免多个线程操作同一资源
5.返回信息:实现Callable接口,List<Future<String>>.add(pool.submit(new Callable(){});
这种匿名内部内传参要么用类全局变量,要么局部变量为final,或者.accept 写个 public
public Callable<Map<String,Object>> accept(
Map<String, Object> mapinfo)
{
this.mapinfo = mapinfo;
return this;
}
获取future的返回值可以用future.isDone()来判断是否执行完,然后get(),或者直接get()会一直阻塞直到执行完成
线程不会传递Exception到主线程,需要本地处理异常
线程的sleep及线程的分发,每个系统不一样,所以不要完全相信在你本地分发的策略
Thread.currentThread()返回当前线程引用
6.Thread.currentThread().setPriority(Thread.MAX_PRORITY OR Thread.NORM_PRIORITY OR Thread.MIN_PRIORITY)
一般不使用,Thread.yield()暗示可以让别的线程使用CPU了,仅仅是暗示,不代表会被采纳,其实不能依赖yield
设置线程优先级setPriority 只能在run方法里,如果在别的地方设置比如构造函数,将do no good(这是什么意思,不好的意思还是不能设置的意思,百度了一下应该是无济于事的意思),以为此事Executor在那个时间点还没有开始任务
7.后台程序:只要主干程序结束后台程序就结束,主干程序的非后台线程结束主干程序就结束。
Thread().setDaemon(true)设置后台程序
public class RunMore extends Thread implements Runnable{
@Override
public void run() {
while(true) {
try {
Thread.sleep(100);
System.out.println(Thread.currentThread());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String args[]) {
for(int i =0;i<=10; i++) {
Thread daemo = new Thread(new RunMore());
daemo.setDaemon(true);
daemo.start();
}
System.out.println("all thread start");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Thread.sleep()这个时间设置的越小 主干程序存活的时间越小,后台线程打印的数量越小。
setDeamon这个方法的意思是设置守护线程,附属于主线程,主线程结束则附属线程结束
ExcutorService exec = Excutors.newCachedThreadPool(new ThreadFactory);
可以重写ThreadFactory的newThread方法,让thread都以deamo状态来运行,Excutors可以重新属性(daemon,priority,name)
(衍生一下,ThreadFactory还可以加统一异常处理,思路:定义一个UncaughtExceptionHandler,然后ThreadFactory.setUncaughtExceptionHandler(new yourHandler),然后Excutors.newCachedThreadPool(your ThreadFactory),然后excutor.execute(new Runable());
deamo的线程衍生出来的线程都是deamo
public class DaemonThreadFactory implements ThreadFactory {
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
return t;
}
}
public class DaemonFromFactory implements Runnable {
public void run() {
try {
while(true) {
TimeUnit.MILLISECONDS.sleep(100);
print(Thread.currentThread() + " " + this);
}
} catch(InterruptedException e) {
print("Interrupted");
}
}
public static void main(String[] args) throws Exception {
ExecutorService exec = Executors.newCachedThreadPool(
new DaemonThreadFactory());
for(int i = 0; i < 10; i++)
exec.execute(new DaemonFromFactory());
print("All daemons started");
TimeUnit.MILLISECONDS.sleep(500); // Run for a while
}
}
deamo线程如果有try catch finally 那么finally这段代码不会执行(为啥呢,这个英文有点翻译不来,大概意思是守护进程可能随时被主程序终止,finally代码是不可靠执行的,所以干脆就不给你这个思想,让你别realy on finally)
8.为啥建议不直接new Thread(new Runable)呢 而是建议用Thread工厂,比如Excutors.CacheExcutorPool这种,因为你直接Thread,书上有种情况是在构造函数里面直接start(),这种代码第一不好看,第二是可能某些参数还没有初始化导致出错,书上这样写的,其实也不会这么傻吧。
9.加入一个线程t.join();这样子的:当一个主线程调用另外一个线程t,t.join(),然后主线程开始暂停等待t执行完再执行(t.isAlive()为false); 加入线程join(),必须要捕获编译异常InterruptException(),当主线程.interrupt() 就会被捕获,但是在被捕获后isInterrupt()这个方法会被清除标志,本来应该是true(已经被终止),但是捕获后再打印出来依旧是false;
.interrupt()这个方法只是标识一个中断标识,并不会立即中断线程,比如sleep会检测这个中断标志,如果是true,则抛出InterruptException();
来来来,看了几篇interrupt的文章,总算了解到皮毛了,是这样一个意思:线程理应该自己中断进程,不应该由其他进程帮忙中断,所以已经放弃了Thread.stop()等一些方法。然而这个方法只是一个标识更改,并不会导致程序结束,除了在阻塞状态(sleep waite join 同步锁等状态)在收到interrupt这个状态更改后会立即抛出一个interruptException,在正常的执行的没有阻塞状态不会抛出,会正常继续执行。为什么呢?因为在阻塞的时候你没法知道已经给了一个阻塞标识,所以需要抛出。正常情况是在线程里需要一直(this.isInterrupted())进行检测是否置于终止状态,然后进行处理,比如退出或者做其他的。
isInterrupted()是否中断,不会改变终止状态
interrupted是否中断,但会更改终止状态,也就是如果是终止状态则更改为非终止
while (!Thread.interrupted()) { //一般这样做
// do more work.
}
join也可以添加时间参数,如果这个时间添加的t没有处理完,直接返回进行主线程。
10.线程如何处理异常:我们用Excutors管理线程,Thread.UncaughtExceptionHandler可以绑定线程,当线程发生异常,会自动调用Thread.UncaughtException.uncaughtException().所以我们要做的就是新建一个ExcutorService并且指定自定义ThreadFactory,里面绑定UncaughtExceptionHandler给每新建的线程.
class MyUncaughtExceptionHandler implements
Thread.UncaughtExceptionHandler {
public void uncaughtException(Thread t, Throwable e) {
System.out.println("caught " + e);
}
}
class HandlerThreadFactory implements ThreadFactory {
public Thread newThread(Runnable r) {
System.out.println(this + " creating new Thread");
Thread t = new Thread(r);
System.out.println("created " + t);
t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
System.out.println(
"eh = " + t.getUncaughtExceptionHandler());
return t;
}
}
//创建ExcutorService
ExecutorService exec = Executors.newCachedThreadPool(
new HandlerThreadFactory());
如果不需要每个线程对应不同的Exception,而是设置一个全局的捕获异常操作,可以这样做
Thread.setDefaultUncaughtExceptionHandler(
new MyUncaughtExceptionHandler());
当没有单独设置异常处理器,才会去调用这个Default默认处理器,如果有设置过,那就不会去调
11.解决资源争夺,在语句前加上一个锁协议,成为互斥锁,mutex。关键字synchronized。比较重要的一点,如果使用多线程,需要让字段表示为private,否则synchronized不会防止其他任务访问字段。
synchronized void f() { /* ... */ }
synchronized void g() { /* ... */ }
For the preceding methods, if f( ) is called for an object by one task, a different task cannot call f( ) or g( ) for the same object until f( ) is completed and releases the lock. Thus, there is a single lock that is shared by all the synchronized methods of a particular object,
一个任务多次获取同一个对象的锁,因为肯定只有一个task能获得锁,在第一次获取的时候jvm会加锁计数加1,每获取一次加一(这种情况是这个对象获取了这个锁,可以在方法里继续获取锁,只能相同任务才能多次获取锁),离开一个synchronized减一,直到变成0就可以给其他task用。
看了下博客,有一个点要搞懂:如果一个对象有多个synchronized方法,只要一个线 程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法,也就是说一个对象只有一把锁,是一个对象一个实例一把锁,一个对象不同实例相互不影响。比如synchronized(this)[只作用于这个对象]和synchronized(XXX.class)[作用于这个类下所有实例对象]是不一样的。然后我想了一下,比如我们在一个类里面有两个方法都要同步method a()和 method b(),但是只要有一个实例调用了a就不能有其他实例调用b,但是我想实现的是,不能有任务同时调用a或同时调用b,交叉调用没关系,那就每个方法定义一个锁对象,比如a(){synchronized(new 一个实例)) b(){synchronized(new 一个其他实例))那么是两个方法两个锁,就不会互相影响
class A{
private A a1 = new A();
private A a2 = new A();
public void K(){
synchronized(a1){
....
}
}
public void M(){
synchronized(a2){
...
}
}
synchronized static method可以在类的范围内对对象进行锁,不是对象的实例
12.获取锁有两种方式,一种是利用synchronized关键字,一种是显示获取锁
public class MutexEvenGenerator extends IntGenerator {
private int currentEvenValue = 0;
private Lock lock = new ReentrantLock();
public int next() {
lock.lock();
try {
++currentEvenValue;
Thread.yield(); // Cause failure faster
++currentEvenValue;
return currentEvenValue;
} finally {
lock.unlock();
}
}
那什么时候用关键字什么时候用显示锁呢,有一个情境,比如你要先看看这段代码有没有被锁,等待一秒钟还没解锁就要返回到interface,那就可以用显示的,先lock.tryLock(),如果false就代表被锁了,就可以返回说有线程在用。
try {
captured = lock.tryLock(2, TimeUnit.SECONDS);
} catch(InterruptedException e) {
throw new RuntimeException(e);
}
但是用显示的lock要注意必须try catch finnali(释放锁),并且return语句应该放在try里面,因为怕锁先释放后,暴露了data给第二个任务
13.原子性和易变性(volatility)
只要是原子性的操作就不用担心同步问题,只要开始就不会context switch,比如除了long和double的基础类型的操作都是原子性,但是long和double是64位的类型,所以在32位操作系统上是分两步进行赋值,因此在32位上不是原子性的,加上volatile就可以保证原子性。说了很多,就是说我们这种人是达不到只写原子操作的代码的,让我们不要想了,该用syncronized还是要用,不要觉得自己牛逼。
讲了一个多核处理器可视性visibility更重要比原子性,比如一个task改变了某个东西,存在缓存里,其他的task对于这个改变就是不可见的,但是syncronized同步系统是强制性的可见
volatile也保证了在应用中的可视性(visibility),如果一个字段用volatile修饰,标识只要写入这个字段,所有的读写都可以观察到他的变化,就算用了local caches。
好了,看了一会的volatile和syncronized后,总结一下:volatile能保证可见性,不能保证原子性,sysnconized保证原子性,从而也保证了可见性,synconized肯定消耗资源更多。为什么volatile不能保证原子性,比如一个状况:对于一个i进行赋值,有十个线程进行赋值,在一个线程取出i放入工作内存和寄存器后,其他线程进行更改i,虽然volatile可以保证可见性,但是寄存器里的值还是以前的不会更新,只有工作内存的值无效重新获取,所以导致不是原子性的。再看看一个解释的
既然i是被volatile修饰的变量,那么对于i的操作应该是线程之间是可见的啊,就算A.,B两个线程都同时读到i的值是5,但是如果A线程执行完i的操作以后应该会把B线程读到的i的值置为无效并强制B重新读入i的新值也就是6然后才会进行自增操作才对啊。
后来参照其他博客终于想通了:
1、线程读取i
2、temp = i + 1
3、i = temp
当 i=5 的时候A,B两个线程同时读入了 i 的值, 然后A线程执行了 temp = i + 1的操作, 要注意,此时的 i 的值还没有变化,然后B线程也执行了 temp = i + 1的操作,注意,此时A,B两个线程保存的 i 的值都是5,temp 的值都是6, 然后A线程执行了 i = temp (6)的操作,此时i的值会立即刷新到主存并通知其他线程保存的 i 值失效, 此时B线程需要重新读取 i 的值那么此时B线程保存的 i 就是6,同时B线程保存的 temp 还仍然是6, 然后B线程执行 i=temp (6),所以导致了计算结果比预期少了1。如果不加sychronized,值永远小于预期
也就是temp在寄存器里面,还有一层高速缓存。自增操作不是线程安全的
还讲了些原子性的东西,但我在jdk7出现的结果和书上不一样,就不深究了,总结下就是说尽量不要用volilate,尽量用锁(隐实锁synchronized和显示锁lock)
可以同步方法也可以同步代码块 用synchronized(object){}来进行标记,同步代码块能提高访问对象的性能,不像整个方法的同步,不会一直锁住线程安全的对象。一般同步临界用的object就是this,比如synchronized(this)
再来:volilate能保证可见性和有序性:有序性表示可能某一步比如新建对象,new instance();
当不用volilate时,处理器可能会优化而进行指令重排,正常应该是1.分配空间 2.实例化对象放入空间 3. 引用指向空间。如果重排 则2 3 可能调换位置,导致另外一个线程读取到instance已经非空也就是实例化了,再进行其他步骤导致出错。
所以volilate不适用于依赖前一个值的时候,比如i++,尽管加了volilate,只是主内存能看见,但是i++依然分成了很多个步骤,只保证了可见性,没保证原子性。
https://www.cnblogs.com/dolphin0520/p/3920373.html
ThreadLocal:
ThreadLocal<HttpRequest> request = new ThreadLocal<HttpRequest>;
request.get();
request.set(HttpRequest);
简而言之,就是比如有一个变量需要在很多地方用,一般我们会怎么做,我们会把这个变量设置为static,但是有个问题,这样不是线程安全的,所以使用ThreadLocal来保证每个线程都有自己的副本,不会相互影响。反正就是你用static那肯定多个线程获取的对象就会改变,因此这样做。
ThreadLocal不是为了解决资源共享和并发问题,是为了每个线程能保存自己的变量
最典型的是管理数据库的Connection:当时在学JDBC的时候,为了方便操作写了一个简单数据库连接池,需要数据库连接池的理由也很简单,频繁创建和关闭Connection是一件非常耗费资源的操作,因此需要创建数据库连接池~
那么,数据库连接池的连接怎么管理呢??我们交由ThreadLocal来进行管理。为什么交给它来管理呢??ThreadLocal能够实现当前线程的操作都是用同一个Connection,保证了事务!
Thread有四种状态
:new、runnable、blocked、dead
变成blocked的方式:sleep(milliseconds)、wait()[这个方法如果不调用notify()或者notifyAll()不会回到runnable状态]、这个线程正在等待IO、线程去调用synchronized方法或者代码(已经被别的线程获取了锁)。取消了suspend()和resume()因为会造成死锁,取消了stop()因为可能造成运行的不稳定,突然停止某个程序,使之无法释放锁,并且能被修改。
Thread.interrupt(),抛出IterruptedException以及轮训中断Thread.interrupted()会重置中断标志
现在一般都不直接操作Thread,而是操作Excutor线程池,也就是Excutor.shutdownNow(),将会发送给每个线程Thread.interrupt();那如何由Excutor停止某一个线程呢,那就是不用execute而是使用submit获取一个Future泛型对象,future.cancle();
当处于block状态,调用interrupt(),会将终止状态设置为true,如果是sleep或者wait方法,则直接抛出interruptException,否则继续执行,只是状态改了,在哪里进行终止由执行代码决定。也就是说你能中断任何要求抛出InterruptedExcetion的调用,但不能中断比如等待获取锁的同步程序或者IO任务。如果要中断IO阻塞,可以从底层资源关闭,比如socket.close();\
锁阻塞应该如何停止,使用显示锁Lock,并调用lock.lockInterruptibly(),当线程被t.interrupt,则会抛出InterruptedException异常。
当被互斥锁阻塞
当线程t.interrupt,只有几个情况能被动中断线程:
1.sleep
2.wait
3.join
4.lock.lockInterruptibly
,也就是synchronized或者显示锁Lock,显示锁可以被中断,reentrantLock(),lock.lockInterruptibly();
ReentrantLock获取锁定与三种方式:
a) lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁b) tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;
c)tryLock(long timeout,TimeUnit unit), 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;
d) lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断
线程间的协作wait()和notifyALL()
wait必须在synchronized方法里面使用,否则会报错IllegalMonitorStateException,相当于说,wait表示:我已经做完了我的了,给其他线程做,如果不是给同步线程使用,那可以直接使用yield,所以这是线程协作的场景,也就是共同使用一个共享资源进行协作。需要同步.在调用wait()、notify()、notifyAll()之前,必须获取锁。
sleep()也和wait()差不多,
1.但是sleep不会被唤醒,wait()可以被notify()、notifyAll()唤醒
2.第二sleep不会释放锁,wait会释放锁
一样的:都可以通过构造时间,到时了自动恢复。
wait两种构造方式,wait()和wait(millions),和sleep一个道理 ,只不过会释放锁,等多少时间自动恢复,没有构造参数的只能通过notify()或者notifyAll()唤醒
wait()、notify()、notifyAll()都是基类object的一部分,不是thread得一部分,调用wait()、notify()、notifyAll()只能在同步控制方法或同步控制块里调用,因为需要先获取锁。但是sleep()就不需要,随便哪里都可以。
记住,wait()需要用while循环包裹,可能说如果只有你用notify,就其实不需要循环条件,就是怕其他你不知道的地方,比如继承了你的类进行Notify,你的条件不满足就进行处理,就会出问题
如何利用wait()、notify()、notifyAll(),这里讲了一个生产者消费者的场景,比如生产者要生产必须库存小于总库存,消费者要消费必须库存大于0.库存为锁对象,因为生产者和消费者都会操作库存。当库存为0,消费者需要等待并且把锁释放给其他线程(比如下一个消费者或者生产者),并且wait()外面必须由while循环包裹进行唤醒后的判断是否能进行操作或者继续等待。当生产者获得锁并且生产了一个,就立即notifyAll,如果是notify某一个线程可能造成死锁,详情可以看https://www.jianshu.com/p/25e243850bd2?appinstall=0
public class Something {
private Buffer mBuf = new Buffer();
public void produce() {
synchronized (this) {
while (mBuf.isFull()) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
mBuf.add();
notifyAll();
}
}
public void consume() {
synchronized (this) {
while (mBuf.isEmpty()) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
mBuf.remove();
notifyAll();
}
}
notify造成死锁:比如出现一个情况,锁队列只有一个消费者a,等待队列有很多个生产者,消费者a消费后notify生产者b,这时a、b都在锁队列,a又抢到了锁然后进入等待队列,b生产后notify另外一个生产者c,因此b、c进行获取锁并进入等待区,所有线程都进入等待区导致死锁。