本节目标
-
学习java 多线程编程! -
了解java 中的Thread 类的基本使用! -
掌握Thread 创建线程对象的5种方法 -
学习Thread 类中的一些常用属性和方法! Thread 类 我们知道操作系统中的线程是并发执行的! 而Thread 类是java 给我们提供的一个类,通过Thread 可以实现java 并发编程!Thread 类可以视为java 标准库提供的API 创建好的Thread 实例和操作系统的线程一一对应!!! Thread 是在java.lang 包下的类无需导入!!! 了解并发编程 public class Thread_4 {
public static void main(String[] args) {
Thread t1 = new Thread(()->{
while (true){
System.out.println("hello Thread!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
while (true){
System.out.println("hello main");
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
``
我们可以看到t1 线程和main 并发执行!这里的并发包括并发和并行! 我们也不知道cup 啥时候让这两个线程并发,啥时候并行! 这些都是操作系统的调度问题!!!
多线程优势
当我们要同时自增两数时,采用多线程编程和单线程编程又有啥区别呢,让我们看看下方代码就知道了!
public class Thread_8 {
private static final long count=100_0000_0000L;
public static void main(String[] args) throws InterruptedException{
concurrent();
serial();
}
public static void concurrent() throws InterruptedException {
long beign = System.nanoTime();
Thread t1 = new Thread(()->{
long a = 0;
for (int i = 0; i < count; i++) {
a++;
}
});
t1.start();
long b = 0;
for (int i = 0; i < count; i++) {
b++;
}
t1.join();
long end = System.nanoTime();
System.out.println("并行:"+(end-beign));
}
public static void serial(){
long begin = System.nanoTime();
long a = 0;
for (int i = 0; i < count; i++) {
a++;
}
long b = 0;
for (int i = 0; i < count; i++) {
b++;
}
long end = System.nanoTime();
System.out.println("串行:"+(end-begin));
}
}
-
我们并不知道何时并发的两个线程是并行还是并发,这都是取决于线程的调度器的调取问题! -
并发编程也不一定比串行快,如果自增的两数较小,创建变量的时间占据该线程执行的大部分时间,那么就达不到并发编程的优势! -
是否采用多线程编程视情况而定,并非无脑多线程! 一个java 进程中自动会创建一个main 线程!!!并在操作系统有对应该线程!
我们如何查看线程是否在操作系统中创建成功呢? 我们再jdk 中的lib 文件夹下打开jconsole.jar 文件便可以查看线程!!! 我们打开便可以看到我们创建的java 线程以及jvm 自带的一些线程!!!
创建Thread 实例
class Mythread extends Thread{
@Override
public void run() {
System.out.println("run");
}
}
public class Thread_1 {
public static void main(String[] args) {
Thread t1 = new Mythread();
t1.start();
}
}
这里的run 只是描述该线程需要执行那些任务!这只是创建了一个类! 线程的执行需要该线程实例执行start 方法!执行start 方法后操作系统才会创建对应的线程!
- 方法二
创建一个类实现Runnable 接口,重写run 方法,再将该类传参到创建Thread 对象的构造方法!
class MyRun implements Runnable{
@Override
public void run() {
System.out.println("实现Runnable接口创建线程!");
}
}
public class Thread_2 {
public static void main(String[] args) {
Thread t1 = new Thread(new MyRun());
t1.start();
}
}
我们来看看Thread 类的4个构造方法! Thread() 无参构造 通过继承Thread 类 Thread(Runnable target); 传入Runnable 对象 Thread(String name); 给线程命名,便于程序员调试
public class Thread_3 {
public static void main(String[] args) {
Thread t1 = new Thread(){
@Override
public void run() {
System.out.println("Thread匿名内部类!");
}
};
t1.start();
}
}
public class Thread_5 {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Runnable匿名内部类!");
}
});
t1.start();
}
}
public class Thread_6 {
public static void main(String[] args) {
Thread t1 = new Thread(()->{
System.out.println("lambda表达式!");
});
t1.start();
}
}
我们比较推荐使用Runnable 接口创建线程实例! 因为实现Runnable 接口,可以让线程和线程执行的任务,更好进行解耦!!! 简单说就是Runnable 方法创建线程实例, Runnable 可以将线程和线程任务分开! 我们实现一个Runnable 接口,我们只需要把入线程任务描述出来,不必关系那个线程执行该线程任务!,可能是线程,可能是进程,可能是协程,这些我们都不用关系!!!
Thread下的一些重要方法
start();
决定操作系统中是否真正创建了该线程! 如果没有执行该语句,那线程就不会执行!!!
run();
run 只是一个方法,单纯描述了该线程,就是线程内容,并不是真正的run 便没有创建好该线程!!!
中断线程
中断线程就是让线程停下来!
我们有两种方法中断线程,对应的关键就是让run 方法执行完毕!!! 还有一个特殊的main 线程,对于main 线程就需要main 方法执行完毕,线程才会结束!!!
自己创建一个boolean 变量, 来控制线程是否要执行结束!
public class Thread_6 {
public static void main(String[] args) {
boolean isQuit =true;
Thread t1 = new Thread(()->{
while (true&&isQuit){
System.out.println("Hello Thread!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
}
}
我们其他的线程可以控制该标志位isQuit 就可以中断该进程!!!
Thread.interrupted(); 静态方法
Thread.currentThread(); 静态方法 返回当前线程实例!
interrupt(); 实例方法中断线程!
public class Thread_7 {
public static void main(String[] args) {
Thread t1 = new Thread(()->{
while (Thread.currentThread().isInterrupted()){
System.out.println("hello thread!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
t1.interrupt();
}
}
当我们执行上面代码后: 代码打印了异常信息! 因为上方代码线程大部分时间是处于休眠状态阻塞!
而我们的interrupt(); 方法执行后,有两种情况! 1.该进程处于就绪状态,设置标志位为true 2.该进程处于休眠状态,抛出InterruptedException 异常!
所以可以看到当执行完一次run 便抛出异常,程序中断!我们在中断之前也把异常信息打印了!
线程等待join();
每个线程的调度顺序是无法确定的!如果我们要顺序执行某些线程就需要用到线程中的join 方法
public class Thread_9 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
while (true){
}
});
t1.start();
t1.join();
System.out.println("执行main线程");
}
}
显然这个代码,我们无法等待t1 线程执行结束!因为该线程是个死循环无法结束!
而这样的线程等待也毫无意义!毕竟一直死等下去,程序中断在此!
join(毫秒数);
我们的线程等待可以设置毫秒数,如果到了时间,那么其他线程也不会等待该线程,不管该线程是否结束!
public class Thread_9 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
while (true){
}
});
t1.start();
t1.join(10_000);
System.out.println("执行main线程");
}
}
Thread.currentThread(); 静态方法 返回当前线程实例!
我们通过该方法可以获取到当前线程的实例,可以知道某一时刻调用了那个线程!
public class Thread_10 {
public static void main(String[] args) {
Thread t1 = new Thread(){
@Override
public void run() {
System.out.println(this.getName());
}
};
t1.start();
}
}
我们不能通过Runnable 接口来获取当前线程实例,实现该接口的类只是描述了一个线程任务,并无法知道那个线程执行该任务!
sleep();
线程休眠,我们知道如果某线程调用了该方法那么该线程就处于阻塞状态!
我们之前说过,线程在操作系统中,用pcb 结构体结构来描述属性,然后用双向链表连接! 如果某一线程调用了sleep 方法,那么该线程就会在该双向链表中暂时取下,阻塞状态就无法上cpu ~
java线程状态描述
我们的java 对线程的状态又进行了进一步划分!这些状态在操作系统并没有!java 对线程状态进一步划分便于程序员调试bug 当一个线程中断了,这些java 特有的状态描述,便于我们知道该程序是在什么时期卡死的!
new 安排了工作,还未开始行动!
说明此时java 只把线程任务写好,创建好了线程对象,并没有执行start 方法,操作系统并没有创建对应的线程!
runnable 可工作的,又分为正在工作和即将工作
也就是我们已经执行了start 方法,在系统中创建好了对应的线程!一般当线程调用后,如果我们没有进行sleep ,或其他阻塞线程操作,那线程多半处于runnable 状态,在pcb 链表中就绪,随时可以上cpu ,或者已经在cpu 执行!
terminated 工作完成了
操作系统中对应的线程已经执行结束,然而java 中的线程对象还在,获取到的状态!
我们可以通过Thread 类中的getState() 的实例方法,获取当前线程的状态!
public class Thread_11 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(){
@Override
public void run() {
for (int i = 0; i <10; i++) {
System.out.println("hello thread!");
}
}
};
System.out.println("start前:"+t1.getState());
t1.start();
System.out.println("start后:"+t1.getState());
t1.join();
System.out.println("t1结束:"+t1.getState());
}
}
表示排队等待其他事情
下面的3个线程状态都是阻塞状态,但是互相又有些区别!
当前线程在等待锁,导致的阻塞,后面我们会介绍synchronize 而导致的阻塞
public class Thread_13 {
private static int i=0;
public synchronized static void increase(){
for (int j = 0; j <100_0000 ; j++) {
i++;
}
}
public static void main(String[] args) {
Thread t1 = new Thread(){
@Override
public void run() {
increase();
}
};
Thread t2 = new Thread(){
@Override
public void run() {
increase();
}
};
t1.start();
t2.start();
System.out.println("t1:"+t1.getState());
System.out.println("t2:"+t2.getState());
}
}
当前线程在等待唤醒,导致的阻塞,wait 导致的我们后面会介绍!
代码调用了sleep 就会进入timed_waiting 阻塞状态,或者join 超时
public class Thread_12 {
public static void main(String[] args) {
Thread t1 = new Thread(){
@Override
public void run() {
while (true){
System.out.println("hello thread");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t1.start();
System.out.println("start:"+t1.getState());
System.out.println("sleep:"+t1.getState());
}
}
我们再画一个图便于大家理解上述的各种状态!
|