五分钟让你学会Java线程

本文详细介绍了Java中进程与线程的概念、区别,包括线程的组成、特点和创建方式。通过示例代码展示了如何创建和管理线程,以及线程状态。同时,讨论了多线程安全问题和解决方法,如使用`synchronized`关键字确保线程同步。
摘要由CSDN通过智能技术生成

目录:

1.1什么是进程?

1.2什么是线程?

1.3进程和线程的区别是什么?

1.4线程的组成

1.5线程的特点

1.6线程的创建方式

1.7创建线程

1.7.1获取和设置线程的名称

1.8举例演示(Idea)

1.9线程的状态

2.1 Thread类中常用的方法有哪些?

2.2多线程安全问题演示

2.3同步方式

 


 

技术细节:

1.1在学习线程之前我们先了解一下什么是进程:

23ac9366af1f4a65a81b66c6ad786edc.png

1.2那么什么是线程呢?线程和进程的区别又是什么呢?

b8f9305986ba4c8bacf9453f71387c76.png

 1.3线程和进程的区别

1. 进程是操作系统资源分配的基本单位,而线程是CPU的基本调度单位。


2. 一个程序运行后至少有一个线程。


3. 一个进程可以包含多个线程,但是至少需要有一个线程,否则这个进程是没有意义。


4. 进程间不能共享数据段地址,但是同进程的线程之间可以。

1.4线程的组成

*任何一个线程都具有基本的组成部分   
          CPU时间片: 操作系统(OS)会为每个线程分配执行时间  

           *运行数据:
                堆空间: 存储线程需要的对象,多个线程可以共享堆中的数据。
                栈空间:  存储线程需使用的局部变量,每个线程都拥有独立的栈。

            *线程的逻辑代码.

1.5线程的特点

1.线程抢占式执行

      *效率高

      *可防止单一线程长时间独占CPU

2.在单核 CPU 中,宏观上同时执行,微观上顺序执行

1.6线程的创建方式

1. 【继承Thread类,重写run方法】
2. 【实现Runnable接口】
3.  实现Callable接口

1.7创建线程

326e7c6a32ad40c9817f16fe04e53ad1.png

10ced8ccd1554a79b20bf6c5e3064d57.png

 1.7.1获取和设置线程名称

        *获取线程ID 和线程名称

                 * 在Thread的子类中调用this.getId()或this.getName()
                 * 使用Thread.currentThread().getId()和
                  Thread.currentThread().getName()

        *为线程起名

                *通过setName() 为线程起名字

package demo01;

import demo01.MyThread;
//主线程和下面的任务代码,也就是多线程,他们会随机获得时间片
public class Test01 {
    //主线程
    public static void main(String[] args) {
        //1.创建线程对象
        MyThread my = new MyThread();
        //为线程起名字;一定要在线程开启前起名字
        my.setName("线程A");
        //为线程设置优先级
        my.setPriority(Thread.MIN_PRIORITY);
        //2.启动线程----当获取cpu时间片,那么该线程就会执行run方法的任务代码
        my.start();
        //1.创建线程对象
        MyThread my1 = new MyThread();
        //为线程起名字;一定要在线程开启前起名字
        my1.setName("线程B");
        //为线程设置优先级
        my1.setPriority(Thread.MIN_PRIORITY);
        //2.启动线程----当获取cpu时间片,那么该线程就会执行run方法的任务代码
        my1.start();
        //主线程代码
        //主函数的线程名不能用getName拿到;正确方法是Thread.currentThread().getName();意思是获取当前线程
        //Thread.currentThread().getName();可以适应任意场景获取线程名
        for (int j = 0; j < 20; j++) {
            System.out.println(Thread.currentThread().getName()+"=========" + j);
        }
    }
}






package demo01;

public class MyThread extends Thread {//继承Thread
    //重写run方法:线程的执行任务
    public void run() {
        for (int i = 0; i < 20; i++) {
            //getName获取该线程的名字,getName这种方法只能适用于Thread的子类
            System.out.println(getName()+"===============" + i);
        }
    }
}

package demo03;

