当前位置: 代码迷 >> 综合 >> Java学习笔记之----------多线程的创建,状态,安全问题
  详细解决方案

Java学习笔记之----------多线程的创建,状态,安全问题

热度:47   发布时间:2024-02-26 13:27:24.0

Java学习笔记之----------多线程

    • 进程
    • 线程
    • 多线程存在的意义
    • 创建多线程
    • 线程的几种状态
    • 创建多线程(推荐)
    • 多线程的安全问题

进程

进程:应用程序在内存中分配的空间(正在运行中的程序)

线程

线程
进程中负责程序执行的执行单元,也称为执行路径
一个进程至少有一个线程负责该进程的执行,称主线程
如果一个进程启用了多个线程,就称该程序为多线程程序

多线程存在的意义

多线程有时并不能提高效率,所以多线程存在的意义不是提高效率,多线程其实是合理使用资源
解决多部分代码同时执行的需求,合理使用CPU资源
多线程的运行是根据CPU的切换完成的,如何切换是由CPU决定的,所以多线程运行具有随机性
JVM中的线程至少有两个
一个是负责自定义代码运行的
一个是负责垃圾回收的

class Demo{
    //定义垃圾回收方法public void finalize(){
    System.out.println("clear is ok");}
}
public class GcTest{
    public static void main(String args[]){
    new Demo();new Demo();new Demo();System.gc();//启动垃圾回收器System.out.println("TEST OVER!");	}
}

在这里插入图片描述
可以看到,垃圾回收器启动了之后不一定执行,执行与否不确定,由CPU说了算
:因为我的JDK版本较高,所以在编译时出现了警告,显示finalize方法已经过时
在这里插入图片描述
在这里插入图片描述
每个线程都有运行的代码内容,称为线程的任务。而之所以创建线程就是为了去运行指定的任务代码
线程的任务都封装在特定的区域中。例如:主线程运行的任务都定义在main方法中

创建多线程

单线程

//单线程示例
class People{
    private String name;//初始化People(String name){
    this.name = name;}public void show(){
    for(int x = 1;x<11;x++){
    System.out.println(name+"-----"+x);}}
}
public class ThreadDemo{
    public static void main(String args[]){
    People p1 = new People("LILY");People p2 = new People("LUCY");p1.show();p2.show();}
}

在这里插入图片描述

创建多线程
1.继承Thread类
2.覆盖run方法
3.创建子类对象就是创建线程对象
4.调用Thread类中的start方法就可以执行线程,并会调用run方法
在这里插入图片描述

//继承Thread类
class People extends Thread{
    private String name;//初始化People(String name){
    this.name = name;}public void show(){
    for(int x = 1;x<11;x++){
    System.out.println(name+"-----"+x);}}//覆盖run方法public void run(){
    show();}
}
public class ThreadDemo{
    public static void main(String args[]){
    People p1 = new People("LILY");People p2 = new People("LUCY");p1.start();//调用start方法,start会调用run方法,run方法再调用show方法p2.start();}
}

在这里插入图片描述
可以看到,代码两次运行后的结果有区别,说明两个线程的运行是由CPU确定的,运行具有不确定性。

获取当前线程对象名
使用Thread类中的getName()

//单线程示例
class People extends Thread{
    private String name;//初始化People(String name){
    this.name = name;}public void show(){
    for(int x = 1;x<11;x++){
    System.out.println(getName()+"-----"+name+"-----"+x);}}
}
public class ThreadDemo1{
    public static void main(String args[]){
    People p1 = new People("LILY");People p2 = new People("LUCY");p1.show();p2.show();}
}

在这里插入图片描述
Thread-0,Thread-1仅仅代表了当前对象的名字,不代表线程被开启。

获取当前线程路径,(开启线程)
使用Thread.currentThread()方法
:需要调用start方法启动线程

class People extends Thread{
    private String name;//初始化People(String name){
    this.name = name;}public void show(){
    for(int x = 1;x<11;x++){
    //获取自定义的两个线程当前路径System.out.println(Thread.currentThread().getName()+"-------"+name+"-----"+x);}}//覆盖run方法public void run(){
    show();}
}
public class ThreadDemo{
    public static void main(String args[]){
    People p1 = new People("LILY");People p2 = new People("LUCY");p1.start();//调用start方法,start调用run方法,run方法再调用show方法p2.start();//获取主线程当前路径for(int i = 1;i<11;i++){
    System.out.println(Thread.currentThread().getName()+"-----"+i);}}
}

在这里插入图片描述
当启动了p1.start()后是继续执行p2.start()还是执行run方法是不确定的,取决于CPU所分配的时间片,如果当前CPU在主线程上,则会继续启动p2。
可以看到,主线程和自定义的两个线程执行的顺序是不一致的,体现了CPU的随机性。

问题:调用start和调用run有什么区别
:调用run多线程是没有被开启的,此时只有主线程在执行。而调用start是启动run里面自定义的线程对象
线程路径和线程对象名的区别
在这里插入图片描述
线程结束的条件是所属线程里没有其他方法

线程的几种状态

在这里插入图片描述
:sleep需要指定睡眠时间,单位为毫秒

创建多线程(推荐)

首先,我们先写一个售票的小例子,假设总共有火车票100张,开启三个窗口去售卖这些车票

