赞
踩
1.执行完run()或者call()方法自动退出
2.抛出未捕获的Exception或者错误
3.stop(不推荐)
这个方法是不安全的,已被弃用(此外,Thread中被弃用的还有suspend、resume等
)
原因:调用 stop() 方法会立刻停止 run() 方法中剩余的全部工作,且会立即释放线程持有的所有锁
,这可能会导致对象处于不一致状态
以银行转账为例,从A账户向B账户转账500元,第一步是从A账户中减去500元,假如到这时线程就被stop了,那么这个线程就会释放它所取得锁,然后其他的线程继续执行,这样A账户就莫名其妙的少了500元而B账户也没有收到钱。这就是stop方法的不安全性
4.interupt()方法
interrupt只是设置线程的中断标志位
(这里是终止的含义,不是陷入内核的中断),不会强制线程立即终止,最终是否终止由线程执行决定(也就是可以由编写线程的人访问中断标志位并决定是否要退出)。
public class Thread implements Runnable { public static native void yield(); public static void sleep(long millis, int nanos); public synchronized void start(); public void run(); public void interrupt(); public static boolean interrupted(); private native boolean isInterrupted(boolean ClearInterrupted); public final native boolean isAlive(); public final void join(); public final void stop(); //弃用 public void destroy(); //弃用 public final void suspend(); //弃用 public final void resume(); //弃用 }
1. sleep是Thread的方法; wait是Object的方法;
2. sleep不会释放锁; wait会释放锁
;
3. 等待时间不同…
…
提交
与执行
解耦 => 也就是说,执行execute()时仅仅提交任务到队列,并不一定立即开始执行
…public interface Executor {
// 并不代表立即执行线程,只是放入队列
void execute(Runnable command);
}
生命周期
问题,在Executor的基础上添加了一些有用的生命周期管理方法;提交具有返回值的任务
,新增了submit()方法 => 详见Callable与Futurepublic interface ExecutorService extends Executor {
void shutdown();
List<Runnable> shutdownNow();
boolean isShutdown();
boolean isTerminated();
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
.....
}
解决Runnbale中的run()方法不能抛出异常、没有返回值的情况
=>public interface Callable<V> {
V call() throws Exception;
}
Future表示一个任务的生命周期
,并提供相应的方法来判断是否已经完成或取消,以及获取任务的结果
和取消任务等。接口如下:public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
get会阻塞直到任务完成
ExecutorService threadPool=Executors.newXXXThreadPool();
创建一个固定数量的线程池
,每提交一个任务时就创建一个线程,直到达到最大数量;根据需求动态调整线程数量
!!以延迟或定时的方式来执行任务
单线程的线程池
,能确保依照任务在队列中的顺序来串行执行参考资料:管程:并发编程的基石
Java多线程–Monitor对象(一)
对一个变量执行unlock操作之前,必须先将此变量同步回主内存来实现可见性
一个变量在同一时刻只允许一个线程对进行lock操作
保证有序性不可变性
使共享变量不能被修改,没有线程安全问题,他对共享变量的访问等价意义上是原子的绝对线程安全
代码本身封装了所有必要的正确性保证手段(如同步互斥),调用者无需关心多线程下的调用问题,更无需自己实现任何措施来保证多线程环境下的正确调用.
相对线程安全
绝对线程安全很难实现,java中绝大部分线程安全都是说的相对线程安全 => 只能保证对象的单次调用是线程安全的
比如:对于一个Vector对象,一个线程进行remove(i),一个线程执行get(i),可能在某个时刻T1先获得i => T2线程remove(i) => T3线程get(i)则会出现问题。可见Vector的线程安全仅仅只能保证单次调用remove/get是安全的!
阻塞同步意味和已获得共享变量的线程会尚未阻塞未获得共享变量的线程 => 这是一种悲观的并发策略
重量级的
,因为JVM使用操作系统的原生线程,阻塞后续进入的线程会导致用户态到内核态切换,开销较大锁绑定多个条件
:在synchronized中,所对象的wait()和notify()方法配合可以实现一个隐含的条件,如果要和多于一个的条件关联,就得额外添加锁;而一个ReentrantLock对象可以同时绑定多个Condition对象不断进行冲突检测,如果没有其他线程争用共享变量,则使用它;不需要阻塞线程,这是一种乐观的并发策略;基于处理器的特殊指令实现,典型的是test-and-set、compare-and-swap
CAS(V,A,B),如果内存地址V处的值与A值相等,则将V处的值修改为B
,这是一个原子操作for(;;){
int current=get(); // 旧值A(预期值)
int next=current+1; // 新值B
if(compareAndSet(current,next))
....
}
版本号机制
:每次修改数据时跟新版本号
,在上面的例子中,T2第一次看到A的版本号与第二次看到A的版本号不同…volatile不能保证保证原子性
修改volatile变量大概分为四步:
1)数据读取到寄存器
2)修改变量值
3)修改后的值写回(到寄存器或者工作内存)
4)插入内存屏障,即lock指令,让其他线程可见
前三步都是不安全的,取值和写回之间,不能保证没有其他线程修改。原子性需要锁来保证
。
volatile保证可见性
主要通过两条规则保证可见性:
1.变量修改后立即
将新值同步回主内存(普通变量虽然会同步回主内存但不是立即!);
2.使用前立即
从主内存刷新
注意:可见性是说变量第一时间同步到主内存,但并不能保证变量更新后立即被其他线程感知到,因为更新操作不一定是原子的,可能还没有执行同步到主存的指令…
如何实现的: 修改执行完成后,插入cpu指令lock addl $0x0,(%esp)
=> 它的作用是:将本处理器的缓存写入内存…
volatile保证有序性
volatile禁止指令重排序,从而保证了有序性
如何实现的: 修改执行完成后,插入cpu指令lock addl $0x0,(%esp)
=> 由于指令重排序必须保证有依赖数据的指令能得到正确的数据,对于lock addl $0x0,(%esp)而言,要得到正确的数据,则必须要先完成变量的正确修改(就不能在lock前重排序),从而自动保证了变量没有重排序 (似乎没有解释清楚??)
其他知识点
1.被volatile关键字修饰的对象作为类变量或实例变量时,其对象中携带的类变量和实例变量也相当于被volatile关键字修饰了
=> 参考volatile关键字修饰对象是什么效果?
2.volatile只能保证可见性、有序性;而snchronized三者都能保证!
锁住的是对象
锁住的是类
=> 对象仍然可以调用普通方法synchronized(A.class)锁住的是类
;synchronized(obj)锁住的是对象
;monitor
(存在于每个对象的对象头中
,这也是为什么任意对象可以作为锁的原因);ACC_SYNCHRONIZED
,JVM据此执行相应的同步调用synchronized中,锁对象的wait()和notify()方法配合只可以实现一个隐含条件
;而ReentrantLock只需多次调用newCondition()即可绑定多个条件使用举例
// 存储了每个线程的LOCAL class MyThreadLocal{ static final ThreadLocal<Integer> LOCAL=new ThreadLocal<>(); } class MyThread implements Runnable{ String name; Integer id; MyThread(String name, Integer id){ this.name=name; this.id=id; } @Override public void run() { MyThreadLocal.LOCAL.set(id); System.out.println("thread "+id+" set local:"+id); try{ Thread.sleep(100); } catch (InterruptedException e){ e.printStackTrace(); } Integer local=MyThreadLocal.LOCAL.get(); System.out.println("thread "+id+" get local:"+local); } } public class ThreadLocalTest { public static void main(String[] args){ for(int i=0;i<5;i++){ Thread t=new Thread(new MyThread(String.valueOf(i),i)); t.start(); } System.out.println("end!"); } }
即同一个变量,所有线程都可以设置,但是它们设置的只是属于自己的副本
概述
从表面上看ThreadLocal相当于维护了一个map,key就是当前的线程,value就是需要存储的对象 => 然而这是不正确的!
实际上是ThreadLocal的静态内部类ThreadLocalMap为每个Thread都维护了一个数组table,ThreadLocal确定了一个数组下标,而这个下标就是value存储的对应位置
每个线程持有一个ThreadLocalMap对象。每一个新的线程Thread都会实例化一个ThreadLocalMap并赋值给成员变量threadLocals
源码解读!!!
Thread
class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}
ThreadLocal
public class ThreadLocal<T> { public void set(T value) { Thread t = Thread.currentThread(); // 获取当前线程的变量threadLocals !!! ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); // 会为当前线程创建threadLocals } ThreadLocalMap getMap(Thread t) { return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { T result = (T)e.value; return result; } } return setInitialValue(); } public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); } }
ThreadLocalMap => 它是ThreadLocal的静态内部类!
static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } // 存储所有数据! => 不同于HashMap,这里解决冲突使用的是开放定址法 private Entry[] table; private void set(ThreadLocal<?> key, Object value) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i];e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); if (k == key) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } // 要插入的元素放入不冲突的位置i tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); } private Entry getEntry(ThreadLocal<?> key) {.....} }
总结
1.对于每个看似共享的变量ThrealLocal< T>(如上图的ThreadLocal1、ThreadLocal2);
2.每个线程将它作为Key存储在自己的hash表中
(也就是同一个ThreadLocal对象同时在多个线程的hash表中作为key);
3.ThrealLocal< T>进行get、set时:先获得线程对象 => 然后获得线程自己的hash表 => 然后以ThrealLocal< T>自己为key进行查找...
4.需注意:每个线程自己的ThreadLocalMap是开放定址法实现的hash表,不同于HashMap的拉链法
ThreadLocal的内存泄漏问题
根据static class Entry extends WeakReference<ThreadLocal<?>>
=> ThreadLocalMap中使用的key是ThreadLocal的弱引用,而val是强引用
,所以如果ThreadLocal如果没有被外部强引用的时候,key就会被垃圾回收,而val是强引用则无法被回收,从而造成内存泄漏
=> 所以:最好手动清理掉key为null的记录
(调用remove方法)
参考资料
一文彻底搞懂面试中常问的各种“锁”
读数据:读数据时总是认为没有其它线程并发地修改数据,所以总是不上锁;
写数据:写数据时需要判断是否有其他线程并发地修改数据…;
两种实现方式:版本号机制、CAS
适用场景:读多写少
举例:JUC.atomic下的原子变量类就是使用CAS实现乐观锁的
读数据:会上锁 => 总是认为有其它线程并发地修改数据,所以要上锁;
写数据:也会上锁;
适用场景:写多读少;
举例:数据库
中的行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁,可看做悲观锁; Java
中的synchronized总是会加锁,属于悲观锁;
概念:若线程T1想要获取一个锁S,但是另一个线程T2尚未释放锁S。于是线程T1不断循环,直到能够获得锁S,这种循环加锁的方式称为自旋锁。
适用场景:(适用)锁的竞争不激烈,线程T1只需要自旋少许时间便可获获得锁,这样能避免T1阻塞再被唤醒的开销;若锁的竞争过于激烈, T1等待时间过长,则会消耗过多的cpu;
举例:Java中哪些是自旋锁??
疑问:什么才是自旋锁?是T1要获取的锁S吗?还是循环等待的过程称为锁???
每个Java对象都有一个内部锁
(语言内置的),synchronized使用的就是对象的内部锁完成同步的。作用于静态方法时
,锁住的是Class => 因为Class相关数据存储在永久代,这块区域被共享,因此静态方法上的同步锁相当于类的全局锁,会锁所有调用该方法的线程。数据库中也有乐观锁、悲观锁。
见线程安全实现方式…
对于一个锁而言,同一个线程可以反复(递归)的请求并持有它
当两个或者两个以上的线程共享对统一数据的存取时,数据的修改/访问不一定能达到预期,这取决于线程访问数据的次序
=> 这就是竞态条件
1.先检查后执行
即通过一个可能已经失效的观测
结果来决定下一步该做什么,典型的就是懒汉式单例:
public class Singleton {
private Singleton instance;
public Singleton getInstance(){
if(instance == null) { //1:读取instance的值
instance = new Singleton(); //2:实例化instance
}
return instance;
}
}
可能的情况是线程A发现instance为null,准备创建但是尚未创建对象的时候,线程B也发现instance为null => 于是最终A、B都创建了新对象!
2.读取-修改-写入
例如并发地递增一个共享的计数器
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。