//以任务的形式实现线程
public class Test03 {
    public static void main(String[] args) {
        //线程任务类对象
        MyRunnable my = new MyRunnable();
        //把任务交给该线程,该线程就会执行自己的任务,而不是执行main的run方法
        //如果不把my交给t1,他就只会执行主函数main
        //构造函数有两个参,一个是任务,还有一个是名字可以给线程传进取个名字;线程a
        Thread t1 = new Thread(my, "线程a");//创建线程对象 Runable target;
        //启动线程
        t1.start();
        for (int i = 0; i < 20; i++) {
            System.out.println("main=============" + i);

        }
    }
}







package demo03;
//该类不是线程类;他是一个线程任务类
public class MyRunnable implements Runnable {
    @Override
    //重写run方法:线程任务代码
    //这里不能用getName来拿到线成名称,因为实现的Runable接口里面没有那个方法
    public void run() {
        for (int i = 0; i <20;i++) {
            System.out.println(Thread.currentThread().getName()+"~~~~~~~~~~~~~~~~~");
        }
    }
}

1.8举例演示

6678d0304ad4458b8058f528d192070e.png

package demo02;

public class Test02 {
    public static void main(String[] args) {
        TicketThread t1 = new TicketThread("窗口A");//线程名:窗口A
        TicketThread t2 = new TicketThread("窗口B");
        TicketThread t3 = new TicketThread("窗口C");
        TicketThread t4 = new TicketThread("窗口D");
        t1.start();
        t2.start();
        t3.start();
        t4.start();

    }
}






package demo02;

public class TicketThread extends Thread {
    private  int ticket = 100;//票数
    //另外一种起线程名字的方法,通过有参构造
    public TicketThread(String name) {
        super(name);
    }
    public void run() {
        while(ticket>0){
            ticket--;
            System.out.println(Thread.currentThread().getName()+"卖了一张票;剩余:"+ticket+"张");
        }
    }
}

27942d0a694c443ea5ebb936ff1dbc25.png

 示例第一种情况.

package demo04;

public class Test04 {
    public static void main(String[] args) {
        TicketTask task = new TicketTask();
        Thread t1 = new Thread(task,"窗口A");
        Thread t2 = new Thread(task,"窗口B");
        Thread t3 = new Thread(task,"窗口C");
        Thread t4 = new Thread(task,"窗口D");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}






package demo04;

public class TicketTask implements Runnable{
    private int ticket = 100;
    @Override
    public void run() {
        while(true) {
            if (ticket>0){
                ticket--;
                System.out.println(Thread.currentThread().getName() + " 卖了一张票;剩余: " + ticket+"张");
            }else {
                break;
            }
        }

    }
}

7f8349dcbe5f4e95be6b28f3682b3a99.png

这种情况会导致:

e67969cc1a87450186b75ce3bb438998.png

2.1 Thread类中常用的方法有哪些?

1.

701c9cbed2764353bfc54c54d8401907.png*sleep代表方法名。

*long millis 代表休眠时间 单位是毫秒。

代码演示

package demo05;

//他是一个线程类,他是继承的
public class MyThread extends Thread {//继承Thread
    //重写run方法:线程的执行任务
    public void run() {
        for (int i = 0; i < 20; i++) {
                //Thread.sleep(500);休眠方法;使用场景:演示代码执行时间超时
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            //getName获取该线程的名字,getName这种方法只能适用于Thread的子类
            System.out.println(getName()+"===============" + i);
        }
    }
}




public class Test01 {
    //主线程
    public static void main(String[] args) {
        //1.创建线程对象
        MyThread my = new MyThread();

        //为线程起名字;一定要在线程开启前起名字
        my.setName("刘德华");

        //2.启动线程----当获取cpu时间片,那么该线程就会执行run方法的任务代码
        my.start();

        //主线程代码
        //主函数的线程名不能用getName拿到;正确方法是Thread.currentThread().getName();意思是获取当前线程
        //Thread.currentThread().getName();可以适应任意场景获取线程名
        for (int j = 0; j < 20; j++) {
            System.out.println(Thread.currentThread().getName()+"=========" + j);
        }
    }
}

685c9398756e440998c469b7296fa55d.png

2.

a9e2c885107f4fa4a6565b407090de40.png

package demo06;

public class MyThread extends Thread {//继承Thread
    //重写run方法:线程的执行任务
    public void run() {
        for (int i = 0; i < 20; i++) {
            Thread.yield();//使用这个方法线程AB交替的概率高
            //getName获取该线程的名字,getName这种方法只能适用于Thread的子类
            System.out.println(getName()+"===============" + i);
        }
    }
}





public class Test01 {
    //主线程
    public static void main(String[] args) {
        //1.创建线程对象
        MyThread my = new MyThread();
        //为线程起名字;一定要在线程开启前起名字
        my.setName("线程A");
      
        //2.启动线程----当获取cpu时间片,那么该线程就会执行run方法的任务代码
        my.start();
        //1.创建线程对象
        MyThread my1 = new MyThread();
        //为线程起名字;一定要在线程开启前起名字
        my1.setName("线程B");
        
        //2.启动线程----当获取cpu时间片,那么该线程就会执行run方法的任务代码
        my1.start();
        //主线程代码
        //主函数的线程名不能用getName拿到;正确方法是Thread.currentThread().getName();意思是获取当前线程
        //Thread.currentThread().getName();可以适应任意场景获取线程名
        for (int j = 0; j < 20; j++) {
            System.out.println(Thread.currentThread().getName()+"=========" + j);
        }
    }
}

