1、为什么使用多线程
简单理解就是让串行的线程任务可以以并行多线程方式去运行,提供CPU利用效率。
2、多线程怎么创建
继承thread 实现runnable new Thread().start(); new Thread(new RunnableImp).start(); new Thread( public void run(){}).start(), 线程池Executors.newFixedThreadPool(1).execute(new Thread())【推荐用这种,减少线程创建和销毁时候系统资源消耗】
3、线程的一些基本概念
①、线程状态:创建 new、 就绪runnable 、 运行running 、blocked阻塞 、 等待 waiting 、 消亡 dead
②、线程的一些主要方法:
一、start() 启动方法。
二、run() 被线程支持的方法。
三、sleep()、yield()、让线程休眠,让出cpu执行权,但是不会释放的线程锁(synchroinzed锁住的对象或者代码块等)
四、interrupt()方法。只是改变中断状态而已,当一个阻塞的线程收到一个中断信号,这个线程就可以退出阻塞的状态。只有线程被wait(),join(),sleep()方法修饰,才会进入阻塞状态。比如我们平常调用Thread.sleep()方法总会抛出一个捕获一个 catch (InterruptedException e),这个就是我们调用interrupt()方法时候,进入catch的部分。如果线程没有进入阻塞状态,interrupt将不起作用。
五、wait()、notify()、notifyAll()
1、概念:wait()是让线程等待,会释放线程锁的。
2、条件:synchronized是使用wait和notify的必要条件,如果不适用会报异常java.lang.IllegalMonitorStateException
3、等待和锁住必须要是同样的一个对象
4、如果notify和wait在一起用的话,必须要先notify()再调用wait()。生产者和消费者模式 wait()和notify()必须在synchronized的代码块中使用,因为只有在获取当前对象的锁时才能进行这两个操作 否则会报异常
六、jion() 等待所有线程都执行完或者等待一定时间。比如我们有一个线程和一个主方法,我们需要让这线程都执行完之后再执行后面的主方法。那就可以用jion();实际上是调用了wait()的方法。会释放线程锁。jion()方法需要紧跟着start()方法
七、stop()、destroy()方法
4、线程安全
①、为什么引入线程安全的这个问题?
比如我们有多个线程,对一个变量sum进行求和计算1+..100。 本来结果应该是5050,但是却有可能出现结果不等于5050情况,这样我们虽然使用线程提高了效率,但是数据不准确,还不如不用,所以要想合理利用线程,必须要知道线程安全相关知识点。
多个线程同时访问一个资源时,会导致程序运行结果并不是想看到的结果,临界资源(共享资源)也就是说,当多个线程同时访问临界资源(一个对象,对象中的属性,一个文件,一个数据库等)时,就可能会产生线程安全问题
多个线程执行一个方法,方法内部的局部变量并不是临界资源,因为方法是在栈上执行的,而Java栈是线程私有的,因此不会产生线程安全问题。
②、为了线程安全而引入的知识点。
一、synchronized 关键字
①:互斥锁,能够达到互斥访问目的的锁
②:当一个线程正在访问一个对象的synchronized方法,那么其他线程不能访问该对象的其他synchronized方法,如果当前访问的是static方法,那就可以访问
③:释放锁只会有两种情况:对于synchronized方法或者synchronized代码块,当出现异常时,JVM会自动释放当前线程占用的锁,因此不会由于异常导致出现死锁现象,当线程执行完了代码块也会释放的
④:缺点:当线程被阻塞了就无法释放线程锁,就不会释放线程锁。再比如无法实现多线程的读写文件,因为读文件不会产生冲突,所以不需要加锁而synchronized是对整个代码块都加锁无法实现正对写加锁,读取不加锁,而lock可以
二、Lock 类
①:需要手动去关闭锁,锁的类型有ReentrantLock(可重入锁),ReentrantReadWriteLock 读写锁
②:lock()、tryLock()、lock.newCondition()、tryLock(long time, TimeUnit unit)和lockInterruptibly() Lock里面的接口。如果同时启动两个线程调用tryLock(),其中一个线程会获取不到锁,退出执行;如果调用tryLock(long time, TimeUnit unit)其中一个线程获取不到锁会等待一段时间,然后继续执行。condition.signal();condition.await();和wait()和notify()一个意思,不过condition是和lock绑定的
③lock与synchronized区别:
1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
5)Lock可以提高多个线程进行读操作的效率。
6)lock可以实现ReentrantReadWriteLock 实现读取不加锁,而写加锁
三、CutDownLatch( [lætʃ]),CyclicBarrier、Semaphore(['sɛməfɔr] 信号)并发编程辅助类
①CutDownLatch类似于程序计数器,表明上和jion()功能类似,但是jion是调用wait和notify的,容易出bug而且实现的只是部分的cutdownlatch的功能。
1)CutDownLatch可以实现类似程序计数器的功能,比如有一个方法method5(),必须等待其他的线程method()执行完之后最后才执行可以用这个。
2)CutDownLatch主要方法有CutDownLatch latch=new CutDownLatch(n);需要等待的主方法调用latch.wait(); 然后每个线程里面处理完任务最后都要调用latch.countDown();会将构造法方法里面定义的计数器n值减一,当有n个线程调用过latch.countDown()方法之后,才会继续执行主方法的latch.wait();后面的类容。如果没有n个调用,线程会一直挂起的。所以我们要启动N个线程
②CyclicBarrier俗名回环栅栏,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。
1)CyclicBarrier可以实现功能。用来挂起当前线程,直至所有线程都到达barrier状态再同时执行后续任务;比如有三个线程,两个次线程,一个主线程。主线程要等到两个线程都跑完了才继续跑,可以利用CyclicBarrier的方式去实现,虽然这种功能可以利用很多重方法去实现
2)CyclicBarrier主要使用方法有两种。
【1】CyclicBarrier cb= new CyclicBarrier(2, new MainThread());这个就是上面举的例子。每个子线程都要设置cb.await();拦截点。当其他子线程都执行完了之后,才会执行 MainThread()的线程。【如果子线程设置拦截点cb.await()后面还有代码的话,会等待MainThread()执行之后继续执行各自线程栅栏后面的代码!!!】当然也要开启2个线程!不然会一直等待挂起。
【2】CyclicBarrier cb= new CyclicBarrier(N); 比如N个线程run()方法里面执行步骤都类似这样 【1】dosomething1; 【2】拦截点cb.await()【3】dosomething2;
我们想要的效果是,N个线程都先执行【1】dosomething模块,执行完了之后统一都在执行【3】dosomething2模块!
③Semaphore可以控同时访问的线程个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。
2)使用场景:假若一个工厂有5台机器,但是有8个工人,一台机器同时只能被一个工人使用,只有使用完了,其他工人才能继续使用。那么我们就可以通过Semaphore来实现:
1)使用方法:Semaphore semaphore = new Semaphore(5); semaphore.acquire();和semaphore.release();释放
4)CyclicBarrier与countdownlatch的区别
1、CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同:CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。
举个例子,伪代码
countDownLatch.await();//第一步
普通方法();//第二步
线程1();//第三步
因为当代码执行到第一步时候 会等待countDownLatch计数器为0(也就是说参与计数的线程都执行完) 才会顺序执行步骤2,然后步骤三。
伪代码
cyclicBarrier.await();//第一步
普通方法();//第二步
线程1();//第三步
cyclicBarrier却不是这样玩的,第一步执行一半的时候可能执行第二步。
四、同步容器
①java集合容器有List 、Set、Queue、Map,除了Map是最上级接口,其他的都继承了Collection接口。
②ArrayList、LinkedList、HashMap这些容器都是非线程安全。
③线程安全同步容器有:Vector、HashTable、Stack;Collection类里面提供的静态工厂方法,Collections.synchronizedMap(),
④同步原理,利用了 synchronized进行了同步,所以效率比较低。
⑤利用同步类写的代码,不一定线程安全!
五、CopyOnWrite ( 读不加锁,写加锁为了解决同步容器效率低而出现的)
①CopyOnWriteArrayList、CopyOnWriteMap<String, String>等等。是一种用于程序设计中的优化策略。其基本思路是,从一开始大家都在共享同一个内容,当某个人想要修改这个内容的时候,才会真正把内容Copy出去形成一个新的内容然后再改,这是一种延时懒惰策略。读写分离的思想,但是读取的时候可能会读取到脏数据
② CopyOnWriteArrayList和CopyOnWriteArraySet分别代替List和Set
六、CurrentHashMap()
①ConcurrentHashMap代替同步的Map(Collections.synchronized(new HashMap())
② ConcurrentHashMap可以做到读取数据不加锁,并且其内部的结构可以让其在进行写操作的时候能够将锁的粒度保持地尽量地小,不用对整个ConcurrentHashMap加锁。
③ConcurrentHashMap内部使用了segment的结构,和CAS数据结构,避免使用锁
④支持 compareAndSetState 【CAS】数据结构的有 AtomicXXX、 ConcurrentMap、 CopyOnWriteList、 ConcurrentLinkedQueue
七、原子类线程安全
八、线程安全的一些关键字、类
九、阻塞队列
ArrayBlockingQueue:基于数组实现的一个阻塞队列,在创建ArrayBlockingQueue对象时必须制定容量大小。并且可以指定公平性与非公平性,默认情况下为非公平的,即不保证等待时间最长的队列最优先能够访问队列。
LinkedBlockingQueue:基于链表实现的一个阻塞队列,在创建LinkedBlockingQueue对象时如果不指定容量大小,则默认大小为Integer.MAX_VALUE。
PriorityBlockingQueue:以上2种队列都是先进先出队列,而PriorityBlockingQueue却不是,它会按照元素的优先级对元素进行排序,按照优先级顺序出队,每次出队的元素都是优先级最高的元素。注意,此阻塞队列为无界阻塞队列,即容量没有上限(通过源码就可以知道,它没有容器满的信号标志),前面2种都是有界队列。
DelayQueue:基于PriorityQueue,一种延时阻塞队列,DelayQueue中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue也是一个无界队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞
put方法用来向队尾存入元素,如果队列满,则等待;
take方法用来从队首取元素,如果队列为空,则等待;
offer方法用来向队尾存入元素,如果队列满,则等待一定的时间,当时间期限达到时,如果还没有插入成功,则返回false;否则返回true;
poll方法用来从队首取元素,如果队列空,则等待一定的时间,当时间期限达到时,如果取到,则返回null;否则返回取得的元素;
drainTo(Collection<? super E> c); //移除此队列中所有可用的元素,并将它们添加到给定 collection 中。
drainTo(Collection<? super E> c,int maxElements);//最多从此队列中移除给定数量的可用元素,并将这些元素添加到给定 collection 中
offer(E o); //如果可能的话,将指定元素插入此队列中。
offer(E o, long timeout, TimeUnit unit); //将指定的元素插入此队列中,如果没有可用空间,将等待指定的等待时间(如果有必要)。
poll(long timeout, TimeUnit unit); //检索并移除此队列的头部,如果此队列中没有任何元素,则等待指定等待的时间(如果有必要)。
put(E o); //将指定元素添加到此队列中,如果没有可用空间,将一直等待(如果有必要)。
remainingCapacity(); //返回在无阻塞的理想情况下(不存在内存或资源约束)此队列能接受的元素数量;如果没有内部限制,则返回 Integer.MAX_VALUE。
take(); //检索并移除此队列的头部,如果此队列不存在任何元素,则一直等待。
十、多线程执行顺序
cat /proc/sys/kernel/threads-max