线程
轻量进程,程序执行流的最小单元。
线程有3个基本状态:执行、就绪、阻塞
线程有5种基本操作:派生、阻塞、激活、 调度、 结束
线程三大特性
多线程有三大特性,原子性、可见性、有序性
原子性:保证数据一致性,线程安全。
可见性:对另一个线程是否课件
有序性:线程之间执行有顺序
创建多线程有两种方法:
1.继承Thread,重写run方法
2.实现Runnable接口,重写run方法
3.实现 Callable 接口;(忽略)
一般我们使用实现Runnable接口,Java 不支持多重继承,因此继承了 Thread 类就无法继承其它类,但是可以实现多个接口。可以避免java中的单继承的限制,应该将并发运行任务和运行机制解耦,因此我们选择实现Runnable接口这种方式。
run():仅仅是封装被线程执行的代码,直接调用是普通方法
start():首先启动了线程,然后再由jvm去调用该线程的run()方法。
线程状态:
方法:
获取线程名称: getName();
线程的强制运行: .join() 方法 在线程中调用另一个线程的 join() 方法,会将当前线程挂起,而不是忙等待,直到目标线程结束。
线程的休眠: .sleep()
中断线程: .interrupt() 如果该线程处于阻塞、限期等待或者无限期等待状态,那么就会抛出 InterruptedException,从而提前结束该线程
线程的礼让 .yield() 声明了当前线程已经完成了生命周期中最重要的部分,可以切换给其它线程来执行
等待 .wait()
唤醒 notify() 或者 notifyAll()
setDaemon() 守护线程 :只要任何非守护线程还在运行,程序就不会终止(ex: thread.setDaemon(true)
wait() 和 sleep() 的区别:
- wait() 是 Object 的方法,而 sleep() 是 Thread 的静态方法;
- wait() 会释放锁,sleep() 不会。
同步和异步:
同步和异步通常用来形容一次方法调用。同步方法调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为。异步方法调用更像一个消息传递,一旦开始,方法调用就会立即返回,调用者可以继续后续的操作。
Java多线程加锁机制,有两种:
- Synchronized
- 显式Lock
两种锁机制来控制多个线程对共享资源的互斥访问:
互斥同步:两种方法
1.JVM 实现的 synchronized
2.JDK 实现的 ReentrantLock (Lock接口的实现类:ReentrantLock)
synchronized:
(1).同步代码方法
synchronized 方法返回值 方法名称(参数列表){}
public synchronized void func () {
// ...
}
(2).同步代码块synchronized () { }解决
好处是解决多线程安全问题但是弊端是多个线程需要判断锁,较为消耗资源
ex:
public void func() {
synchronized (this) {
// ...try
}
}
synchronized是一种互斥锁(一次只能允许一个线程进入被锁住的代码块),synchronized取得的锁都是对象锁,而不是把一段代码或方法当做锁。
synchronized用处是什么?
synchronized保证了线程的原子性。(被保护的代码块是一次被执行的,没有任何线程会同时访问)
synchronized还保证了可见性。(当执行完synchronized之后,修改后的变量对其他的线程是可见的)
一般我们用来修饰三种东西:修饰普通方法、修饰代码块、修饰静态方法
(3)案例:线程安全的懒汉式 DLC
/*private modeltwo() {
}
private static volatile modeltwo m = null;
private static modeltwo getInstanse() {
if (m == null) {
synchronized (modeltwo.class) {
if(m==null)
m = new modeltwo();
}
}
return m;
}*/
同步函数的锁是this
静态同步函数的锁是class类
ReentrantLock :
ReentrantLock 是 java.util.concurrent(J.U.C)包中的锁,Lock接口的实现类:ReentrantLock
public class LockExample {
private Lock lock = new ReentrantLock();
public void func() {
lock.lock();
try {
for (int i = 0; i < 10; i++) {
System.out.print(i + " ");
}
} finally {
lock.unlock(); // 确保释放锁,从而避免发生死锁。
}
}
}
死锁原因以及解决方法
91)线程之间交错执行
解决:以固定的顺序加锁
(2)执行某方法时就需要持有锁,且不释放
解决:缩减同步代码块范围,最好仅操作共享变量时才加锁
(3)永久等待
解决:使用tryLock()定时锁,超过时限则返回错误信息
J.U.C 一个包,中包含AQS
AQS:LOCK基于AQS实现,AQS是一个给予实现同步锁、同步器的一个框架
线程池:
线程池可以看做是线程的集合,线程池给这个请求分配一个空闲的线程,任务完成后回到线程池中等待下次任务(而不是销毁)。这样就实现了线程的重用。
用的最多的一种线程池:ThreadPoolExecutor
三个同步工具类:https://segmentfault.com/a/1190000015785789
(1)CountDownLatch(闭锁)
某个线程等待其他线程执行完毕后,它才执行(其他线程等待某个线程执行完毕后,它才执行
(2)CyclicBarrier(栅栏)
一组线程互相等待至某个状态,这组线程再同时执行。
(3)Semaphore(信号量)
控制一组线程同时执行
(1)
CountDownLatch(闭锁):
ex:
public static void main(String[] args) {
Final CountDownLatch countDownLatch =new CountDownLatch(5);
System.out.println("a");
//启动
new Thread(new Runnable() {
public void run() {
try{
countDownLatch.await();
}catch(Exception e){
e.printStackTrace();
}
System.out.println("b");
}
}
).start();
for(int x=0;x<5;x++){
new Thread(new Runnable(){
public void run() {
System.out.println("c");
countDownLatch.countDown();
}
}
).start();
}
}
(2)CyclicBarrier
public static void main(String[] args) {
final CyclicBarrier cyclicBarrier = new CyclicBarrier(2);
for(int i=0;i<2;i++){
new Thread(new Runnable() {
public void run() {
String name = Thread.currentThread().getName();
if (name.equals("Thread-0")) {
name="a";
} else {
name="b";
}
System.out.println(name+"-c");
try {
cyclicBarrier.await();
System.out.println("跟" + name + "去夜上海吃东西~");
cyclicBarrier.await();
System.out.println("跟" + name + "去夜上海吃东西~");
cyclicBarrier.await();
System.out.println("跟" + name + "去夜上海吃东西~");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}).start();
}
}
(3)Semaphore
public static void main(String[] args) {
int count = 20;
final Semaphore semaphore = new Semaphore(10);
for (int i = 0; i < count; i++) {
new Thread( new Runnable() {
public void run() {
try{
semaphore.acquire();
System.out.println("a");
Thread.sleep(1000);
System.out.println("b");
semaphore.release();
}catch(Exception e){
e.printStackTrace();
}
}
}).start();
}
}
线程安全:
线程安全有以下几种实现方式:
1.不可变
不可变(Immutable)的对象一定是线程安全的,不需要再采取任何的线程安全保障措施
(final 关键字修饰的基本数据类型、String、枚举类型。。。)
2.互斥同步
synchronized 和 ReentrantLock。
3.非阻塞同步(CAS、AtomicInteger、ABA )
4.无同步方案(栈封闭、线程本地存储、可重入代码)
多线程开发良好的实践
-
给线程起个有意义的名字,这样可以方便找 Bug。
-
缩小同步范围,从而减少锁争用。例如对于 synchronized,应该尽量使用同步块而不是同步方法。
-
多用同步工具少用 wait() 和 notify()。首先,CountDownLatch, CyclicBarrier, Semaphore 和 Exchanger 这些同步类简化了编码操作,而用 wait() 和 notify() 很难实现复杂控制流;其次,这些同步类是由最好的企业编写和维护,在后续的 JDK 中还会不断优化和完善。
-
使用 BlockingQueue 实现生产者消费者问题。
-
多用并发集合少用同步集合,例如应该使用 ConcurrentHashMap 而不是 Hashtable。
-
使用本地变量和不可变类来保证线程安全。
-
使用线程池而不是直接创建线程,这是因为创建线程代价很高,线程池可以有效地利用有限的线程来启动任务
ThreadLocal:
ThreadLocal设计的目的就是为了能够在当前线程中有属于自己的变量,并不是为了解决并发或者共享变量的问题
ThreadLocal 的作用和目的:用于实现线程内的数据共享,即对于相同的程序代码,多个模块在同一个
线程中运行时要共享一份数据,而在另外线程中运行时又共享另外一份数据