 3.

003f56ee69e24a8db692732d76e9c6f4.png

package demo07;

import demo01.MyThread;

//主线程和下面的任务代码,也就是多线程,他们会随机获得时间片
public class Test01 {
    //主线程
    public static void main(String[] args) throws InterruptedException {
        //1.创建线程对象
        MyThread my = new MyThread();
        //为线程起名字;一定要在线程开启前起名字
        my.setName("线程A");

        //2.启动线程----当获取cpu时间片,那么该线程就会执行run方法的任务代码
        my.start();

      my.join();//A线程执行结束,在执行主线程main;A线程使用join进行插队
        for (int j = 0; j < 20; j++) {
            System.out.println("main=========" + j);
        }
    }
}




package demo07;

public class MyThread extends Thread {//继承Thread
    //重写run方法:线程的执行任务
    public void run() {
        for (int i = 0; i < 20; i++) {

            //getName获取该线程的名字,getName这种方法只能适用于Thread的子类
            System.out.println(getName()+"===============" + i);
        }
    }
}

 4.

26b65df412c24a42b9fec7538782a467.png

package demo01;

import demo01.MyThread;
//主线程和下面的任务代码,也就是多线程,他们会随机获得时间片
public class Test01 {
    //主线程
    public static void main(String[] args) {
        //1.创建线程对象
        MyThread my = new MyThread();
        //为线程起名字;一定要在线程开启前起名字
        my.setName("线程A");
        //为线程设置优先级
        my.setPriority(1);
        //2.启动线程----当获取cpu时间片,那么该线程就会执行run方法的任务代码
        my.start();
        //1.创建线程对象
        MyThread my1 = new MyThread();
        //为线程起名字;一定要在线程开启前起名字
        my1.setName("线程B");
        //为线程设置优先级
        my1.setPriority(10);
        //2.启动线程----当获取cpu时间片,那么该线程就会执行run方法的任务代码
        my1.start();
        //主线程代码
        //主函数的线程名不能用getName拿到;正确方法是Thread.currentThread().getName();意思是获取当前线程
        //Thread.currentThread().getName();可以适应任意场景获取线程名
        for (int j = 0; j < 20; j++) {
            System.out.println(Thread.currentThread().getName()+"=========" + j);
        }
    }
}





package demo01;

public class MyThread extends Thread {//继承Thread
    //重写run方法:线程的执行任务
    public void run() {
        for (int i = 0; i < 20; i++) {
            //getName获取该线程的名字,getName这种方法只能适用于Thread的子类
            System.out.println(getName()+"===============" + i);
        }
    }
}

