大概这么4种 extends Thread; implRunnable; implCallable通过 FutureTask包装器来创建线程; 使用ExecutorService、Callable、Future实现有返回结果的多线 程。; extends Thread 和 implRunnable 的线程没有返回值, 而 FutureTask 和 ExecutorService(ExecutorService.submit(xxx) return Future<?> ) 有返回值.
第一是创建状态。在生成线程对象,并没有调用该对象的 start方法,这是线程处于创建状态。 第二是就绪状态。当调用了线程对象的start方法之后, 该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。在线程运行之后,从等待或者睡眠中回来之后,也会处于就绪 状态。 第三是运行状态。线程调度程序将处于就绪状态的线 程设置为当前线程,此时线程就进入了运行状态,开始运 行run函数当中的代码。 第四是阻塞状态。线程正在运行的时候,被暂停,通常 是为了等待某个时间的发生(比如说某项资源就绪)之后再继 续运行。sleep,suspend,wait等方法都可以导致线程阻塞。 第五是死亡状态。如果一个线程的run方法执行结束或 者调用stop方法后,该线程就会死亡。对于已经死亡的线程, 无法再使用start方法令其进入就绪。
多线程同步和异步不是一回事。 几种情况, 1.就是大家说的synchronized 他可以保证原子性,保证多个 线程在操作同一方法时只有一个线程可以持有锁,并且操作 该方法, 2.就是手动调用读写锁, 3.手动操作线程的wait和notify 4.volatile我记得是没有原子性的,他可以保证内存可见性, 在多线程的情况下保证每个线程的数据都是最新的
一、死锁的定义 多线程以及多进程改善了系统资源的利用率并提高了系统 的处理能力。然而,并发执行也带来了新的问题——死 锁。所谓死锁是指多个线程因竞争资源而造成的一种僵局 (互相等待),若无外力作用,这些进程都将无法向前推 进。 二、死锁产生的原因
/**
* 一个简单的死锁类
* 当DeadLock类的对象flag==1时(td1),先锁定o1,睡
眠500毫秒
* 而td1在睡眠的时候另一个flag==0的对象(td2)线程启
动,先锁定o2,睡眠500毫秒* td1睡眠结束后需要锁定o2才能继续执行,而此时o2已被
td2锁定;
* td2睡眠结束后需要锁定o1才能继续执行,而此时o1已被
td1锁定;
* td1、td2相互等待,都需要得到对方锁定的资源才能继续执
行,从而死锁。
*/
public class DeadLock implements Runnable { public int flag
= 1;
//静态对象是类的所有对象共享的
private static Object o1 = new Object(), o2
= new Object();
@Override
public void run() { System.out.println("flag=" +
flag); if (flag == 1) {
synchronized (o1) { try {
Thread.sleep(500);
} catch (Exception e)
{ e.printStackTrace();
}synchronized (o2)
{ System.out.println("1");
}
}
}
if (flag == 0)
{ synchronized (o2) {
try {
Thread.sleep(500);
} catch (Exception e)
{ e.printStackTrace();
}
synchronized (o1)
{ System.out.println("0");
}
}
}
}
public static void main(String[] args) {
DeadLock td1 = new DeadLock();
DeadLock td2 = new DeadLock();
td1.flag = 1;
td2.flag = 0;
//td1,td2都处于可执行状态,但JVM线程调度先执行哪
个线程是不确定的。
//td2的run()可能在td1的run()之前运行newThread(td1).start();
new Thread(td2).start();
}
}
在有些情况下死锁是可以避免的。三种用于避免死锁的技 术:
Thread 1:
lock A
lock B
Thread 2:
wait for A
lock C (when A locked)
Thread 3:
2. 加锁时限(线程尝试获取锁的时候加上一定的时限,超
过时限则放弃对该锁的请求,并释放自己占有的锁)
3. 死锁检测
加锁顺序
当多个线程需要相同的一些锁,但是按照不同的顺序加
锁,死锁就很容易发生。
如果能确保所有的线程都是按照相同的顺序获得锁,那么
死锁就不会发生。看下面这个例子:
wait for A
wait for B
wait for C
如果一个线程(比如线程3)需要一些锁,那么它必须按照确定的顺序获取锁。它只有获得了从顺序上排在前面的锁之 后,才能获取后面的锁。例如,线程2和线程3只有在获取了锁A之后才能尝试获取锁C(译者注:获取锁A是获取锁C的必要条件)。因为线程1已 经拥有了锁A,所以线程2和3需要一直等到锁A被释放。然 后在它们尝试对B或C加锁之前,必须成功地对A加了锁。按照顺序加锁是一种有效的死锁预防机制。但是,这种方式需要你事先知道所有可能会用到的锁(译者注:并对这些锁做适当的排序),但总有些时候是无法预知的。加锁时限另外一个可以避免死锁的方法是在尝试获取锁的时候加一个超时时间,这也就意味着在尝试获取锁的过程中若超过了这个时限该线程则放弃对该锁请求。若一个线程没有在给定的时限内成功获得所有需要的锁,则会进行回退并释放所有已经获得的锁,然后等待一段随机的时间再重试。这段随机的等待时间让其它线程有机会尝试获取相同的这些锁,并且让该应用在没有获得锁的时候可以继续运行(译者注:加锁超时后可以先继续运行干点其它事情,再回头来重复之前加锁的逻辑)。以下是一个例子,展示了两个线程以不同的顺序尝试获取 相同的两个锁,在发生超时后回退并重试的场景:
Thread 1 locks A
Thread 2 locks B
Thread 1 attempts to lock B but is blocked
Thread 2 attempts to lock A but is blocked
Thread 1's lock attempt on B times out
Thread 1 backs up and releases A as well
Thread 1 waits randomly (e.g. 257 millis)
before retrying. Thread 2's lock attempt on A times out
Thread 2 backs up and releases B as well
Thread 2 waits randomly (e.g. 43 millis)
before retrying.
在上面的例子中,线程2比线程1早200毫秒进行重试加锁,因此它可以先成功地获取到两个锁。这时,线程1尝试获取锁A并且处于等待状态。当线程2结束时,线程1也可以顺利的获得这两个锁(除非线程2或者其它线程在线程1成功获得两个锁之前又获得其中的一些锁)。需要注意的是,由于存在锁的超时,所以我们不认为这种场景就一定是出现了死锁。也可能是因为获得了锁的线程(导致其它线程超时)需要很长的时间去完成它的任 务。此外,如果有非常多的线程同一时间去竞争同一批资源,就算有超时和回退机制,还是可能会导致这些线程重复地尝试但却始终得不到锁。如果只有两个线程,并且重试的超时时间设定为0到500毫秒之间,这种现象可能不会发生,但是如果是10个或20个线程情况就不同了。因为这些线程等待相等的重试时间的概率就高的多(或者非常接近以至于会出现问题)。(译者注:超时和重试机制是为了避免在同一时间出现的竞争,但是当线程很多时,其中两个或多个线程的超时时间一样或者接近的可能性就会很大,因此就算出现竞争而导致超时后,由于超时时间一样,它们又会同时开始重试, 导致新一轮的竞争,带来了新的问题。)这种机制存在一个问题,在Java中不能对synchronized同步块设置超时时间。你需要创建一个自定义锁,或使用Java5中java.util.concurrent包下的工具。写一个自定义锁类不复杂,但超出了本文的内容。后续的Java并发系列会涵盖自定义锁的内容。 死锁检测 死锁检测是一个更好的死锁预防机制,它主要是针对那些不可能实现按序加锁并且锁超时也不可行的场景。每当一个线程获得了锁,会在线程和锁相关的数据结构中(map、graph等等)将其记下。除此之外,每当有线程 请求锁,也需要记录在这个数据结构中。当一个线程请求锁失败时,这个线程可以遍历锁的关系图 看看是否有死锁发生。例如,线程A请求锁7,但是锁7这个时候被线程B持有,这时线程A就可以检查一下线程B是否已经请求了线程A当前所持有的锁。如果线程B确实有这样的请求,那么就是发生了死锁(线程A拥有锁1,请求锁7; 线程B拥有锁7,请求锁1)。当然,死锁一般要比两个线程互相持有对方的锁这种情况 要复杂的多。线程A等待线程B,线程B等待线程C,线程C 等待线程D,线程D又在等待线程A。线程A为了检测死锁, 它需要递进地检测所有被B请求的锁。从线程B所请求的锁开始,线程A找到了线程C,然后又找到了线程D,发现线程D请求的锁被线程A自己持有着。这是它就知道发生了死锁。下面是一幅关于四个线程(A,B,C和D)之间锁占有和请求 的关系图。像这样的数据结构就可以被用来检测死锁。
那么当检测出死锁时,这些线程该做些什么呢?一个可行的做法是释放所有锁,回退,并且等待一段随机的时间后重试。这个和简单的加锁超时类似,不一样的是只有死锁已经发生了才回退,而不会是因为加锁的请求超时了。虽然有回退和等待,但是如果有大量的线程竞争同一批锁,它们还是会重复地死锁(编者注:原因同超时类似,不能从根本上减轻竞争)。一个更好的方案是给这些线程设置优先级,让一个(或几个)线程回退,剩下的线程就像没发生死锁一样继续保持着它们需要的锁。如果赋予这些线程的优先级是固定不变的,同一批线程总是会拥有更高的优先级。为避免这个问题,可以在死锁发生的时候设置随机的优先级。
run() 和普通的成员方法一样,可以被重复调用。但是如果单独调用 run 方法,则不是在子线程中执行。start()这个方法只能被调用一次。调用这个方法后 程序会启动一个 新的线程来 执行 run 方法。注意 :调用start 后,线程处于可运行状态(并没有运行),一旦得到 cup 时间片,就开始执行run 方法,run 方法结束后,线程则立即终止。
1.volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。 2.volatile仅能使用在变量级别;synchronized则可以使用 在变量、方法、和类级别的volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性 3.volatile不会造成线程的阻塞;synchronized可能会造成 线程的阻塞。 4.volatile标记的变量不会被编译器优化;synchronized标 记的变量可以被编译器优化
当多个线程要共享一个实例对象的值得时候,那么在考虑安全的多线程并发编程时就要保证下面3个要素:
原子性(Synchronized, Lock)
有序性(Volatile,Synchronized, Lock) 可见性(Volatile,Synchronized,Lock)
当然由于synchronized和Lock保证每个时刻只有一个线程执行同步代码,所以是线程安全的,也可以实现这一功能,但是由于线程是同步执行的,所以会影响效率。 下面是对3个要素的详细解释: 原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。在Java中,基本数据类型的变量的读取和赋值操作是原子 性操作,即这些操作是不可被中断的,要么执行,要么不执行。
可见性:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。当一个共享变量被volatile修饰时,它会保证修改的值会立 即被更新到主存,当有其他线程需要读取共享变量时,它会去内存中读取新值。普通的共享变量不能保证可见性,因为普通共享变量被修改后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。更新主存的步骤:当前线程将其他线程的工作内存中的缓存变量的缓存行设置为无效,然后当前线程将变量的值跟新到主存,更新成功后将其他线程的缓存行更新为新的主存地址其他线程读取变量时,发现自己的缓存行无效,它会等待缓存行对应的主存地址被更新之后,然后去对应的主存读取最新的值。
有序性:即程序执行的顺序按照代码的先后顺序执行。 在Java内存模型中,允许编译器和处理器对指令进行重排 序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。可以通过volatile关键字来保证一定的“有序性”。当在处理并发编程的时候,只要程序满足了原子性,可见性和有序性,那么程序就不会发生脏数据的问题。
ThreadLocal用于保存某个线程共享变量:对于同一个staticThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。
1、ThreadLocal.get: 获取ThreadLocal中当前线程共享变量的 值。 2、ThreadLocal.set: 设置ThreadLocal中当前线程共享变量的 值。 3、ThreadLocal.remove: 移除ThreadLocal中当前线程共享变量的值。
4、ThreadLocal.initialValue: ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法, 返回此方法值。
notify 方 法 1.方法调用之前由于也需要获取该对象的锁,所以使用的位置: synchronized方法中或者synchronized代码块中 2.通知那些可能等待该对象的对象锁的其他线程。如果有多个线程等待,则线程规划器任意挑选出其中一个wait() 状 态 的 线 程 来 发 出 通 知 3.wait()方法执行完毕之后,会立刻释放掉锁,如果没有再 次使用notify,其他wait()的线程由于没有得到通知,会继续阻塞在wait()的状态,等待其他对象调用notify或者notifyAll来唤醒。 notifyAll方法 1.使用位置和notify一样 2.notifyAll唤醒所有处于wait的线程
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。