java-day13-多线程

13.1 概述-进程&线程

进程:就是应用程序在内存中分配空间(正在运行中的程序)
线程:是进程中负责程序执行的执行单元,也成为执行路径。

  • 一个进程中至少有一个线程在负责该进程的运行
  • 如果一个进程中出现了多个线程,就称该程序为多线程程序。

13.2 多线程技术原理

举例:运动场—鸟巢。水立方。

多线程技术:解决多部分代码同时执行的需求。可以合理使用CPU资源。

CPU某一时刻只能处理一个线程。用时间片的方式,非常快速的切换线程执行。

13.3 JVM中的多线程&垃圾回收

多线程的运行根据CPU的切换完成的,怎么切换CPU说了算,所以多线程运行有一个随机性(CPU快速切换造成的)

jvm中的多线程。至少有两个线程:
一个是负责自定义代码运行的,这个从main方法开始执行的线程称之为主线程。
一个是负责垃圾回收的。

package day13;

class Demo1{

    //定义垃圾回收方法
    public void finalize(){
        System.out.println("demo ok");
    }

}
public class FinalizeDemo {
    public static void main(String[] args){

        new Demo1();
        new Demo1();
        new Demo1();
        System.gc();//启动垃圾回收器。
        System.out.println(".............");

    }
}

输出结果之一:(输出的顺序每次都是不确定的)

demo ok
.............
demo ok
demo ok

或者

.............
demo ok
demo ok
demo ok

或者....

通过实验,发现每次得结果不一定相同,因为随机性造成的。

而且每一个线程都有运行的代码内容,这个称之为线程的任务。
之所以创建一个线程就是为了去运行指定的任务代码。
而线程的任务都封装在特定的区域中。

比如:
主线程运行的任务都是定义在main方法中
垃圾回收线程在收垃圾都会运行finalize方法。

13.4 单线程的问题

package day13;

class Demo2{

    private String name;

    Demo2(String name){
        this.name = name;
    }

    public void show(){
        for(int i=0; i<10; i++){
            System.out.println(name+",i="+i);
        }
    }
}

public class ThreadDemo {
    public static void main(String[] args){
        Demo2 d1 = new Demo2("A");
        Demo2 d2 = new Demo2("B");
        d1.show();
        d2.show();
    }
}

输出结果:

A,i=0
A,i=1
A,i=2
A,i=3
A,i=4
A,i=5
A,i=6
A,i=7
A,i=8
A,i=9
B,i=0
B,i=1
B,i=2
B,i=3
B,i=4
B,i=5
B,i=6
B,i=7
B,i=8
B,i=9

可以看出主线程在执行的时候,是先执行A 再执行B
可是A和B的执行是没有关系的,如何同时执行呢??

13.5 创建线程方式-继承Thread类

如何开辟一个执行路径?
通过查阅API文档 java.lang.Thread类。
该类的描述中有创建线程的两种方式:

1.继承Thread类

  • 继承Thread类。
  • 覆盖run方法
  • 创建子类对象,就是创建线程对象
  • 调用Thread类中的start方法就可以执行线程,并会调用run方法。

start()开启线程后,都会执行run方法,说明run方法存储的是线程要运行的代码。
所以,记住,自定义线程的任务代码都存储在run方法中。

package day13;

class Demo2 extends Thread{

    private String name;

    //覆盖run 方法
    public void run(){

        show();
    }

    Demo2(String name){
        this.name = name;
    }

    public void show(){
        for(int i=0; i<10; i++){
            System.out.println(name+",i="+i);
        }
    }
}

public class ThreadDemo {
    public static void main(String[] args){
        Demo2 d1 = new Demo2("A");
        Demo2 d2 = new Demo2("B");
        d1.start();
        d2.start();
        for (int i=0; i<10; i++){
            System.out.println("mian"+i);
        }
    }
}

输出结果:

mian0
mian1
mian2
mian3
mian4
mian5
mian6
mian7
mian8
mian9
B,i=0
A,i=0
B,i=1
A,i=1
B,i=2
A,i=2
A,i=3
B,i=3
A,i=4
B,i=4
A,i=5
B,i=5
A,i=6
B,i=6
A,i=7
B,i=7
A,i=8
B,i=8
A,i=9
B,i=9