class TicketDemo  extends Thread{
    private int tickets = 100;public void run(){
    while(true){
    		if(tickets>0){
    System.out.println(Thread.currentThread().getName()+"-----"+tickets--);}}		}
}
public class SaleTicket{
    public static void main(String args[]){
    TicketDemo t1 = new TicketDemo();TicketDemo t2 = new TicketDemo();TicketDemo t3 = new TicketDemo();//开启三个线程t1.start();t2.start();t3.start();}
}

在这里插入图片描述
但是运行结果似乎有一点点的不对劲,我们总共100张票,但是三个线程都输出了相同的数,就是说,一张票在不同的窗口被售卖了三次,这正常吗?明显不正常!

分析代码可以得知,我们在堆中new了三个TicketDemo,每个TicketDemo中都有100张票,但是我们在本例子中只需要一个TicketDemo,那么,如何在实例化一个对象的前提下,开启三个或者多个线程呢

此处需要注意:如果只实例化一个对象,那么当前线程只有2个,即主线程和自定义开启的线程,但要注意的是,多次启用一个线程是非法的,特别是当线程已经结束执行后,不能再重新启动,如果线程已启动,会抛出IllegalThreadStateException
在这里插入图片描述
是否可以使用Thread类对象来创建四个线程呢?

public class SaleTicket{
    public static void main(String args[]){
    Thread t1 = new Thread();Thread t2 = new Thread();Thread t3 = new Thread();//开启三个线程t1.start();t2.start();t3.start();		}
}

在这里插入图片描述
运行代码会看到没有输出结果,这是为什么呢?这里需要注意的是,我们使用Thread创建线程,那么start调用的run方法会是父类自身的run方法,而该方法是一个空方法,所以没有任何输出结果。

创建多线程方式二-----实现Runnable接口
1.定义一个类实现Runnable。
2.覆盖Runnable接口中的run方法。
3.通过Thread类创建线程对象,并将实现了Runnable接口的对象作为Thread类的构造函数的参数进行传递。
4.调用start方法,启动线程。

//实现Runnable接口
class TicketDemo implements Runnable{
     //extends Thread{
    private int tickets = 100;public void run(){
    while(true){
    		if(tickets>0){
    System.out.println(Thread.currentThread().getName()+"-----"+tickets--);}}		}
}
public class SaleTicket{
    public static void main(String args[]){
    //TicketDemo t1 = new TicketDemo();//TicketDemo t2 = new TicketDemo();//TicketDemo t3 = new TicketDemo();TicketDemo t = new TicketDemo();Thread t1 = new Thread(t);Thread t2 = new Thread(t);Thread t3 = new Thread(t);//开启三个线程t1.start();t2.start();t3.start();		}
}

在这里插入图片描述
实现Runnable接口的好处
1.避免了继承Thread类的单继承的局限性
2.Runnable接口更符合面向对象,将线程单独进行对象的封装
3.Runnable接口降低了线程对象和线程任务的耦合性

多线程的安全问题

CPU时间片的分配可能会导致程序运行时出错
测试代码

//实现Runnable接口
class TicketDemo implements Runnable{
     //extends Thread{
    private int tickets = 100;public void run(){
    while(true){
    		if(tickets>0){
    //让线程在该处暂停try{
    Thread.sleep(10);}catch(InterruptedException e){
    }System.out.println(Thread.currentThread().getName()+"-----"+tickets--);}}		}
}

在这里插入图片描述

可以看到,程序执行到最后时,出错了。出错的原因是当某一个线程在执行了if判断之后,CPU将时间片分配给了另一个进程,当时间片又分配给当前进程时,当前进程无需做if判断而直接输出,导致了输出结果异常。
解决方式-----同步代码块

//实现Runnable接口
class TicketDemo implements Runnable{
     //extends Thread{
    private int tickets = 100;//使用一个现有的类创建对象Object obj = new Object();public void run(){
    while(true){
    /*使用同步代码块,是一种内部机制,当有线程使用时,其他线程没有该对象的使用权synchronized(对象){需要被同步的代码}同步机制其实就是锁的机制*/synchronized(obj){
    	if(tickets>0){
    //让线程在该处暂停try{
    Thread.sleep(10);}catch(InterruptedException e){
    }System.out.println(Thread.currentThread().getName()+"-----"+tickets--);}}}		}
}
public class SaleTicket{
    public static void main(String args[]){
    //TicketDemo t1 = new TicketDemo();//TicketDemo t2 = new TicketDemo();//TicketDemo t3 = new TicketDemo();TicketDemo t = new TicketDemo();Thread t1 = new Thread(t);Thread t2 = new Thread(t);Thread t3 = new Thread(t);//开启三个线程t1.start();t2.start();t3.start();		}
}

在这里插入图片描述
同步在目前状态下保证了一次只有一个线程在执行,其他进程无法进入。
好处:解决了多线程的安全问题
弊端:降低了效率,但是在可接受的范围,一种以效率换安全的方式
注意:synchronized(对象)中需要用公有的锁,例子中如果换成synchronized(new Obiect()),那么每个进程使用的都是自己的锁,无法做到同步。
同步锁使用的前提是首先判断程序是不是多线程,单线程无需用到同步锁,且多个线程在同步中必须使用同一个锁。

  相关解决方案