文章目录
- 一、故事背景
- 二、知识点主要构成
- 1、线程的概念
- 2、启动方式
- 2.1、继承Thread类 重写run方法
- 2.2、实现Runnable接口 重写run方法
- 2.3、实现Callable 重写call方法 配合FuterTask获取线程结果
- 3、常用方法
- start()方法:
- run()方法:
- sleep(long millis)方法:
- join()方法:
- interrupt()方法:
- isAlive()方法:
- 4、synchronized
- 4.1、锁升级
- 4.2、锁消除
- 4.3、锁粗化
- 5、线程同步
- 三、总结提升
一、故事背景
最近在学习多线程与高并发编程系列的相关知识,这里总结出几点给大家分享一下,
二、知识点主要构成
1、线程的概念
一个程序进入内存被称之为进程;同一个进程内部,有多个任务并发执行的需求,(比如,一边计算,一边接受网络数据,一边刷新页面)线程共享进程的内存空间,但是不共享计算;进程是静态的,程序进入内存,分配对应资源(内存空间),进程进入内存,同时产生一个主线程;线程是动态的,可执行的计算单元。
2、启动方式
2.1、继承Thread类 重写run方法
public class ThreadTest extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"运行了"+i);
}
}
public static void main(String[] args) {
ThreadTest threadTest = new ThreadTest();
Thread thread1 = new Thread(threadTest);
thread1.start();
}
}
2.2、实现Runnable接口 重写run方法
public class RunnableTest implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+":hello thead"+i);
}
}
/**
* 多线程第二种启动方式:
* 1.自己定义一个类去实现 runnable接口
* 2.重新run方法--线程执行的代码
* 3.创建自己的对象
* 4.创建一个Thread类的对象 并开启线程
*/
public static void main(String[] args) {
RunnableTest runnableTest = new RunnableTest();
// 线程1
Thread t1=new Thread(runnableTest);
t1.start();
t1.setName("线程1");
// 线程2
Thread t2=new Thread(runnableTest);
t2.start();
t2.setName("线程2");
}
}
2.3、实现Callable 重写call方法 配合FuterTask获取线程结果
public class CallableTest implements Callable<Integer> {
@Override
public Integer call() {
int sum=0;
for (int i = 0; i < 100; i++) {
sum=sum+i;
}
return sum;
}
/**
* 多线程第三种启动方式:
* 解决:多线程其他两种没有返回值
* 特点:可以获取多线程的运行结果
*
* 1.创建一个类 实现Callable接口
* 2.重写 call(有返回值表示结果)
* 3.创建任务对象
* 4.创建 FutureTask对象(管理多线程结果)
* 5.创建Thread类并启动,
*/
public static void main(String[] args) throws ExecutionException, InterruptedException {
CallableTest callableTest = new CallableTest();
FutureTask<Integer> futureTask = new FutureTask(callableTest);
Thread thread = new Thread(futureTask);
thread.start();
System.out.println(futureTask.get());
}
}
3、常用方法
start()方法:
方法签名:public synchronized void start()
作用:启动线程并调用线程的run()方法。run()方法中定义了线程的实际执行逻辑。
Thread thread = new MyThread(); // MyThread是继承自Thread的类
thread.start();
run()方法:
方法签名:public void run()
作用:定义线程的执行逻辑。需要在子类中重写该方法,以实现自定义的线程行为。
示例:
class MyThread extends Thread {
public void run() {
// 线程的执行逻辑
}
}
sleep(long millis)方法:
方法签名:public static native void sleep(long millis) throws InterruptedException
作用:让当前线程睡眠指定的毫秒数,暂时释放CPU资源。
try {
Thread.sleep(1000); // 线程睡眠1秒
} catch (InterruptedException e) {
// 处理异常
}
join()方法:
方法签名:public final synchronized void join() throws InterruptedException
作用:等待调用该方法的线程执行完毕后再继续执行当前线程。
Thread thread = new MyThread();
thread.start();
try {
thread.join(); // 等待thread线程执行完毕
} catch (InterruptedException e) {
// 处理异常
}
interrupt()方法:
方法签名:public void interrupt()
作用:中断线程的执行,会设置线程的中断状态。线程可以通过检查中断状态来自行决定是否中断。
Thread thread = new MyThread();
thread.start();
thread.interrupt(); // 中断thread线程的执行
isAlive()方法:
方法签名:public final native boolean isAlive()
作用:判断线程是否还活着(是否在执行或者等待状态)。
Thread thread = new MyThread();
thread.start();
boolean alive = thread.isAlive(); // 检查thread线程是否还活着
4、synchronized
在JDK 1.6后,Jvm为了提高锁的获取与释放效率对(synchronized )进行了优化,引入了 偏向锁 和 轻量级锁 ,
从此以后锁的状态就有了四种。
4.1、锁升级
在锁对象的对象头里面有一个 threadid 字段,在第一次访问的时候 threadid 为空,jvm 让其持有偏向锁,并将 threadid 设置为其线程 id,再次进入的时候会先判断 threadid 是否与其线程 id 一致,如果一致则可以直接使用此对象,如果不一致,则升级偏向锁为轻量级锁,通过自旋循环一定次数来获取锁,执行一定次数之后,如果还没有正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁。
通俗来说就是:对象刚刚被new出来,没有任何人给他上锁,处于无锁状态,此时来了一个线程,升级为偏向锁,如果没有其他线程和其进行抢锁,那么下次直接就能获取锁,如果有其他线程和其进行争抢,那么就撤销偏向锁,升级为轻量级锁,线程在自己的线程栈中生成LockRecord,用CAS操作将markword设置为指向自己这个线程LR指针,设置成功者得锁,在进行CAS的过程中是一直在占用CPU的资源的,默认在进行10次自选操作之后升级为重量级锁,此时由操作系统来给线程分配锁,分配之后进入锁的队列中进入阻塞状态,此时是不消耗CPU资源的,等CPU什么时候有时间了线程进行执行状态;
4.2、锁消除
public void add(String str1,String str2){
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(str1).append(str2);
}
StringBuffer是线程安全的,因为它的关键方法都是被synchronized修饰过的,单是看上面的代码,会发现stringBuffer这个引用只会在add方法中调用,不可能被其他线程引用,(因为是局部变量,线程栈私有的)因此stringBuffer是不可能共享的资源,JVM会自动消除StringBuffer对象内部的锁;
4.3、锁粗化
public String test(String str) {
int i = 0;
StringBuffer stringBuffer = new StringBuffer();
while (i < 100) {
stringBuffer.append(str);
i++;
}
return stringBuffer.toString();
}
JVM会检测到这样一连串的操作都对同一个对象加锁,while循环内100次执行append,没有锁粗化的就要进行100次加锁、解锁的操作,此时JVM就会将加锁的范围粗化到这一连串操作的外部,while循环体外,使得这一连串操作只需要加一次锁即可;
5、线程同步
Java实现生产者消费者问题
三、总结提升
以往的经验中,只要用到synchronized就以为它已经成为了重量级锁。在jdk1.2之前确实如此,后来发现太重了,消耗了太多操作系统资源,所以对synchronized进行了优化。以后可以直接用,至于锁的力度如何,JVM底层已经做好了我们直接用就行。
如果本篇博客对您有一定的帮助,大家记得留言+点赞+收藏哦。