主线程,d1和d2启动线程,三个线程同时执行。

13.6 调用start和run的区别

获取线程的名字:
在run()方法里面直接getName()就可以得到线程的名字。
如果获取主线程的名字,就先获取当前线程,然后使用getName()

start()方法做了两件事:

  • 开启线程
  • 调用run方法
package day13;

class Demo2 extends Thread{

    private String name;

    //覆盖run 方法
    public void run(){

        for(int i=0; i<40; i++ ){
            System.out.println(getName()+"......"+name+"...."+i);
        }
    }

    Demo2(String name){
        this.name = name;
    }

    public void show(){
        for(int i=0; i<10; i++){
            System.out.println(name+",i="+i);
        }
    }
}

public class ThreadDemo {
    public static void main(String[] args){
        Demo2 d1 = new Demo2("A");
        Demo2 d2 = new Demo2("B");
        d1.start();
        d2.start();
        for (int i=0; i<10; i++){
            System.out.println(Thread.currentThread().getName()+"----------mian"+i);
        }
    }
}

输出:

main----------mian0
main----------mian1
main----------mian2
main----------mian3
main----------mian4
main----------mian5
main----------mian6
main----------mian7
main----------mian8
main----------mian9
Thread-0......A....0
Thread-1......B....0
Thread-0......A....1
Thread-1......B....1
Thread-0......A....2
Thread-1......B....2
Thread-0......A....3
Thread-1......B....3
Thread-1......B....4
Thread-1......B....5
Thread-1......B....6
Thread-1......B....7
Thread-1......B....8
Thread-1......B....9
Thread-0......A....4
Thread-0......A....5
Thread-0......A....6
Thread-0......A....7
Thread-0......A....8
Thread-0......A....9

将start改成run:

package day13;

class Demo2 extends Thread{

    private String name;

    //覆盖run 方法
    public void run(){

        for(int i=0; i<10; i++ ){
            System.out.println(Thread.currentThread().getName()+"......"+name+"...."+i);
        }
    }


    Demo2(String name){
        this.name = name;
    }

    public void show(){
        for(int i=0; i<10; i++){
            System.out.println(name+",i="+i);
        }
    }
}

public class ThreadDemo {
    public static void main(String[] args){
        Demo2 d1 = new Demo2("A");
        Demo2 d2 = new Demo2("B");
        d1.run();
        d2.run();
        for (int i=0; i<10; i++){
            System.out.println(Thread.currentThread().getName()+"----------mian"+i);
        }
    }
}

输出:

main......A....0
main......A....1
main......A....2
main......A....3
main......A....4
main......A....5
main......A....6
main......A....7
main......A....8
main......A....9
main......B....0
main......B....1
main......B....2
main......B....3
main......B....4
main......B....5
main......B....6
main......B....7
main......B....8
main......B....9
main----------mian0
main----------mian1
main----------mian2
main----------mian3
main----------mian4
main----------mian5
main----------mian6
main----------mian7
main----------mian8
main----------mian9

调用start和run的区别:

  • 调用start会开启线程,让开启的线程去执行run方法中的线程任务。
  • 直接调用run方法,线程并未开启,去执行run方法的只有主线程。

13.7 多线程的内存图

在这里插入图片描述
在这里插入图片描述

13.8 多线程的运行状态

在这里插入图片描述

13.9 售票示例

package day13;

class SaleTicket extends Thread{
    private int tickets = 10;

    //卖票的代码需要被多个线程执行,所以要将这些代码定义在线程任务中。run方法。
    public void run(){
        while (tickets>0){
            System.out.println(getName()+"........."+tickets--);
        }
    }
}

