link-JAVA多线程与高并发系列[前言,大纲]
举个例子:
程序:QQ.exe是一个程序,存放在硬盘上,是一个静态的概念;
进程:当你双击它,QQ程序运行起来了,这就是一个进程.相对程序来说,进程是一个动态的概念;
线程:进程中最小的执行单元,一个进程内可以有多个线程同时执行命令
在JAVA中创建一个线程:
如果调用T1的run()方法,那就是简单的方法调用,代码还是在main线程中从上到下依次执行;
如果调用T1的start()方法,则会开启一个新的线程(记为thread1)执行,那么thread1和main线程同时执行,表现为控制台"T1"和"main"交替输出.
public class T01_WhatIsThread {
private static class T1 extends Thread {
@Override
public void run() {
for(int i=0; i<10; i++) {
try {
TimeUnit.MICROSECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("T1");
}
}
}
public static void main(String[] args) {
//new T1().run();
new T1().start();
for(int i=0; i<10; i++) {
try {
TimeUnit.MICROSECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main");
}
}
}
1:Thread 2: Runnable 3:Executors.newCachedThrad
public class T02_HowToCreateThread {
static class MyThread extends Thread {
@Override
public void run() {
System.out.println("Hello MyThread!");
}
}
static class MyRun implements Runnable {
@Override
public void run() {
System.out.println("Hello MyRun!");
}
}
public static void main(String[] args) {
// 第一种,对象继承Thread,然后直接new该对象
new MyThread().start();
// 第二种,对象实现Runnable,该对象作为参数创建一个Thread对象
new Thread(new MyRun()).start();
// 第二种的变式,其实是匿名内部类的实例作为实现Runnable的对象,又加了lambda表达式简化代码
new Thread(()->{
System.out.println("Hello Lambda!");
}).start();
// 第三种线程池Executors.newCachedThrad等,其实最终实现也是前两种之一
}
}
CPU只管执行命令,对于CPU来说,没有线程的概念,它只是不断的从内存中去拿取指令去执行.
多线程呢,就是有好多个线程竞争着去给CPU发送命令,每个线程上的命令在CPU上执行一小会,多个线程的命令快速交替执行,这样看起来像是同时执行的.
各个线程和CPU之间,相当于有一个"等待队列",线程们在队列中排队,等着CPU从队列中随机找一个线程去执行;CPU执行某个线程的一小段命令后,不等执行完,就把它扔回等待队列,然后重新找一个线程执行,这样快速的切换感觉像是多个线程同时执行.如果线程执行完了,那就终结了自己的一生,不会再进入等待队列了.
线程操作:
线程状态迁移图:
线程在"等待队列"中等着被CPU执行时,就是Ready状态;线程正在被CPU执行时,是Running状态;Ready和Running合称为Runnable状态.
线程执行完后(或者被操作系统kill掉),进入terminated状态,结束了自己的一生,啥都不能干了(等待被GC回收).
获取线程状态的方法:new MyThead().getState();
多个线程去访问同一个资源的时候,需要上锁,目的是保证状态的一致,就像数据库的事务一样.
类比场景:多个同学去厕所蹲坑.
是一个对象(包括class对象),拿到锁之后才能去执行某段代码.而不是锁的代码.
不能用String的原因:所有的字符串常量都是同一个对象,假如引用的依赖包中用了String常量作为锁的对象,自己的程序也刚好用了相同String常量作为锁的对象,那么肯定会引起奇怪的问题.
也尽量不用String对象.
不能用Integer等基础数据类型的原因:Integer内部做了一些特殊处理,Integer的对象的值一旦改变,就会变成一个新对象
public class T {
private int count = 10;
private Object o = new Object();
public void m() {
synchronized(o) { //任何线程要执行线面的代码,必须先拿到对象o的锁
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
}
JVM规范中没有任何要求,只要能保证功能完整就行.
HotSpot是这样的:对象头(64位)中,拿出2位来记录这个对象是不是被锁定了,mark word.
同一个ClassLoader内是单例的;多个ClassLoader间,不是单例;但是不同加载器之间不能互相访问.所以,可以认为是单例.
是子类对象,打印一下this即可证明:
public class T {
synchronized void m() {
System.out.println("super m start");
System.out.println(this);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("super m end");
}
public static void main(String[] args) {
new TT().m();
System.out.println("---");
new TT().m2();
System.out.println("---");
new TT().m3();
}
}
class TT extends T {
@Override
synchronized void m() {
System.out.println("child m start");
System.out.println(this);
super.m();
System.out.println("child m end");
}
synchronized void m2() {
System.out.println("child m2 start");
System.out.println(this);
super.m();
System.out.println("child m2 end");
}
void m3() {
System.out.println("child m2 start");
System.out.println(this);
super.m();
System.out.println("child m2 end");
}
}
可以.
抛出异常会释放锁,所以一定要处理好异常,防止出现异常后被其他线程访问资源,从而导致各种状态不一致的问题.
锁细化:锁住(synchronize包括)的代码,在能保证业务逻辑OK下,越少越好.
锁粗化:假如一段业务逻辑,中间有很多个细化的小锁,这些小锁的方法别的业务又不会调用,那就把这些小锁合并成一个大锁.
锁的属性变化不会影响锁的功能;
但是如果锁的变量(或者说"引用")指向了别的对象,那就不是同一把锁了,会出问题.可以通过给变量加上final关键字避免这个问题.