多线程
Thread类的常用方法:
currentThread(), isAlive()判断当前线程是否存活。 sleep(long millis)方法
StackTraceElement[] getStackTrace() 返回一个表示该线程堆栈跟踪元素数组。
Static void dumpStack() 是将当前线程的堆栈跟踪信息输出至标准错误流;
Static Map<Thread,StackTraceElement[]> getAllStackTrace()
getId() 用于获取线程的唯一标识。
使正在运行的一个线程终止运行:
1.使用退出标志,结合return()方法使用;
2.使用stop()方法强行退出,不建议使用;
3.使用interrupt()方法中断线程,interrupt()方法不会终止一个正在运行的线程,还需要加入一个判断才可以完成线程的停止。
判断线程是否停止:interrupted()判断当前线程是否已经是中断状态
isInterrupted()判断线程Thread对象是否是中断状态;
先判断线程对象是否是中断状态,然后可以通过throw一个新的异常来是线程停止运行;
示例代码:
public class Interrupt extends Thread{
@Override
public void run() {
super.run();
try {
for (int i = 0; i < 500000; i++) {
//判断是否处于中断状态
if (this.isInterrupted()){
System.out.println("发现中断标志,我要退出了");
//通过抛出一个异常结束当前线程
throw new Exception("不好意思,我真的退出啦!");
}
System.out.println("i=" + (i +1));
}
System.out.println("for后边的代码继续执行");
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args){
try {
Interrupt thread = new Interrupt();
thread.start();
Thread.sleep(200);
thread.interrupt();
} catch (Exception e) {
e.printStackTrace();
}
}
}
暂停线程:
使用suspend()方法暂停线程,使用resume()方法恢复线程的执行。
- suspend()方法用于使线程不在执行任务,线程对象并不销毁;
- 极易造成公共同步对象被独占
wait(), notify(), notifyAll()
yield()的作用是 放弃当前CPU资源,让其他任务占用CPU执行时间,放弃的时间不确定,有可能刚刚放弃,马上又获得CPU时间片。
线程优先级
setPriority(),设置线程的优先级;java中线程的优先级分为1~10级,如果优先级小于1或者大于10就throw new IllegalArgumentException()
线程的优先级具有继承性,A线程启动B线程,则B线程的优先级与A线程是一样的。
线程的优先级与代码执行顺序无关;
获取当前线程的优先级:Thread.currentThread().getPriority();
守护线程:随着主线程的销毁而自动销毁,通过setDaemon(true)的方法来设置,必须在start()方法之前执行,否则会出现异常;
线程安全
1、方法中的变量具有私有特性,不存在非线程安全问题
public class MainThread {
public static void main(String[] args) {
MyService myService = new MyService();
new ThreadA(myService).start();
new ThreadB(myService).start();
}
}
class MyService{
public void add(String userName){
try {
int num = 0;
if (userName.equals("a")){
num = 100;
System.out.println("a set over");
Thread.sleep(2000);
}else {
num = 200;
System.out.println("b set over");
}
System.out.println(userName + " num " + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class ThreadA extends Thread{
private MyService myService;
public ThreadA(MyService myService){
this.myService = myService;
}
@Override
public void run() {
super.run();
myService.add("a");
}
}
class ThreadB extends Thread{
private MyService myService;
public ThreadB(MyService myService){
this.myService = myService;
}
@Override
public void run() {
super.run();
myService.add("b");
}
}
2、如果多个变量共同访问同一个对象中的实例变量,则有可能出现非线程安全问题。
线程安全问题:指对同一资源或者同一数据进行并发操作时。
将上边Myservice的代码修改为:
class MyService{
private int num = 0;
public void add(String userName){
try {
if (userName.equals("a")){
num = 100;
System.out.println("a set over");
Thread.sleep(2000);
}else {
num = 200;
System.out.println("b set over");
}
System.out.println(userName + " num " + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Synchronized锁
3、同步方法
将上边的Myservice修改为:
class MyService{
private int num = 0;
public synchronized void add(String userName){
try {
if (userName.equals("a")){
num = 100;
System.out.println("a set over");
Thread.sleep(2000);
}else {
num = 200;
System.out.println("b set over");
}
System.out.println(userName + " num " + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
synchronized在字节码指令中的原理,同步方法是使用了flag标记ACC_SYNCHRONIZED,当调用方法时,指令会检查方法的ACC_SYNCHRONIZED访问标志是否设置,如果设置了,执行线程先持有同步锁然后执行方法,最后完成任务后释放锁。
使用synchronized同步代码块:monitorenter和monitorexit指令进行不同处理。
4、多个线程对不同业务实例对象进行访问的时候不存在线程安全问题,因为持的锁不是同一把锁。
将上边MainThread修改如下:
public class MainThread {
public static void main(String[] args) {
MyService myService1 = new MyService();
MyService myService2 = new MyService();
new ThreadA(myService1).start();
new ThreadB(myService2).start();
}
}
创建了两个实例对象,相当于创建了两把不同的锁,每个线程执行自己业务对象中的同步方法,不存在线程安全问题。
5、多个线程执行同一个业务对象中的不同同步方法时,是按顺序同步的方式调用的。(因为锁对象是一样的)
public class MainThread {
public static void main(String[] args) {
MyService myService = new MyService();
new ThreadA(myService).start();
new ThreadB(myService).start();
}
}
class MyService{
private int num = 0;
public synchronized void add(String userName){
try {
if (userName.equals("a")){
num = 100;
System.out.println("a set over");
Thread.sleep(2000);
}else {
num = 200;
System.out.println("b set over");
}
System.out.println(userName + " num " + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void methodB(){
System.out.println("b method开始执行了");
}
}
class ThreadA extends Thread{
private MyService myService;
public ThreadA(MyService myService){
this.myService = myService;
}
@Override
public void run() {
super.run();
myService.add("a");
}
}
class ThreadB extends Thread{
private MyService myService;
public ThreadB(MyService myService){
this.myService = myService;
}
@Override
public void run() {
super.run();
myService.methodB();
}
}
多个线程调用同一个对象中的不同名称的synchronized同步方法或synchronized(this)同步代码块时,调用的效果是按顺序执行,即同步的。
6、synchronized可重入锁:一个线程得到一个对象锁后,再次请求此对象锁时还是可以得到该对象锁的;也就是synchronized方法和代码块的内部调用本类的其他synchronized方法和代码块时,是可以获得到锁的。前提是锁的对象要相同。
public class MainThread {
public static void main(String[] args) {
MyService myService = new MyService();
Runnable runnable = new Runnable() {
@Override
public void run() {
myService.methodA();
}
};
new Thread(runnable).start();
}
}
class MyService{
public synchronized void methodA(){
System.out.println("methodA");
methodB();
}
public synchronized void methodB(){
System.out.println("methodB");
methodC();
}
public synchronized void methodC(){
System.out.println("methodC");
}
}
重入锁支持继承的环境:比如子类继承父类,子类的一个同步方法为A,在A方法里边调用父类的同步方法B是没有问题的;
public class MainThread {
public static void main(String[] args) {
MyService myService = new MyService();
Runnable runnable = new Runnable() {
@Override
public void run() {
myService.methodA();
}
};
new Thread(runnable).start();
}
}
class MyService extends ParentService{
public synchronized void methodA(){
System.out.println("methodA");
super.methodA();
}
}
class ParentService{
public synchronized void methodA(){
System.out.println("这是父类的一个方法");
}
}
当出现异常时,锁自动释放,但是线程调用了suspend()和sleep()方法后,锁不会被释放;
子类继承父类中的同步方法,如果子类不使用synchronized关键字就是非同步方法,使用后就变成了同步方法。
synchronized同步代码块
synchronized同步方法是将当前对象作为锁,而同步代码块是将任意对象作为锁。
使用同步代码块锁非this对象,则synchronized(非this)代码块中的程序与同步方法是异步的,因为有两把不同的锁。
同步方法放在非同步方法中进行声明,并不能保证调用方法的线程执行同步。
synchronized关键字加到static静态方法上的方式是将Class类对象作为锁,而加到非静态方法上是将当前所在类的对象作为锁。而Class类是单例的。这就造成Class锁可以对所有的对象实例起作用了。
一般情况下是不会将String作为锁对象的,因为String有常量缓存池这种东西,避免两个线程拿到的锁是同一把锁。
死锁:不同的线程都在等待根本不可能被释放的锁,互相等待对方释放锁,就有可能出现死锁的情况。
Volatile关键字:
可见性:一个线程能看到另一个线程被修改的值。
原子性:volatile i++操作时是非原子的
禁止指令重排序
线程间的通信
wait()、notify()机制
wait()方法是Object类的方法,使当前线程暂停执行,并释放锁,进入等待状态,直到接到通知或中断为止。
notify()方法唤醒调用了wait()方法的线程,使其进入就绪状态,并且尝试重新获取锁。notify()方法执行后并不会立即释放锁,要等待当前线程的任务执行完,才会释放。
wait(),notify()方法只能在同步方法或同步代码块中调用,且必须拥有相同的锁,也就是它们的锁对象必须是相同的。如果不是在同步方法或同步代码块中调用wait(),notify()方法,则会抛出java.lang.IllegalMonitorStateException异常。
public class MainThread {
public static void main(String[] args) {
try {
//线程1和线程2持有相同的锁对象
Object lock = new Object();
Thread1 thread1 = new Thread1(lock);
thread1.start();
Thread.sleep(3000);
Thread2 thread2 = new Thread2(lock);
thread2.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Thread1 extends Thread{
private Object lock;
public Thread1(Object lock){
super();
this.lock = lock;
}
@Override
public void run() {
synchronized (lock){
try {
System.out.println("开始等待时间" + System.currentTimeMillis());
lock.wait();
System.out.println("结束等待时间" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Thread2 extends Thread{
private Object lock;
public Thread2(Object lock){
super();
this.lock = lock;
}
@Override
public void run() {
synchronized (lock){
System.out.println("开始唤醒时间" + System.currentTimeMillis());
lock.notify();
System.out.println("结束唤醒时间" + System.currentTimeMillis());
}
}
}
把业务代码封装进业务层
public class MainThread {
public static void main(String[] args) {
try {
//线程1和线程2持有相同的锁对象
MyService myService = new MyService();
Thread1 thread1 = new Thread1(myService);
thread1.start();
Thread.sleep(3000);
Thread2 thread2 = new Thread2(myService);
thread2.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Thread1 extends Thread{
private MyService myService;
public Thread1(MyService myService){
super();
this.myService = myService;
}
@Override
public void run() {
myService.waitMethod();
}
}
class Thread2 extends Thread{
private MyService myService;
public Thread2(MyService myService){
super();
this.myService = myService;
}
@Override
public void run() {
myService.notifyMethod();
}
}
class MyService{
private Object lock = new Object();
//等待方法
public void waitMethod(){
synchronized (lock){
try {
System.out.println("开始等待时间" + System.currentTimeMillis());
lock.wait();
System.out.println("结束等待时间" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//唤醒方法
public void notifyMethod(){
synchronized (lock){
System.out.println("开始唤醒时间" + System.currentTimeMillis());
lock.notify();
System.out.println("结束唤醒时间" + System.currentTimeMillis());
}
}
}
- 当线程调用了wait()方法后,在对该线程对象执行interrupt()方法就会出现Interrupted-Exception异常。
- 每调用一次notify()方法,只能唤醒一个线程,唤醒的顺序与执行wait()方法的顺序一致。
- notifyAll()方法唤醒全部的线程,按照执行wait()方法的倒序依次对其他线程进行唤醒。
join()方法的使用
应用场景:主线程创建并启动子线程,如果子线程要进行大量的耗时运算,主线程往往将早于子线程结束,我们预期的是想主线程等待子线程执行完毕之后再结束。
public class MainThread {
public static void main(String[] args) {
JoinThread joinThread = new JoinThread();
joinThread.start();
System.out.println("主线程:我想等待joinThread线程执行完在执行");
}
}
class JoinThread extends Thread{
@Override
public void run() {
try {
int secondValue = (int) (Math.random() * 10000);
System.out.println("子线程:我执行需要耗时" + secondValue);
Thread.sleep(secondValue);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
执行结果:
join()方法的作用就是使所属线程对象a正常执行完任务,而使当前线程b无限期阻塞,等待线程a销毁后再继续执行线程b的任务。可以理解为,有两个a,b线程,在b线程里边创建并启动a线程,那么b线程可以理解成一个主线程,a线程则为子线程,如果a线程的执行耗时比b线程要多,那么往往会b先执行完在执行a线程,加上a.join()方法后,则会使a线程先执行完,在执行b线程。
public class MainThread {
public static void main(String[] args) {
try {
JoinThread joinThread = new JoinThread();
joinThread.start();
joinThread.join();
System.out.println("我想等待joinThread线程执行完在执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class JoinThread extends Thread{
@Override
public void run() {
try {
int secondValue = (int) (Math.random() * 10000);
System.out.println("子线程:我执行需要耗时" + secondValue);
Thread.sleep(secondValue);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
join()方法和sychronized的区别是,join()方法内部使用wait()方法进行等待,而sychronized关键字使用锁作为同步。
源码:
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
- join()方法使用过程中,当前线程不能被中断,不能和interrupt()方法一起使用,否则会抛java.lang.InterruptedException异常。
- join(long)方法,等待一定时间,不管子线程是否执行完毕,时间到了当前线程将重新获取锁,继续往后执行。
类ThreadLocal的使用
类ThreadLocal主要将数据存入当前线程对象中的Map中,也就是ThreadLocalMap对象,Map中的key存储的是当前线程的对象也就是ThreadLocal对象,value存储的值,只对当前线程可见,其他线程不可以访问当前线程对象中map的值。随线程销毁,map随之销毁,map中的数据如果没有被引用则随时GC回收。
public class MainThread {
public static void main(String[] args) {
ThreadLocal local = new ThreadLocal();
local.set("2324");
System.out.println(local.get());
}
}
源码解析:
ThreadLocal的set方法
ThreadLocal.ThreadLocalMap threadLocals = null;
public void set(T value) {
//当前线程对象
Thread t = Thread.currentThread();
//从当前线程中获取当前线程的Map对象
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else //第一次调用set方法时,执行createMap
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
验证ThreadLocal的隔离性,以及ThreadLocal不能实现值继承:
public class MainThread {
public static void main(String[] args) {
Thread1 thread1 = new Thread1();
Thread2 thread2 = new Thread2();
thread1.start();
thread2.start();
System.out.println("这是main线程的变量:" + ThreadLocalTest.local.get());
}
}
class ThreadLocalTest{
public static ThreadLocal local = new ThreadLocal();
}
class Thread1 extends Thread{
@Override
public void run() {
ThreadLocalTest.local.set("这是thread1设置的变量");
System.out.println(ThreadLocalTest.local.get());
}
}
class Thread2 extends Thread{
@Override
public void run() {
ThreadLocalTest.local.set("这是thread2设置的变量");
System.out.println(ThreadLocalTest.local.get());
}
}
在第一次调用ThreadLocal类的get()方法时,返回值是null,如果想解决第一次调用不反悔null,可以通过继承ThreadLocal类重写initialValue()方法来实现。
public class MainThread {
public static ThreadLocalTest threadLocalTest = new ThreadLocalTest();
public static void main(String[] args) {
System.out.println(threadLocalTest.get());
}
}
class ThreadLocalTest extends ThreadLocal{
@Override
protected Object initialValue() {
return "返回默认值";
}
}
Lock对象的使用
ReentrantLock锁
public class MainThread {
public static void main(String[] args) {
MyService myService = new MyService();
//开启两个线程
new Thread1(myService).start();
new Thread1(myService).start();
}
}
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();
}
}
class Thread1 extends Thread{
private MyService myService;
public Thread1(MyService myService){
super();
this.myService = myService;
}
@Override
public void run() {
myService.testMethod();
}
}
没开锁之前:
开锁之后实现了同步:
公平锁与非公平锁
公平锁:
采用先到先得的策略,每次获取锁之前都会检查队列里边有没有排队等待的线程,有的话就将当前线程追加到队列里,没有才尝试获取锁。
Lock lock = new ReentrantLock(true);
非公平锁:
一个线程获取锁之前先去尝试获取锁,而不是在队列里等待,有可能后到的线程也可以获取到锁。
Lock lock = new ReentrantLock(false);
源码:
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
ReentrantLock锁的常用方法:
public int getHoldCount()方法:查询当前线程调用lock()方法的次数,调用lock()方法进行锁重入导致count计数加1,执行unlock()方法使count呈减1的效果。
public class MainThread {
public static void main(String[] args) {
MyService myService = new MyService();
new Thread1(myService).start();
}
}
class MyService{
ReentrantLock lock = new ReentrantLock();
public void testMethod(){
System.out.println(lock.getHoldCount());
//同步代码块,开启锁
lock.lock();
System.out.println(lock.getHoldCount());
//需要手动释放锁
lock.unlock();
System.out.println(lock.getHoldCount());
}
}
class Thread1 extends Thread{
private MyService myService;
public Thread1(MyService myService){
super();
this.myService = myService;
}
@Override
public void run() {
myService.testMethod();
}
}
public final boolean isFair()
判断是不是公平锁。
ReentrantLock lock = new ReentrantLock(true)
System.out.println(lock.isFair())); //true
ReentrantLock lock = new ReentrantLock(false)
System.out.println(lock.isFair())); //false
public boolean isLocked()
查询此锁是否由任意线程保持,并没有释放。
public boolean tryLock()
嗅探拿锁,如果当前线程发现锁被其他线程持有,则返回false,程序继续执行后边的代码,而不是呈阻塞等待锁的状态。
public class MainThread {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
new MyService().testMethod();
}
};
Thread threadA = new Thread(runnable);
threadA.setName("A线程");
threadA.start();
Thread threadB = new Thread(runnable);
threadB.setName("B线程");
threadB.start();
}
}
class MyService{
ReentrantLock lock = new ReentrantLock();
public void testMethod(){
if (lock.tryLock()){
System.out.println(Thread.currentThread().getName() + "获取到了锁");
}else {
System.out.println(Thread.currentThread().getName() + "没有获取到锁");
}
}
}
ReentrantReadWriteLock锁
ReentrantLock锁主要缺点就是所有操作都是同步的,哪怕只是对实例变量进行读操作,会降低运行效率。
读写锁有两个锁,一个是读操作相关的锁,也称共享锁,readLock;另一个是写操作相关的锁,也称排他锁,writeLock;
读锁不互斥,读锁和写锁互斥,写锁和写锁互斥,也就是只要出现写锁,就会出现互斥同步的效果。
读读共享
public class MainThread {
public static void main(String[] args) {
MyService myService = new MyService();
Runnable runnable = new Runnable() {
@Override
public void run() {
myService.testMethod();
}
};
//这样开启的锁不是同一把锁。
//Runnable runnable = new Runnable() {
// @Override
// public void run() {
// MyService myService = new MyService();
// myService.testMethod();
// }
// };
//开启两个线程
Thread threadA = new Thread(runnable);
threadA.setName("A线程");
threadA.start();
Thread threadB = new Thread(runnable);
threadB.setName("B线程");
threadB.start();
}
}
class MyService{
private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void testMethod(){
try {
//开启读锁
readWriteLock.readLock().lock();
System.out.println("begin" + Thread.currentThread().getName() + System.currentTimeMillis());
Thread.sleep(3000);
System.out.println("end" + Thread.currentThread().getName() + System.currentTimeMillis());
readWriteLock.readLock().unlock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
beginA线程1623898098906
beginB线程1623898098906
endB线程1623898101914
endA线程1623898101914
写写互斥:
public class MainThread {
public static void main(String[] args) {
MyService myService = new MyService();
Runnable runnable = new Runnable() {
@Override
public void run() {
myService.testMethod();
}
};
//开启两个线程
Thread threadA = new Thread(runnable);
threadA.setName("A线程");
threadA.start();
Thread threadB = new Thread(runnable);
threadB.setName("B线程");
threadB.start();
}
}
class MyService{
private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void testMethod(){
try {
//开启读锁
readWriteLock.writeLock().lock();
System.out.println("begin" + Thread.currentThread().getName() + System.currentTimeMillis());
Thread.sleep(3000);
System.out.println("end" + Thread.currentThread().getName() + System.currentTimeMillis());
readWriteLock.writeLock().unlock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
beginB线程1623898802458
endB线程1623898805467
beginA线程1623898805467
endA线程1623898808476