黑马程序员_Java基础多线程
进程:是一个正在执行中的程序。
每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。
线程:就是进程中的一个独立的控制单元。
线程在控制着进程的执行。
一个进程中至少有一个线程。
启动一个线程不能直接调用run()方法,这样不会创建一个新的线程,只是简单的在当前线程中执行了run()方法,
而应该调用start()方法,这样就会创建一个新的线程,在这个新的线程上执行run()方法上的代码。
当run()方法执行完后,线程也就相应的结束,我们可以通过控制run()方法中循环的条件来控制线程的结束。
(一个进程里面的多个线程都是无规律的交替运行)
Thread类中的setDaemon(true)设置一个线程为后台线程,该方法必须在线程启动之前调用,也就是在调用start()方法之前调用。
如果一个进程中只有后台线程在运行,这个进程就会结束。
thread1.join()的作用是把thread1所对应的线程合并到调用thread1.join()的线程中。
yield()方法可以让当前正在运行的线程对象临时暂停,让别的线程运行。
sleep(...)方法可以让当前正在运行的线程进入睡眠状态。
获取和更改线程的优先级分别用Thread对象的getPriority()方法和setPriority(int newPriority)方法。线程的默认优先级的值是5。
创建一个线程类有两种方法:
1,直接继承Thread类;
2, 实现Runnable接口,然后用new Thread(实现Runnable接口的类的对象)来产生一个线程类。
使用Runnable接口创建多线程,适合多个相同程序代码的线程去处理同一份资源的情况。还可以避免由于java的单继承特性带来的局限。
事实上,几乎所有的多线程应用都可用Runnable接口方式。
每个线程都会有一个运行的时间片,都会有开始和时间片到期的时候。
所以我们要注意了:一个线程的时间片到期的时候,此线程有可能执行到程序的任何一个位置而被暂停,并没有完全执行完一个原子性的模块,
然后就进入时间片的轮换,这样就导致多线程的运行结果不可意料。此时我们就需要对线程进行同步处理。
线程同步关键字是:synchronized
同步代码块放在如下的大括号中:
synchronized(obj){ //用于synchronized的obj叫lock旗标(锁旗标),可以是任何对象,我们通常称之为同步对象或者监视器对象。
}
同步方法只须在方法前加synchronized关键字修饰即可。
同步非静态方法的监视器是this,而同步静态方法的监视器是当前所在的类的Class对象。
只要使用相同的同步对象(监视器),synchronized方法和synchronized代码块也可以实现同步。
在编写多线程同步的时候一定要注意避免死锁的发生。
String str1 = "";
String str2 = "";
while(true) {
synchronized(str1) {
...
synchronized(str2){
...
}
...
}
}
while(true) {
synchronized(str2) {
...
synchronized(str1){
...
}
...
}
}
如果上面的两个while块里面的代码分别由两个线程执行,那么就形成了死锁。
我们在编写线程安全的程序代码的时候也必须防止一个线程对共享的数据仅仅做了部分的操作的时候,这个线程就结束了,这种情况下就会破坏数据的一致性。
要实现同步的线程所检查的监视器对象必须是同一个对象,才能保证线程的同步。
不同的类中的方法或者语句块也可以实现同步,只要是同一个监视器对象就可以了。
多线程编程里面的run()方法里面通常都是一个while循环程序。
Object类里面有几个方法是用于线程通信的:wait(),notify(),notifyAll()。由于任何对象都可以作为监视器对象,所以任何对象都可以调用这几个方法。
但是这些方法只能在被synchronized修饰的方法或者代码块中调用。
每个对象除了有一个锁旗标之外,还有一个线程等待队列(wait set),一个对象刚创建的时候,它的等待队列是空的。
如果一个线程里面有while循环,我们可以设置一个boolean flag变量作为while的循环条件,while(flag),
当我们需要结束这个线程的时候就可以在另外一个线程中将这个flag设置为false即可。
但是有时候我们还需要结合interrupt()方法来结束一个线程,见如下例子:
package com.heima.exam;
class StopThreadTest {
public static void main(String[] args) {
ThreadInStop t1 = new ThreadInStop();
t1.start();
int index = 0;
while(true) {
if(index++ == 500){
t1.stopThread();
t1.interrupt();
break;
}
System.out.println(Thread.currentThread().getName());
}
System.out.println("main() exit");
}
}
class ThreadInStop extends Thread {
private boolean bStop = false;
public synchronized void run() {
while(!bStop) {
try {
wait();
} catch(InterruptedException e) {
if(bStop) {
return;
}
}
System.out.println(getName());
}
}
public void stopThread() {
bStop = true;
}
}
/*
Java VM 启动的时候会有一个进程java.exe.
该进程中至少一个线程负责java程序的执行。
而且这个线程运行的代码存在于main方法中。
该线程称之为主线程。
扩展:其实更细节说明jvm,jvm启动不止一个线程,还有负责垃圾回收机制的线程。
1,如何在自定义的代码中,自定义一个线程呢?
通过对api的查找,java已经提供了对线程这类事物的描述。就Thread类。
创建线程的第一种方式:继承Thread类。
步骤:
1,定义类继承Thread。
2,复写Thread类中的run方法。
目的:将自定义代码存储在run方法。让线程运行。
3,调用线程的start方法,
该方法两个作用:启动线程,调用run方法。
发现运行结果每一次都不同。
因为多个线程都获取cpu的执行权。cpu执行到谁,谁就运行。
明确一点,在某一个时刻,只能有一个程序在运行。(多核除外)
cpu在做着快速的切换,以达到看上去是同时运行的效果。
我们可以形象把多线程的运行行为在互相抢夺cpu的执行权。
这就是多线程的一个特性:随机性。谁抢到谁执行,至于执行多长,cpu说的算。
为什么要覆盖run方法呢?
Thread类用于描述线程。
该类就定义了一个功能,用于存储线程要运行的代码。该存储功能就是run方法。
也就是说Thread类中的run方法,用于存储线程要运行的代码。
*/
class Demo extends Thread
{
public void run()
{
for(int x=0; x<60; x++)
System.out.println("demo run----"+x);
}
}
class ThreadDemo
{
public static void main(String[] args)
{
//for(int x=0; x<4000; x++)
//System.out.println("Hello World!");
Demo d = new Demo();//创建好一个线程。
//d.start();//开启线程并执行该线程的run方法。
d.run();//仅仅是对象调用方法。而线程创建了,并没有运行。
for(int x=0; x<60; x++)
System.out.println("Hello World!--"+x);
}
}