Java学习:多线程

一、学习目的

每个正在系统上运行的程序都是一个进程。每个进程包含一到多个线程。进程也可能是整个程序或者是部分程序的动态执行。线程是一组[指令]的集合,或者是[程序]的特殊段,它可以在程序里独立执行。也可以把它理解为代码运行的上下文。所以线程基本上是轻量级的进程,它负责在单个程序里执行多任务通常由[操作系统]负责多个线程的调度和执行。线程是程序中一个单一的顺序控制流程.在单个程序中同时运行多个线程完成不同的工作,称为多线程。

二,学习内容

(1)线程和进程的区别

进程:正在运行的一个程序,系统会为这个程序分配独立的内存空间
线程:程序具体执行任务的最小单位, 一个进程最少拥有一个线程(主线程 运行起来就执行的线程), 线程之间是共享内存资源的(进程申请的),线程之间可以通信 数据传递 多数为主线程和子线程之间的数据传递,每一个线程都有自己的运行回路(生命周期)

(2)线程的生命周期
  • NEW:新建 线程刚被创建好 。但是在Java层面的线程被创建了,而在操作系统中的线程其实是还没被创建的,所以这个时候是不可能分配CPU执行这个线程的!
  • RUNNABLE:可运行状态 就绪状态 只要抢到时间片就可以运行这个线程
  • BLOCKED:阻塞状态 sleep wait,这个状态下也不能分配CPU
  • WAITING(无时间限制的等待状态):这个状态下也是不能分配CPU执行的。有三种情况会使得Runnable状态到waiting状态
  • TIME_WAITING(有时间限制的等待状态):其实这个状态和Waiting就是有没有超时时间的差别,这个状态下也是不能分配CPU执行的。有五种情况会使得Runnable状态到waiting状态
  • TERMINATED(终止状态):在我们的线程正常run结束之后或者run一半异常了就是终止状态

    生命周期
(3)多线程的优点

如果在主线程中存在有比较耗时的操作,比如下载视频 上传文件 数据处理等,这些操作会阻塞主线程,后面的任务必须等这些任务执行完毕,之后才能执行,用户体验比较差。所以 为了不阻塞主线程,需要将耗时的操作放在子线程中去处理。

(4)多线程的缺点

如果有大量的线程,会影响性能,因为操作系统需要在它们之间切换。更多的线程需要更多的内存空间。线程可能会给程序带来更多“bug”,因此要小心使用。线程的中止需要考虑其对程序运行的影响。

(5)如何创建多线程

1,写一个类继承于Thread,并实现run()方法

class TestThread extends Thread{
//实现run方法
// 方法里面就是具体需要执行的代码
public void run(){
    String name = Thread.currentThread().getName();
    for (int i = 1; i 

创建线程:创建两个线程

 public class MyClass {
  static TestThread tt2;
  public static void main(String[] args) {
   //main方法里面的代码都是在主线程里面执行的 主线程的名称是main
   String name = Thread.currentThread().getName();
   System.out.println(name);
   // 创建Thread的对象
   TestThread tt = new TestThread();
   //设置线程的名称 主线程的名称不可更改
   tt.setName("子线程1");
   //执行任务
  tt.start();
    for (int i = 0; i 
输出结果部分截图

2,实现Runnable接口,
a,创建任务 创建类实现Runnable接口
b,使用Thread为这个任务分配线程
c,开启任务 start

class XEJThread implements Runnable{
@Override
public void run() {
    for (int i = 0; i 

创建线程(四种方法):

   //第二种,接口 抽象方法
   //创建一个任务:创建一个类实现Runnable接口
   XEJThread xt = new XEJThread();
    //使用Thread操作这个任务
    Thread t1 = new Thread(xt);

   t1.setName("子线程1");
   t1.start();

  Thread t2 = new Thread(xt);

    t2.setName("子线程2");
    t2.start();

    //这个任务只能执行一次
    Thread t3 = new Thread(new Runnable() {
        @Override
        public void run() {
            for (int i = 0; i  {
       for (int i = 0; i 
输出结果部分截图
(6)线程安全

一、使用synchronized锁
需要一个同步监听器 需要一把锁,任何一个对象都有一个监听器
1,同步代码块: 如果多个线程操作同一个代码块,并且需要同步 那么必须操作同一个对象,同一把锁

   synchronized(监听器/对象/锁){
     //需要枷锁的代码
    }

2,同步方法: 同步监听器是当前对象本身, 必须确保多个对象调用的同步方法是操作的是一个对象,这种方法容易错,使用同步代码快就可以了

    public synchronized void test(){
          //需要执行的代码
       }

1,同步代码块

  //用于买票的任务
class Ticket implements Runnable {
//定义所有车票的数量
public static int num = 100;
String name;

public Ticket(String name) {
    this.name = name;
}

static final Object obj = new Object();
//同步代码块
@Override
public void run() {
    for(int i = 1; i  0) {
                System.out.println(name + "出票" + num);
                num-;
            } else {
                break;
            }
        }
     }
   }
 }

2,同步方法

 @Override
 public void run() {
   test();
 }
 public synchronized void test() {
   for (int i = 1; i  0) {
           System.out.println(name + "出票" + num);
          num--;
       } else {
           break;
       }
   }
 }
}

二、使用ReentrantLock同步

//创建一个可重入的锁
static ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
    for(int i = 1; i  0) {
                System.out.println(name + "出票" + num);
                num--;
            } else {
                break;
            }
            //解锁
       lock.unlock();
   }
 }
(7)线程间的通信:让两个线程交替执行
//用于买票的任务
class Ticket implements Runnable {
//定义所有车票的数量
public static int num = 100;
String name;

public Ticket(String name) {
    this.name = name;
}
static ReentrantLock lock1 = new ReentrantLock();
static final Object obj = new Object();
//同步代码块
@Override
public void run() {
    for(int i = 1; i  0) {
                System.out.println(name + "出票" + num);
                num--;
                try {
                    obj.notify();//唤醒其他线程 通知其他线程执行
                    obj.wait(); //让自己线程等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } else {
                break;
            }
        }
   }
 }
}

三、利用接口实现线程之间的数据回调

1,Agent类,创建一个接口

package day11.Demo;
public class Agent extends Thread {
AgentInterface target; //记录谁需要数据
@Override
public void run() {
    System.out.println(Thread.currentThread().getName());
    System.out.println("开始找房子");
    System.out.println("----------");
    System.out.println("房子找到了 即将返回类型");
    target.callBack("西南大学学区房");
    super.run();
}
public interface AgentInterface{
    void callBack(String desc);
  }
}

2,Person类,需要接受数据的都需要实现AgentInterface接口

package day11.Demo;
public class Person implements Agent.AgentInterface {
public void needHouse(){
    Agent xw = new Agent();
    xw.target = this;
    xw.start();
}
@Override
public void callBack(String desc) {
    System.out.println("我是小王,接收到你的数据"+desc);
}

}
3,运行一下

package day11.Demo;
public class MyClass {
public static void main(String[] args) {
    Person zs = new Person();
    zs.needHouse();
 }
}
运行结果

四、学习感悟

多线程的学习内容多,使用灵活并且容易出错,因此在使用的过程中一定要小心仔细总的来说我们需要把握如何创建一个线程 ,线程的同步,主线程与子线程之间使用接口回调数据,线程间的通信,这四点内容,勤加练习,定会有所收获。