Java中常用的线程操作方法
取得当前JVM中正在执行的线程对象
public static native THread currentThread();
线程名称的命名和取得
- public Thread(Runnable target, String name)
- public final synchronized void setName(String name)
- String getName()
线程休眠(线程暂缓执行一下)
立即交出CPU,不会释放对象锁 sleep(long time)
线程让步(暂停当前正在执行的线程对象,让拥有相同优先级的线程获取CPU)
不会立即交出CPU,不会释放对象锁 yield()
线程等待(在线程中加入一个线程,本线程阻塞,当加入的线程执行完,本线程恢复执行)
join()
线程停止
- 设置标志位(无法处理线程阻塞时停止的问题)
- 调用Thread类提供的stop方法强行关闭线程,但是会产生不完整数据
- 调用Thread类提供的interrupt():
- 若线程中没有使用类似sleep/wait/join时,调用此线程对象的interrupt方法并不会真正中断线程,只是简单地将线程的状态置为interrupt而已,根据此状态来进一步确定如何处理线程。
- 若线程中调用了阻塞线程的方法如:sleep()、wait()、join()此时再调用线程的interrupt方法时会抛出异常InterruptedException,同时将线程状态还原(isInterrupted = false)
Thread类提供的public boolean isInterrupted()可以检测当前线程状态是否为中断状态
线程优先级(线程优先级越高越有可能先执行,仅此而已)
- 设置优先级setPriority(int priority)
- 取得优先级int getPriority()
JDK内置了三种优先级
- MAX_PRIORITY = 10;
- NORM_PRIORITY = 5;
- MIN_PRIORITY = 1;
线程的继承性:
若在一个线程中创建了子线程,默认子线程与父线程的优先级相同。
守护线程(只要JVM存在任何一个用户线程没有终止,守护线程就一直在工作)
将线程对象设置为守护线程 setDaemon(true)
用户线程,默认创建的线程,包括主线程
守护线程:垃圾回收线程
线程的同步与死锁
同步:线程间通信(线程合作)
互斥:多线程并发时,某一时刻只能有一个线程访问共享资源
锁
一把锁保护一个相应资源,不同锁保护不同的对象/资源
锁的实现
使用synchronized关键字为程序逻辑上锁
synchronized的两种用法
- 同步代码块:synchronized(锁的对象|Object及其子类|类对象) {}
- 同步方法:public [static] synchronized method(){}
synchronized的应用
synchronized(this){} 锁的是当前对象,在任意时刻,只能有一个线程操作此对象,对别的对象不影响
synchronized(类对象) {} 锁的是当前类.class,在任意时刻,只能有一个线程操作当前类的一个对象,影响其他对象
synchronized修饰普通方法,锁当前对象,
synchronized修饰静态方法,锁当前类.class
如何保护毫无关系的资源?使用多把锁锁住不同的资源
class Account {
// 余额
int sal;
// 密码
String password;
// 余额资源的锁
private Object salLock = new Object();
// 密码资源的锁
private Object passLock = new Object();
public int getMoney() {
synchorinzed(salLock) {}
}
public void setMoney() {
synchorinzed(salLock) {}
}
public String getPassword() {
synchorinzed(passLock) {}
}
public void setPassword() {
synchorinzed(passLock) {}
}
}
如何保护有关联关系的对象(使用同一把锁):
public void zhuangzhang(Account target) {
synchroinzed(Account.class) {
this.sal -= 100;
target.sal += 100;
}
}
synchronized底层实现:
两个命令: 编译命令:javac -encoding utf-8 主类名称
反编译命令:javap -v 主类名称
对象锁Monitor机制
monitorenter:
- 检查对象的Monitor计数器值是否为0,为0表示此监视器还未被任意一个线程获取,此时线程可以进入同步代码块并且将Monitor值+1,将Monitor的持有线程标记为当前线程
- 当Monitor计数器值不为0且持有线程不是当前线程,表示Monitor已经被别的线程占用,当前线程只能阻塞等待。
- (可重入锁:)当执行MonitorEnter时。对象的Monitor计数器值不为0,但是持有线程恰好是当前线程,此时将Monitor计数器值再次+1,当前线程继续进入同步方法或代码块
monitorexit
- 当执行MonitorEnter时。对象的Monitor计数器值不为0,但是持有线程恰好是当前线程,此时将Monitor计数器值再次+1,当前线程继续进入同步方法或代码块
之所以采用这种计数器的方式,是为了同一个线程重复获取同一把锁,如果Java类中拥有多个synchronized方法,那么这些方法之间的相互调用,不管是直接还是间接的,都会涉及对同一把锁的重复加锁操作,因此,我们需要设计这个一个可重入的特性,来避免编程里的隐式约束
synchronized优化(JDK1.6之后)
主要思想:让每个线程通过同步代码块时速度提高,之前synchronized如果获取锁失败,就会将线程挂起(悲观锁策略),但线程启动需要占用大量资源
CAS操作(CompareAndSwap(V,O,N))
V:内存中该变量的具体值
O:当前线程存储的变量值
N:希望修改后的变量值
已知内存中的旧值,多个线程同时操作来修改他的值,若O==V则证明没有线程修改他的值,则直接修改为N,返回N,在接下来的线程尝试修改时,内存中的值已被修改为N,此时O!=V,返回内存中的最新值V。多个线程再次尝试修改变量(乐观锁)
存在的问题以及解决方式
1.ABA问题:添加版本号
2.自旋在CPU上跑无用指令,会浪费CPU资源:自适应式自旋
3.公平性问题(处于阻塞的线程可能无法获得到锁):lock锁实现公平锁
偏向锁:JDK1.6之后默认synchronized
最乐观的锁:进入同步块或同步方法的始终是一个线程
当出现另一个线程也尝试获取锁(在不同时刻)时,偏向锁会升级为轻量级锁
轻量级锁
不同时刻有不同的线程尝试获取锁,"亮黄灯策略"
同一时刻有不同线程尝试获取锁,会将偏向锁升级为重量锁
重量级锁
JDK1.6之前synchronized都是重量级锁,将线程阻塞挂起(JDK1.6自适应自旋)
锁只有升级过程没有降级过程
锁粗化
当出现多次连续的加锁与解锁过程,会将多次加减锁过程粗化为一次的加锁与解锁过程(StringBuffer)
锁消除
当对象不属于共享资源时,对象内部的同步方法或同步代码块的锁会被自动解除(StringBuffer)