 5.

a5e64899ec1b4c2e9c3642dfb68e57ea.png

296f715a7bdb4d11a4089d9ed8e399f7.png

把t1循环200下

e4d964ef4fe64fcc9b98700e0dda0232.png 设置为守护线程后只循环了54下

 2.2多线程安全问题演示

0f74ee18d97b4c22adb60754c69e6d9d.png

package demo08;

import java.util.ArrayList;
import java.util.Arrays;

public class Test05 {
    private static String arr [] = new String[3];
    private static int index=0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {//匿名内部类
            @Override
            public void run() {
                if (arr[index] == null) {
                    arr[index] = "hello";
                    index++;
                }
            }
        });

        Thread t2 = new Thread(new Runnable() {//匿名内部类
            @Override
            public void run() {
                if (arr[index] == null) {
                    arr[index] = "world";
                    index++;
                }
            }
        });
        t1.start();
        t2.start();

        t1.join();
        t2.join();//必须让这两个线程进行插队,因为 System.out.println(Arrays.asList(arr));也是一个线程,不进行插队
        //主线程如果抢到事件片,会输出为空,因为前两个线程没有抢到事件片,没有往数组里添加内容
        System.out.println(Arrays.asList(arr));
    }
}

 *会出现这种情况

f1aa1328e06e4fc5b061e3b33132f8d2.png

*原因

 因为两个线程同时操作这个数组,当word进行添加时,还没有来得及放值 ,然后时间片到了,就进行了hello

2.3同步方式

概要:当前线程执行完之后,另外一个线程才能执行

094fa44d13d644488f858f75cfc0f0c3.png

错误演示:

 1.fc13f33b520949fda49b3ad6ce61e7ec.png

2.

b224f63f988b4dacbb12b571a9200849.png

3.

9843b25e918d4d128ac23f0e7164dee1.png

 正确演示:

 1.

package demo08;

import java.util.ArrayList;
import java.util.Arrays;

public class Test05 {
    private static String arr [] = new String[3];
    private static int index=0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {//匿名内部类
            @Override
            public void run() {
               synchronized (arr) {
                   if (arr[index] == null) {
                       arr[index] = "hello";
                       index++;
                   }
               }
            }
        });

        Thread t2 = new Thread(new Runnable() {//匿名内部类
            @Override
            public void run() {
                synchronized (arr) {
                    if (arr[index] == null) {
                        arr[index] = "world";
                        index++;
                    }
                }
            }
        });
        t1.start();
        t2.start();

        t1.join();
        t2.join();//必须让这两个线程进行插队,因为 System.out.println(Arrays.asList(arr));也是一个线程,不进行插队
        //主线程如果抢到事件片,会输出为空,因为前两个线程没有抢到事件片,没有往数组里添加内容
        System.out.println(Arrays.asList(arr));
    }
}

8f0b00666cf34ff089d2c419e2553005.png

 *输出结果不会只出现一个,另外一个没有执行的情况

2.

package demo04;

public class Test04 {
    public static void main(String[] args) {
        TicketTask task = new TicketTask();
        Thread t1 = new Thread(task,"窗口A");
        Thread t2 = new Thread(task,"窗口B");
        Thread t3 = new Thread(task,"窗口C");
        Thread t4 = new Thread(task,"窗口D");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}




package demo04;

public class TicketTask implements Runnable{
    private int ticket = 100;
    @Override
    public void run() {
        while(true) {
            synchronized (this) {//这里是同步代码块,必须获取()里面的锁资源,才能进入同步代码块中
                //this可以当锁,因为 TicketTask task = new TicketTask();  只new了一个
                //这样就不会出现重卖,或者超卖的现象了
                //必须原子操作要求
                if (ticket>0){
                    ticket--;
                    System.out.println(Thread.currentThread().getName() + " 卖了一张票;剩余: " + ticket+"张");
                }else {
                    break;
                }
            }
        }

    }
}

*输出结果不会出现重复

!!!补充

String:不可变,因为底层是final修饰的字符数组,所以不可变

StringBuilder:可变,线程不安全,效率高,底层方法没有加锁

StringBuffer:可变,线程安全,效率较低,底层方法加了锁

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值