首页 > 代码库 > 多线程笔记
多线程笔记
1.多线程的创建方式有两种
a 实现Runnable的接口 实现他的run的方法 建议使用这种 因为接口可以实现多继承
b 集成Thread 的抽象类,重写父类的 run的方法。
2.run() 与start()的区别
调用start方法方可启动线程,而run方法只是thread的一个普通方法调用,还是在主线程里执行。
把需要并行处理的代码放在run()方法中,start()方法启动线程将自动调用 run()方法,这是由jvm的内存机制规定的。并且run()方法必须是public访问权限,返回值类型为void.
3.多线程的Synchronized
a Synchronized 需要锁住对象,不然的话还是会互斥。
b 静态方法和实例方法同步的话需要锁住 class本身,(静态方法需要同步只有锁住字节码的对象才可以 Object.class)
c 要用到共同数据,最好全部在同一个类中实现,既可以达到高内聚,也可以实现同步锁。
4.sleep和wait的区别
a sleep来自Thread类,和wait来自Object类。
b 最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
c wait等待的线程可以用 notify 唤醒。这个可以用while 循环一起使用可以防止伪唤醒,因为有时候没用调用notify的时候也可以唤醒线程。
d 在wait()/notify()机制中,不要使用全局对象,字符串常量等。应该使用对应唯一的对象。
public void method(){
while(true){
wait();
........
notify();
}
}
5.线程共享变量
a ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。
b 也可以用HashMap类来实现这个功能,map.put(当前线程,需要该线程共享的值);
c ThreadLocal 的具体实现是 ThreadLocalMap 这个map的具体key是当前线程。该类的set方法与下面类似用来取值;
d 一个ThreadLocal只能放一个变量 如果有多个的话可以用一个实体封装。或者新建多个ThreadLocal对象;
public T get() {
//获取当前执行线程
Thread t = Thread.currentThread();
//取得当前线程的ThreadLocalMap实例
ThreadLocalMap map = getMap(t);
//如果map不为空,说明该线程已经有了一个ThreadLocalMap实例
if (map != null) {
//map中保存线程的所有的线程本地变量,我们要去查找当前线程本地变量
ThreadLocalMap.Entry e = map.getEntry(this);
//如果当前线程本地变量存在这个map中,则返回其对应的值
if (e != null)
return (T)e.value;
}
//如果map不存在或者map中不存在当前线程本地变量,返回初始值
return setInitialValue();
}
6.Thread.stop() 方法是不安全的.
释放该线程所持有的所有的锁,那么被保护数据就有可能呈现不一致性,其他线程在使用这些被破坏的数据时,有可能导致一些很奇怪的应用程序错误。
7.如何正确停止线程
关于如何正确停止线程,这篇文章(how to stop thread)给出了一个很好的答案, 总结起来就下面3点(在停止线程时):
1. 使用violate boolean变量来标识线程是否停止
2. 停止线程时,需要调用停止线程的interrupt()方法,因为线程有可能在wait()或sleep(), 提高停止线程的即时性
3. 对于blocking IO的处理,尽量使用InterruptibleChannel来代替blocking IO
总结:
1.局部变量中的基本数据类型(8种)永远是线程安全的。
2.局部变量中的对象类型只要不会被其他线程访问到,也是线程安全的。
3.一个对象实例被多个线程同时访问时,他的成员变量就可能是线程不安全的。
===========================================================================
java.util.concurrent 线程并发包
(compareAndSet)CAS同步的原理:http://www.blogjava.net/syniii/archive/2010/11/18/338387.html?opt=admin
CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
利用CPU的CAS指令,同时借助JNI来完成Java的非阻塞算法。
CAS看起来很爽,但是会导致“ABA问题”。
CAS算法实现一个重要前提需要取出内存中某时刻的数据,而在下时刻比较并替换,那么在这个时间差类会导致数据的变化。
比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。如果链表的头在变化了两次后恢复了原值,但是不代表链表就没有变化。因此前面提到的原子操作AtomicStampedReference/AtomicMarkableReference就很有用了。这允许一对变化的元素进行原子操作。
java.util.concurrent.AtomicInteger :
AtomicInteger,一个提供原子操作的Integer的类。在Java语言中,++i和i++操作并不是线程安全的,在使用的时候,不可避免的会用到synchronized关键字。而AtomicInteger则通过一种线程安全的加减操作接口。
采用的是compareAndSet CAS来保证数据的同步,是通过CPU来实现的,比起用synchronized, 这里的排他时间要短的多. 所以在多线程情况下性能会比较好.
=========================================================================================================
线程池 参考文档:http://www.oschina.net/question/565065_86540
线程池的作用:线程池作用就是限制系统中执行线程的数量。根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。
为什么要用线程池:1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。
ExecutorService 真正的线程池接口。
ScheduledExecutorService 能和Timer/TimerTask类似,解决那些需要任务重复执行的问题。
ThreadPoolExecutor ExecutorService的默认实现。
ScheduledThreadPoolExecutor 继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。
1. newSingleThreadExecutor
创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
2.newFixedThreadPool
创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
3. newCachedThreadPool
创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,
那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
4.newScheduledThreadPool
创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
以上的所有底层实现都是用ThreadPoolExecutor这个来做的。具体的可以查看源代码
=================================================================================================================
Callable 与 Future 的应用
配合使用可以得到每个线程的返回值 Callable返回 Future接收
=================================================================================================================
java.util.concurrent.Lock :
共同点:Lock能够完成synchronized所实现的所有功能
不同点:Lock有比synchronized更精确的线程和更好的性能,并且提供了多样化的同步,比如有时间限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。
synchronized是在JVM层面上实现的,在代码执行时出现异常,JVM会自动释放锁定.但Lock不能,只能手动释放,并且在finally从句中释放
性能:在jdk1.5中Synchronized是使用的独占悲观锁,资源竞争不激烈的情况下synchronized性能略高,资源竞争激烈的情况下性能要远低于Lock。
jdk1.6后synchronized和lock一样都是使用CAS乐观锁操作,所以性能差不多,对于具体使用哪一个要看具体的系统应用需要,Synchronized相对简单易用,若需要精细的灵活控制则可以考虑选择lock。
Lock 也是采用compareAndSetState CAS 机制来实现锁的
ReentrantLock 互斥锁, 指的是一次最多只能有一个线程持有的锁. 在jdk1.5之前, 我们通常使用synchronized机制控制多个线程对共享资源的访问。而现在Lock提供了比synchronized机制更广泛灵活的锁定操作。
=================================================================================================================
ReadWriteLock 读写锁
ReentrantReadWriteLock 和 ReentrantLock 不是继承关系,但都是基于 AbstractQueuedSynchronizer 来实现。
注意:在同一线程中,持有读锁后,不能直接调用写锁的lock方法 ,否则会造成死锁。
读 写
读 允许 不允许
写 不允许 不允许
=================================================================================================================
Condition 可以实现可阻塞队列。可参考api里的经典列子
Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用
Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得 Condition 实例,请使用其 newCondition() 方法。
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
=================================================================================================================
自旋锁 :类似于while死循环,不断的判断表达式,判断是否还持有该把锁对象.
会导致死锁。如果长期持有自旋锁
独占悲观锁:synchronized
CAS乐观锁 :Lock
a 实现Runnable的接口 实现他的run的方法 建议使用这种 因为接口可以实现多继承
b 集成Thread 的抽象类,重写父类的 run的方法。
2.run() 与start()的区别
调用start方法方可启动线程,而run方法只是thread的一个普通方法调用,还是在主线程里执行。
把需要并行处理的代码放在run()方法中,start()方法启动线程将自动调用 run()方法,这是由jvm的内存机制规定的。并且run()方法必须是public访问权限,返回值类型为void.
3.多线程的Synchronized
a Synchronized 需要锁住对象,不然的话还是会互斥。
b 静态方法和实例方法同步的话需要锁住 class本身,(静态方法需要同步只有锁住字节码的对象才可以 Object.class)
c 要用到共同数据,最好全部在同一个类中实现,既可以达到高内聚,也可以实现同步锁。
4.sleep和wait的区别
a sleep来自Thread类,和wait来自Object类。
b 最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
c wait等待的线程可以用 notify 唤醒。这个可以用while 循环一起使用可以防止伪唤醒,因为有时候没用调用notify的时候也可以唤醒线程。
d 在wait()/notify()机制中,不要使用全局对象,字符串常量等。应该使用对应唯一的对象。
public void method(){
while(true){
wait();
........
notify();
}
}
5.线程共享变量
a ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。
b 也可以用HashMap类来实现这个功能,map.put(当前线程,需要该线程共享的值);
c ThreadLocal 的具体实现是 ThreadLocalMap 这个map的具体key是当前线程。该类的set方法与下面类似用来取值;
d 一个ThreadLocal只能放一个变量 如果有多个的话可以用一个实体封装。或者新建多个ThreadLocal对象;
public T get() {
//获取当前执行线程
Thread t = Thread.currentThread();
//取得当前线程的ThreadLocalMap实例
ThreadLocalMap map = getMap(t);
//如果map不为空,说明该线程已经有了一个ThreadLocalMap实例
if (map != null) {
//map中保存线程的所有的线程本地变量,我们要去查找当前线程本地变量
ThreadLocalMap.Entry e = map.getEntry(this);
//如果当前线程本地变量存在这个map中,则返回其对应的值
if (e != null)
return (T)e.value;
}
//如果map不存在或者map中不存在当前线程本地变量,返回初始值
return setInitialValue();
}
6.Thread.stop() 方法是不安全的.
释放该线程所持有的所有的锁,那么被保护数据就有可能呈现不一致性,其他线程在使用这些被破坏的数据时,有可能导致一些很奇怪的应用程序错误。
7.如何正确停止线程
关于如何正确停止线程,这篇文章(how to stop thread)给出了一个很好的答案, 总结起来就下面3点(在停止线程时):
1. 使用violate boolean变量来标识线程是否停止
2. 停止线程时,需要调用停止线程的interrupt()方法,因为线程有可能在wait()或sleep(), 提高停止线程的即时性
3. 对于blocking IO的处理,尽量使用InterruptibleChannel来代替blocking IO
总结:
1.局部变量中的基本数据类型(8种)永远是线程安全的。
2.局部变量中的对象类型只要不会被其他线程访问到,也是线程安全的。
3.一个对象实例被多个线程同时访问时,他的成员变量就可能是线程不安全的。
===========================================================================
java.util.concurrent 线程并发包
(compareAndSet)CAS同步的原理:http://www.blogjava.net/syniii/archive/2010/11/18/338387.html?opt=admin
CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
利用CPU的CAS指令,同时借助JNI来完成Java的非阻塞算法。
CAS看起来很爽,但是会导致“ABA问题”。
CAS算法实现一个重要前提需要取出内存中某时刻的数据,而在下时刻比较并替换,那么在这个时间差类会导致数据的变化。
比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。如果链表的头在变化了两次后恢复了原值,但是不代表链表就没有变化。因此前面提到的原子操作AtomicStampedReference/AtomicMarkableReference就很有用了。这允许一对变化的元素进行原子操作。
java.util.concurrent.AtomicInteger :
AtomicInteger,一个提供原子操作的Integer的类。在Java语言中,++i和i++操作并不是线程安全的,在使用的时候,不可避免的会用到synchronized关键字。而AtomicInteger则通过一种线程安全的加减操作接口。
采用的是compareAndSet CAS来保证数据的同步,是通过CPU来实现的,比起用synchronized, 这里的排他时间要短的多. 所以在多线程情况下性能会比较好.
=========================================================================================================
线程池 参考文档:http://www.oschina.net/question/565065_86540
线程池的作用:线程池作用就是限制系统中执行线程的数量。根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。
为什么要用线程池:1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。
ExecutorService 真正的线程池接口。
ScheduledExecutorService 能和Timer/TimerTask类似,解决那些需要任务重复执行的问题。
ThreadPoolExecutor ExecutorService的默认实现。
ScheduledThreadPoolExecutor 继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。
1. newSingleThreadExecutor
创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
2.newFixedThreadPool
创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
3. newCachedThreadPool
创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,
那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
4.newScheduledThreadPool
创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
以上的所有底层实现都是用ThreadPoolExecutor这个来做的。具体的可以查看源代码
=================================================================================================================
Callable 与 Future 的应用
配合使用可以得到每个线程的返回值 Callable返回 Future接收
=================================================================================================================
java.util.concurrent.Lock :
共同点:Lock能够完成synchronized所实现的所有功能
不同点:Lock有比synchronized更精确的线程和更好的性能,并且提供了多样化的同步,比如有时间限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。
synchronized是在JVM层面上实现的,在代码执行时出现异常,JVM会自动释放锁定.但Lock不能,只能手动释放,并且在finally从句中释放
性能:在jdk1.5中Synchronized是使用的独占悲观锁,资源竞争不激烈的情况下synchronized性能略高,资源竞争激烈的情况下性能要远低于Lock。
jdk1.6后synchronized和lock一样都是使用CAS乐观锁操作,所以性能差不多,对于具体使用哪一个要看具体的系统应用需要,Synchronized相对简单易用,若需要精细的灵活控制则可以考虑选择lock。
Lock 也是采用compareAndSetState CAS 机制来实现锁的
ReentrantLock 互斥锁, 指的是一次最多只能有一个线程持有的锁. 在jdk1.5之前, 我们通常使用synchronized机制控制多个线程对共享资源的访问。而现在Lock提供了比synchronized机制更广泛灵活的锁定操作。
=================================================================================================================
ReadWriteLock 读写锁
ReentrantReadWriteLock 和 ReentrantLock 不是继承关系,但都是基于 AbstractQueuedSynchronizer 来实现。
注意:在同一线程中,持有读锁后,不能直接调用写锁的lock方法 ,否则会造成死锁。
读 写
读 允许 不允许
写 不允许 不允许
=================================================================================================================
Condition 可以实现可阻塞队列。可参考api里的经典列子
Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用
Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得 Condition 实例,请使用其 newCondition() 方法。
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
=================================================================================================================
自旋锁 :类似于while死循环,不断的判断表达式,判断是否还持有该把锁对象.
会导致死锁。如果长期持有自旋锁
独占悲观锁:synchronized
CAS乐观锁 :Lock
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。