public class TicketDemo {
    public static void main(String[] args){
        //创建四个线程,会创建40张票,不建议票变成静态的。
        // 所以如何共享这10张票。需要将资源和线程分离。
        SaleTicket t1 = new SaleTicket();
        SaleTicket t2 = new SaleTicket();
        SaleTicket t3 = new SaleTicket();
        SaleTicket t4 = new SaleTicket();

        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

输出结果:

Thread-1.........10
Thread-2.........10
Thread-3.........10
Thread-0.........10
Thread-3.........9
Thread-2.........9
Thread-1.........9
Thread-2.........8
Thread-3.........8
Thread-0.........9
Thread-3.........7
Thread-2.........7
Thread-1.........8
Thread-2.........6
Thread-3.........6
Thread-0.........8
Thread-3.........5
Thread-2.........5
Thread-1.........7
Thread-2.........4
Thread-3.........4
Thread-0.........7
Thread-3.........3
Thread-2.........3
Thread-1.........6
Thread-2.........2
Thread-3.........2
Thread-0.........6
Thread-3.........1
Thread-2.........1
Thread-1.........5
Thread-1.........4
Thread-1.........3
Thread-0.........5
Thread-1.........2
Thread-0.........4
Thread-1.........1
Thread-0.........3
Thread-0.........2
Thread-0.........1

发现一件奇怪的事情,明明总共只有十张票,为什么卖出去了10*4 = 40张票?

因为线程对象和线程任务捆绑在一起,每个线程都是在处理自己的tickets。
怎么解决这种问题??——声明实现Runnable接口的类。

13.10 创建线程的方式二——实现Runnable接口

使用方法:

  1. 定义一个类实现Runnable
  2. 覆盖Runnable接口中的run方法,将线程要运行的任务代码存在到该方法中。
  3. 通过Thread类创建线程对象,并将先实现了Runnable接口的对象作为Thread类的构造函数的参数进行传递。
  4. 调用Thread类的start方法,开启线程。

Runnable接口的出现:
解耦:降低了线程对象和线程任务的耦合性。

package day13;

 //1. 定义一个类实现Runnable
class SaleTicket1 implements Runnable{
     private static  int tickets = 10;

      //2. 覆盖Runnable接口中的run方法,将线程要运行的任务代码存在到该方法中。
     //卖票的代码需要被多个线程执行,所以要将这些代码定义在线程任务中。run方法。
     public void run(){
         while (tickets>0){
             System.out.println(Thread.currentThread().getName()+"........."+tickets--);
         }
     }
 }
public class TicketsDemo2 {
    public static void main(String[] args){

        SaleTicket1 s = new SaleTicket1();

		 //3. 通过Thread类创建线程对象,并将先实现了Runnable接口的对象作为Thread类的构造函数的参数进行传递。
        //创建四个线程。通过Thread类对象
        Thread t1 = new Thread(s);
        Thread t2 = new Thread(s);
        Thread t3 = new Thread(s);
        Thread t4 = new Thread(s);
        
		// 4. 调用Thread类的start方法,开启线程。
        t1.start();
        t2.start();
        t3.start();
        t4.start();


    }
}

结果:

Thread-0.........10
Thread-0.........6
Thread-0.........5
Thread-0.........4
Thread-0.........3
Thread-0.........2
Thread-0.........1
Thread-2.........8
Thread-1.........9
Thread-3.........7

13.11 实现Runnable接口的好处

实现Runnable接口的好处:

  • 避免了继承Thread类的单继承的局限性
  • Runnable接口出现等符合面向对象,将线程单独进行对象的封装
  • Runnable接口的出现,降低了线程对象和线程任务的耦合性

所以以后创建线程都是用第二种方式。

13.12 安全问题的-原因&同步

package day13;

 //1. 定义一个类实现Runnable
class SaleTicket1 implements Runnable{
     private static  int tickets = 10;

     //卖票的代码需要被多个线程执行,所以要将这些代码定义在线程任务中。run方法。
     public void run(){
         while (tickets>0){
             try{
                 Thread.sleep(10);  //让线程到这里稍微停一下。
             }catch (InterruptedException e){
             }
             System.out.println(Thread.currentThread().getName()+"........."+tickets--);
         }
     }
 }
public class TicketsDemo2 {
    public static void main(String[] args){

        SaleTicket1 s1 = new SaleTicket1();
        //SaleTicket1 s2 = new SaleTicket1();

        //创建四个线程。通过Thread类对象
        Thread t1 = new Thread(s1);
        Thread t2 = new Thread(s1);
        Thread t3 = new Thread(s1);
        Thread t4 = new Thread(s1);

        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

输出:

Thread-0.........8
Thread-3.........7
Thread-1.........9
Thread-2.........10
Thread-0.........6
Thread-1.........5
Thread-3.........6
Thread-2.........4
Thread-3.........3
Thread-1.........2
Thread-0.........1
Thread-2.........0
Thread-3.........-1
Thread-1.........-1

警告:竟然买到了第-1张票。

多线程的安全问题,产生的原因是什么?

  1. 线程任务中有处理到共享的数据。
  2. 线程任务中有多条对共享数据的操作。一个县城在操作共享数据的过程中,其他线程参与了运算,造成了数据的错误。

解决的思想: 只要保证多条操作共享数据的代码在某一时间段,被一条线程执行,在执行期间不允许其他线程参与运算。

怎么体现?
用到了同步代码块。
synchronized(对象){

// 需要被同步的代码
}

package day13;

 //1. 定义一个类实现Runnable
class SaleTicket1 implements Runnable{
     private static  int tickets = 10;
     Object obj = new Object();
     //卖票的代码需要被多个线程执行,所以要将这些代码定义在线程任务中。run方法。
     public void run(){
         {
             while (true){
                 synchronized (obj){
                     if (tickets>0){
                         try{
                             Thread.sleep(10);  //让线程到这里稍微停一下。
                         }catch (InterruptedException e){
                         }
                         System.out.println(Thread.currentThread().getName()+"........."+tickets--);
                     }
                 }
             }
         }

     }
 }
public class TicketsDemo2 {
    public static void main(String[] args){

        SaleTicket1 s1 = new SaleTicket1();
        //SaleTicket1 s2 = new SaleTicket1();

        //创建四个线程。通过Thread类对象
        Thread t1 = new Thread(s1);
        Thread t2 = new Thread(s1);
        Thread t3 = new Thread(s1);
        Thread t4 = new Thread(s1);

        t1.start();
        t2.start();
        t3.start();
        t4.start();


    }
}

输出:

Thread-0.........10
Thread-0.........9
Thread-0.........8
Thread-2.........7
Thread-2.........6
Thread-3.........5
Thread-3.........4
Thread-1.........3
Thread-1.........2
Thread-1.........1

13.13 同步的好处和弊端

例子:
火车上的卫生间

同步在目前情况下保证了一次只能有一个线程在执行,其他线程进不来。这就是同步的机制。

好处:解决了多线程的安全问题。
弊端:降低效率。

13.14 同步的前提

有可能出现这样的情况:
多线程安全问题出现后,加入了同步机制,没有想到的是,安全问题依旧存在,怎么办?
只要遵守了同步的前提,就可以解决了。

同步的前提:
多个线程在同步中必须使用同一个锁,这才是对多个线程同步。

以下同步代码中,有多个锁:

package day13;

 //1. 定义一个类实现Runnable
class SaleTicket1 implements Runnable{
     private static  int tickets = 10;
     Object obj = new Object();
     //卖票的代码需要被多个线程执行,所以要将这些代码定义在线程任务中。run方法。
     public void run(){
         {
             while (true){
                 synchronized (new Object()){
                     if (tickets>0){
                         try{
                             Thread.sleep(10);  //让线程到这里稍微停一下。
                         }catch (InterruptedException e){
                         }
                         System.out.println(Thread.currentThread().getName()+"........."+tickets--);
                     }
                 }
             }
         }

     }
 }
public class TicketsDemo2 {
    public static void main(String[] args){

        SaleTicket1 s1 = new SaleTicket1();
        //SaleTicket1 s2 = new SaleTicket1();

        //创建四个线程。通过Thread类对象
        Thread t1 = new Thread(s1);
        Thread t2 = new Thread(s1);
        Thread t3 = new Thread(s1);
        Thread t4 = new Thread(s1);

        t1.start();
        t2.start();
        t3.start();
        t4.start();


    }
}

结果:

Thread-2.........9
Thread-3.........7
Thread-0.........8
Thread-1.........10
Thread-1.........6
Thread-3.........4
Thread-2.........3
Thread-0.........5
Thread-1.........2
Thread-2.........1
Thread-0.........0
Thread-3.........2
Thread-1.........-1

13.15 同步的小问题

线程任务里面,多条操作共享数据的代码才需要同步,其余不需要同步。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值