复习:
在此之前,我们的代码都是运行在一个线程中,这被称之为“单线程(Thread)编程”。
这个(我们默认使用的)线程又被称为主(primary)线程,或者启动线程。
更具体的说,代码运行在线程的栈(stack,复习)中:一个线程对应着一个栈,这个栈又被称之为线程栈。
单个线程中,Java和C#代码都是同步(依次)运行的。
PS:为什么强调Java和C#,因为JavaScript是单线程都可以异步的,^_^
Thread类的静态方法,传入整型参数,单位毫秒
Thread.sleep(1000);
让当前执行线程睡眠1000毫秒,一般0用于演示/调试。
梗://项目经理要求加的,方便以后优化加钱
Thread current = Thread.currentThread();
然后,可以通过thread对象的属性拿到线程的一些基本信息,比如Id、Name等。
System.out.println(current.getId()); //线程Id System.out.println(current.getName()); //名字 System.out.println(current.getPriority()); //优先级 System.out.println(current.getState()); //线程状态
注意:
Java和C#允许开发人员创建新的线程:(PS:JavaScript不允许)
Thread thread = new Thread();这种线程又被称之为用户线程或子线程。
子线程还可以被分为:
不管是主线程,还是我们开发人员新建线程,默认都是前台线程。后台线程需要显式设置。
演示:设置和输出是否为后台/守护线程等……
System.out.println(thread.isDaemon()); //false thread.setDaemon(true); System.out.println(thread.isDaemon()); //true
新建线程的时候,我们通常都要传递一个方法/函数,让该函数运行在新建的线程上。@想一想@:怎么传递一个函数呢?最简单的就是lambda表达式:
new Thread(()->{ System.out.println("源栈欢迎您"); })
实例方法,启动当前对象所指向的线程。
其实调用start()方法,只是将线程状态改为准备就绪(ready),然后就只能等着操作系统控制CPU执行该线程中的方法,并不一定就马上运行(run)。
static int i = 0; //Java中的lambda只能访问静态字段:
for (i = 0; i < 10; i++) { //注意:i是静态字段 System.out.println("单线程:" + i); new Thread(()->System.out.println(i)).start(); }
由于多个线程共享一些资源,就有可能A线程的运行,干扰了B线程,使得B线程出现非预期的结果,这就被称之为线程不安全(unsafe)。
换言之,多个线程在执行同一段代码的时候,每次的执行结果和单线程执行的结果都是一样的,不存在执行结果的二义性,就可以称作是线程安全的。
我们有时候会看到一些方法的文档,上面就会写着:它是线程安全的。就是说:你放心用,哪怕是多线程调用,它都能保证结果运行正确,不需要我们自己采取锁/同步等方式确保线程安全(safe)。
for (int j = 0; j < 10; j++) { // 注意:i是静态字段 i = 3; new Thread(() -> System.out.println((i / 3.0f))).start(); i = 5; new Thread(() -> System.out.println((i / 5.0f))).start(); }
@想一想@:为啥不都做成线程安全的呢?
多线程的另一个大麻烦是不能在A线程中直接捕获B线程中的异常。
try { throw new RuntimeException("抓我呀!"); //new Thread(()->{ throw new RuntimeException("抓我呀!"); }).start(); } catch (Exception e) { System.out.println("抓到你了!"); }
你可以理解成try...catch是基于栈的,只能控制当前线程中的当前栈。
如果要跨线程捕获异常,需要一系列复杂的操作……大体逻辑:
很多时候我们仍然需要对线程的运行进行控制,常用的手段:
它可以使得线程之间的并行执行变为串行执行
Thread current = new Thread(()->System.out.println(i)); current.start(); current.join();
注意:一定要先start()再join()
理解:join,加入的意思,如果
join()方法其实也可以传递一个参数给它:
current.join(10);
这样调用线程就只会等待current线程执行10毫秒。
另外,join(0)等价于join()
和I/O操作类似,线程运行时,锁定其资源(对象/类/方法,不能是变量),不让其他线程访问。
java中的关键字是synchronized,C#是lock。
为了演示,我们不能使用lambda,而是需要用声明一个类,然后将其方法传递给Thread:
class Student implements Runnable { int i; //这是字段,不是方法变量 @Override public void run() { synchronized (this) { //锁住了当前对象 i = 0; while (i<5) { System.out.println(Thread.currentThread().getId() + ":"+ i); i++; } } } }
Student atai = new Student(); Thread t1 = new Thread(atai); t1.start(); //注意:t1和t2都使用了同一个对象 Thread t2 = new Thread(atai); t2.start();
演示:
从运行的结果来看,好像是t1和t2同步(synchronized)运行,但实际上是因为t1运行时锁住了atai对象,t2只能等着t1运行结束……
线程同步:将操作共享数据的代码行作为一个整体,同一时间只允许一个线程执行,执行过程中其他线程不能参与执行。目的是为了防止多个线程访问一个数据对象时,对数据造成的破坏。
我们已经知道了多个线程可以共享一个同一个对象资源。
但是,基于某些原因(缓存/编译器优化等),线程并不能总是拿到最新的数据(如果没有锁):
/* volatile */static boolean stop = false; public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(()->{ int i = 0; while (!stop) { //System.out.println(Thread.currentThread().getId() + ":" + i); i++; } System.out.println("线程终止, i=" + i); } ); thread.start(); Thread.sleep(1); stop = true; //希望据此终止线程中的while循环 thread.join(); System.out.println("全部结束"); }
演示技巧:
volatile的作用:确保各个线程总是能拿到最新的变量值(可见性),以及该变量相关的操作总是按正确的顺序执行(有序性)。
如何确保?这是JVM的事。注意:Java语法是规范,只要求结果,不管其实现……
同义词/近义词:
Console.WriteLine(current.ManagedThreadId); Console.WriteLine(current.ThreadState); current.IsBackground = true; Console.WriteLine(current.IsBackground);传入Action实例方法:
class Student //不需要实现什么Runnable接口 { public void show() //任意方法名 {
new Thread(new Student().show);lock而不是synchronized:
lock (this)
包含的内容:
多快好省!前端后端,线上线下,名师精讲
更多了解 加: