多线程
java线程分为六个状态:
初始状态(new):新创建一个线程对象,但还没有调用start()方法。
运行状态(runnable):java线程中将就绪(ready)和运行中(running)两种状态合起来叫做运行。
阻塞状态(blocked):表示线程被锁阻塞了。
等待状态(waiting):表示线程需要等待其他线程做出一些特定动作(通知此线程或者终端)。
超时等待(time_waiting):这个状态不等于waiting,它可以定时来进行自动返回,这是等待做不到的。
终止(terminnated):表示线程执行完毕,结束线程。
![线程运行状态图](https://img-blog.csdn.net/20180813135745340?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyNzg0MTA1/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
1. 初始状态:实现runnable接口和继承Thread可以得到线程类,new一个实例出来,线程就进入了初始状态。
2. 就绪状态:
a)就绪状态只是说你有资格运行,调度程序没有挑选到你,你就永远是就绪状态。
b)调用现成的start()方法,此线程进入就绪状态。
c)当前线程sleep()方法结束,其他线程join()结束,等待用户输入完毕,某个线程拿到对象锁,这些线程也将进入就绪状态。
d)当前线程时间片用完了,调用当前线程的yield()方法,当前线程进入就绪状态。
e)锁池里的线程拿到对象锁后,进入就绪状态。
3.运行中状态:
线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。
4.阻塞状态:
阻塞状态时线程阻塞在进入synchronzied关键字修饰的方法或代码块(获取锁)时的状态。
5.终止状态
a)当线程的run()方法完成时,或者主线程main()方法完成时,我们就认为它终止了。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程了。线程一旦终止了,就不能复生。
b)在一个终止的线程上调用start()方法,或抛出java.lang.lllegalThreadStateException异常。
6.等待队列
a)调用obj中的wait(),notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj)代码段内。
b)与等待队列相关的步骤和图
1)线程1获取得到对象A的锁,正在使用对象A。
2)线程1调用对象A的wait()方法。
3)线程1释放对象A的锁,并且马上进入等待队列。
4)锁池中的对象争夺对象A的锁。
5)线程5获得对象A的锁,进入synchronized块,使用对象A。
6)线程5调用对象A的notifyAll()方法,唤醒所有的线程,所有线程进入同步队列。如果线程5调用A对象的notify()方法,那么唤醒一个线程,不知道会唤醒谁,被唤醒的线程进入同步队列。
7)notify()方法所在的synchronized结束,线程5释放对象A的锁。
8)同步队列的线程争抢对象锁,但是线程1什么时候抢到就未知了。
3. 那么什么是同步队列呢?
a)当前线程想调用对象A的同步方法时,发现对象A的锁被别的线程占有,此时当前线程进入同步队列。其实也就是说同步队列中的线程都是想争夺对象锁的线程。
b)当一个线程被另一个线程唤醒的时候,此线程进入同步队列,去争夺对象锁。
c)同步队列是在同步的环境下才有的概念,一个对象对应一个同步队列。
4. 简述几个常看见的方法:
a)Thread.sleep(long millis),一定是当前线程调用此方法,当前线程进入Time_waiting状态,但不释放对象锁,millis后线程自动苏醒进入就绪状态,作用:给其他线程执行机会的最佳方式。
b)Thread.yield(),一定是当前线程调用此方法,当前线程放弃获取的cpu时间片,由运行状态变会就绪状态,让OS再次选择线程。作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。
c)t.join()/t.join(long millis),当前线程里调用其它线程t的join方法,当前线程进入TIME_WAITING/TIME_WAITING状态,当前线程不释放已经持有的对象锁。线程t执行完毕或者millis时间到,当前线程进入就绪状态。
d)obj.wait(),当前线程调用对象的wait()方法,当前线程释放对象锁,进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout)timeout时间到自动唤醒。
e)obj.notify()唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程。
Lock锁
- synchronized和lock的优缺点
a)java中实现多线程下对象的同步访问方法:
synchronized关键字
Lock接口及其实现类ReentrantLock和读写锁ReentrantReadWritLock
b)差别:
1)使用synchronized关键字,锁的控制和释放实在synchronized同步代码块的开始位置和结束位置。而Lock实现同步时,锁的获取和释放可以在不同的代码块,不同的方法中。这一点是基于使用者手动获取和释放锁的特性。
2)lock接口提供了试图获取锁的trylock()方法,在调用tryLock()获取锁失败时候返回false,这样线程可以执行其他的操作,而不至于使线程进入休眠。trylock()方法可进入一个long型的时间参数,允许在一定时间内获取锁。
3)lock接口的实现类ReentrantReadWriteLock提供了读锁和写锁,允许多个线程获得读锁。而只能有一个线程获得写锁。读锁和写锁不能同时获得。这实现了读和写分离。
2.lock锁详解
Reentrantlock可以让代码块原子执行,但是比synchronized更加强大,Reentrantlock具有嗅探锁定,多路分支通知等功能。
嗅探锁定:获取锁的时候如果锁已经被其他线程获取到Reentrantlock可以进行指定等待时间获取锁或者多路分支通知。
多路分支通知:县城发生await时候,县城可以选择注册在不同的监听器Condition对象上,在适当的时候可以选择指定的监听器Condition对象上的线程进行signal通知,执行。
代码例子:
//让t3最后执行
public static void main(String[] args ) {
TargetLock t1 = new TargetLock();
Thread t1 = new Thread(t1,"哈哈");
Thread t2 = new Thread(t1,"嘻嘻");
Thread t3 = new Thread(t1,"呵呵");
t1.start();
t2.start();
t3.start();
}
lock实现代码:
public calss TargetLock implements Runnable {
//生成锁对象 上锁/解锁
//如果需要用到ReentrantLock等子类的特有的管理方法 需向下转型
private static ReentrantLock lock = new ReentrantLock();
//获取所对应的状态 等待/唤醒(单个/所有)
private static Condition cd = lock.newCondition();
public run() {
String ctName = Thread.currentThread().getName();
//上锁
lock.lock();
//由于在上锁期间可能出现太多的不可控制因素,导致线程中断或者损害
//而形成死锁状态,为了处理这类的问题导致的死锁,利用try..finally
//结构保证解锁操作一定会被执行
try{
//呵呵最后进入 之前进入的线程去等待队列
if(!ctName.equals("呵呵")) {
System.out.println(ctName + "去等待了...");
try {
cd.await();
} catch (InterruptedException e) {
e.printStcakTrace();
}
} else {
System.setProperty(ctName,"false");
}
// 如果呵呵已经完事 会唤醒所有等待线程 且需要线程之间通过不断争抢CPU来获取执行权
// 需在执行逻辑代码块之前 提前解锁
if (Boolean.getBoolean("呵呵")) {
if (lock.isLocked()) {
lock.unlock();
}
}
// 执行的逻辑代码块
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ctName + "在执行...");
}
// 执行完毕去唤醒一个线程
// cd.signal();
// 唤醒所有线程 --> 嘻嘻 和 哈哈相互打印
if (lock.isLocked()) {
cd.signalAll();
}
// 当呵呵完事 给呵呵一个标识
if (ctName.equals("呵呵")){
System.setProperty(ctName, "true");
}
} finally {
// 解锁
if (lock.isLocked()) {
lock.unlock();
}
}
}
}
ReentrantLock
//小程序例子
public class MyService {
private Lock lock = new ReentrantLock();
public void testMethod() {
lock.lock();
for(int i = 0; i < 5; i++) {
System.out.println("ThreadName=" + Thread.currentThread().getName() + (" " + (i + 1)));
}
lock.unlock();
}
}
//ReentrantLock()的wait方法
public class MyWait {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
try {
lock.lock();
System.out.println("开始wait");
condition.await();
for(int i = 0; i < 5; i++) {
System.out.println("ThreadName=" + Thread.currentThread().getName() + (" " + (i + 1)));
}
}catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
//使用signal方法唤醒wait线程
public void signal() {
try {
lock.lock();
condition.signal();
}catch (){
}finally {
lock.unlock();
}
}
一个condition对象的signal(signalAll)方法和该对象的await方法一一对应,不能唤醒其他condition对象的await方法。
ReentrantLock类可以唤醒指定条件的线程,而object的唤醒是随机的。
Condition和Object比较
Condition的await方法和Object的wait方法等效。
Condition的signal方法和Object的notify方法等效。
Condition的signalAll方法和Obkect的notifyAll方法等效。
lock的公平锁和非公平锁
//公平锁指的是线程获取锁的顺序是按照加锁顺序来的
Lock lock = new ReentrantLock(true);
//非公平锁是抢锁机制
Lock lock = new ReentrantLock(false);
**ReentrantLock的两种常用方法**
tryLock()能获得锁就返回true,不能就立即返回false
tryLock(long timeout,TimeUnit unit)可以增加时间机制,如果超过该时间段还没获得锁,就返回false。
lock()能获得锁就返回true,不能就一直等待获取锁。
两个线程分别执行lock()和lockInterruptibly(),中断两个线程,前者不会报异常,后者会抛出异常。
ReentrantLock是互斥排他的,效率并不高
//一个小例子说明
public void read() {
try {
try {
lock.readLock().lock();
System.out.println("获得读锁" + Thread.currentThread().getName()
+ " " + System.currentTimeMillis());
Thread.sleep(10000);
} finally {
lock.readLock().unlock();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void write() {
try {
try {
lock.writeLock().lock();
System.out.println("获得写锁" + Thread.currentThread().getName()
+ " " + System.currentTimeMillis());
Thread.sleep(10000);
} finally {
lock.writeLock().unlock();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Lock类也可以实现线程同步,而Lock获得锁需要执行lock方法,释放锁需要执行unLock方法。
Lock类可以创建Condition对象,Condition对象用来是线程等待和唤醒线程,需要注意的是Condition对象的唤醒的是用同一个Condition执行await方法的线程,所以也就可以实现唤醒指定类的线程。
Lock类分公平锁和不公平锁,公平锁是按照加锁顺序来的,非公平锁是不按顺序的,也就是说先执行lock方法的锁不一定先获得锁。
Lock类有读锁和写锁,读读共享,写写互斥,读写互斥。