高并发与多线程目录
文章目录
前言
高并发与多线程学习记录笔记
一、线程、进程的概念
线程:线程是进程的一个实体,是进程的一条执行路径。线程是CPU独立运行和独立调度的基本单位。
进程:系统中正在运行的一个应用程序,程序一旦运行就是进程。进程是系统进行资源分配的独立实体, 且每个进程拥有独立的地址空间。
二、启动线程的方式
1.严格意义上来说线程的创建只有两种方式
① 从Thread类继承,继承之后重写run方法。
② 定义一个类去实现Runnable接口,重写run方法
③ 为第二种的一种变形,使用lambda表达式的方式
代码如下(示例):
public class X02_HowToCreateThread {
//01 继承Thread
static class MyThread extends Thread{
@Override
public void run(){
System.out.println("How Create Thread 01");
}
}
//02 实现Runnable接口
static class MyRun implements Runnable{
@Override
public void run(){
System.out.println("How Create Thread 02");
}
}
public static void main(String[] args) {
new MyThread().start();
new Thread(new MyRun()).start();
//03 lambda表达式
new Thread(() -> {
System.out.println("Lambda");
}).start();
}
}
④ 线程池方式,但实际上线程池启动方式也是上面的两种之一。
代码还未补充
2.Thread的几种方法
sleep:让线程睡眠一段时间,到了规定的时间自动复活,sleep完也会回到就绪。
(java.util.current下的TimeUnit)
yield:让线程回到等待队列里(让出一下cpu)。
join: 用来等待另外一个线程的结束。
getstate:获得一个线程的状态
public class X03_Sleep_Yield_Join {
/**
* 测试sleep方法
*/
static void testSleep(){
new Thread(() -> {
for (int i=0;i<100;i++){
System.out.println("A" + i);
try {
Thread.sleep(500);
// TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
/**
* 测试yield方法
*/
public static class TestYield extends Thread{
public TestYield(String name){
super(name);
}
@Override
public void run(){
for(int i=0; i<100; i++) {
System.out.println(this.getName() +" " + i);
if(i%10 == 0) {
Thread.yield();
}
}
}
}
/**
* 测试join方法
*/
static void testJoin(){
Thread t1 = new Thread(() -> {
for(int i=0;i<100;i++){
System.out.println("A" + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(() -> {
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i=0;i<100;i++){
System.out.println("B" + i);
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t1.start();
t2.start();
}
public static void main(String[] args) {
// testSleep();
// TestYield ty1 = new TestYield("张三");
// TestYield ty2 = new TestYield("李四");
// ty1.start();
// ty2.start();
testJoin();
}
}
3.Thread的六种状态
线程的六种状态分别为
new:刚创建出来还没到start就是new状态
Runnable:Ready和Running统称为Runnable
<< 1. Ready 线程被扔到cpu的等待队列里,等待运行
<< 2. Running 扔到cpu里运行即Running
Terminated:顺利执行完毕进入Terminated
Blocked: synchronized进入Blocked状态 ,加了同步代码块没有得到这个锁的时候,Blocked状态,获得锁跑到就绪状态去运行。
Waitting:调用了o.wait()、t.join()、LockSupport.park()方法进入Waitting状态
TimedWaitting:调用了Thread.sleep(time)、o.wait(time)、t.join(time)、LockSupprt.ParkNanos()、LockSupprt.ParkUtil()方法进入到TimedWaitting状态
ps:关于stop,stop已经不建议用,它太粗暴,容易产生状态的不一致,所以不要关闭线程,让线程正常结束。
4. Synchronized
多个线程访问一个字段的时候需要对这个字段上锁
synchronized底层实现,jvm并不规定,都是hotspot实现的
Synchronized 锁的是对象不是代码
方法上不加任何指令的话锁的是this,锁静态锁的是XX.class
Synchronized(Object)不能用String常量、Integer、Long类型
Synchronized可重入
一个方法可以调用另外一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候仍会得到该对象的锁,也就是说synchronized获得的锁是可重入的。并且它必须可重入,如果不可得话,父子类得继承就会死锁了。
并发处理问题中小心异常
程序在执行过程中,如果出现异常,默认情况下锁会被释放,所以,在并发处理得过程中,有异常要多加小心,不然可能会发生不一致的情况。
比如在一个Web app处理过程中,多个servlet线程共同去访问同一个资源,这时,如果异常处理不合适,在第一个线程中会抛出异常,其他线程会进入同步代码区,有可能会访问到异常产生的数据,因此要非常小心的处理业务逻辑中的异常。
问题记录
问题:同步方法和非同步方法是否可以同时调用
回答:可以,m1加锁了,其他线程调用这个方法在没得到这个锁之前是不行的,但是m2没有锁,m1和m2同时调用是没有任何问题的。
在这里插入代码片
问题:对业务写方法加锁,对业务读方法不加锁,行不行
回答:不行,容易产生脏读问题
在这里插入代码片
5.锁升级
jdk早期用的是重量级的锁,后来改进,出现了锁升级的概念。
首先是偏向锁,对象markword记录这个线程的Id。
当有多个线程征用的时候,升级为自旋锁
10次之后,升级为重量级锁–os(操作系统)
当执行时间短(加锁代码),线程少的时候,用自旋锁。
当执行时间长,线程多的时候,用系统锁。
总结
第一天大概就学了这么多,概念上面,感觉自己大概理解了,但是还没有记住。代码的话,示例代码还没有开始敲。纯当记笔记了,会经常回头来看的!!要努力!!要坚持!!