java线程

一、概念

1、程序,进程,线程

  1. 程序:没有运行的、并且能完成一定功能的静态代码,对比火车站售票厅
  2. 进程:进程指的是运行中的程序,比如qq或微信,就启动了一个进程,操作系统会为该进程分配内存空间。如果再使用别的软件又会是一个新的进程,操作系统会该软件分配新的内存空间。进程是程序的一次执行过程,或是正在运行的一个程序。它有自身产生、存在和消亡过程
  3. 线程:线程是由进程创建的,是进程的一个实体。一个进程可以有多个线程。

2、单线程和多线程

单线程:同一时刻,只允许执行一个线程。
多线程:同一时刻,可以执行多个线程

3、并发和并行

并发:同一时刻多个任务交替执行。
并行:同一时刻多个任务同时执行。

4、线程使用的场景

● 网络连接tomcat,mysql,一个连接对一个一个线程, one connection one thread
● 文件操作,文件下载,后台启动一个线程异步执行长时间的任务

二、多线程实战

1、创建线程

创建线程的方式有两种:继承Thread类或者实现Runable接口

2、通过Thread类创建线程

(1)使子类继承Thread类
(2)重写Thread类中的run方法,在run方法中加入自己的业务逻辑
(3)创建子类对象
(4)子类对象调用start()方法

继承Thread创建线程:
public static void main(String[] args) {
    Cat cat = new Cat();
    cat.start();  
}
**class Cat extends Thread{
    @Override
    public void run(){
        int times=0;
        //该线程每个一秒就会输出一次
        while (true) {
            System.out.println("喵喵,我是一只小猫咪~"+(++times));
            try {
                //Thread.sleep使该线程休眠一秒
                Thread.sleep(1000);  
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (times==8){
                break;             //times=8时,线程退出
            }
        }
    }
}

3、start方法:

(1)虽然每个线程最终执行的是run方法,但是要启动线程需要调用start()方法
(2)main线程是主线程,当执行程序时相当开启了一个进程,main就是主线程,main中的其他线程是子线程
(3)如果main线程中启动了一个子线程,主线程不会阻塞会继续执行(子线程不会随main方法结束而结束)
(4)start( )方法:启动线程,start()方法底层最终还是会执行run方法,执行线程时还会继续向下进行程序
(5)如果直接调用run方法,则只是调用了一个普通方法,并没有启动线程,执行完run方法才会向下执行

4、 Runable方式实现多线程

(1)使类实现Runable接口
(2)重写接口中的run方法,加入自己业务逻辑
(3)创建类对象
(4)创建Thread类对象,将类对象包装到Thread类对象中
(5)通过Thread类对象调用start方法

方式1:
实现runnable接口创建线程
public static void main(String[] args) {
        Dog dog = new Dog();
        new Thread(dog).start();
    }
class Dog implements Runnable{
    int num=0;
    @Override
    public void run() {
        while (true) {
            System.out.println("狗在叫。。"+(++num));
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

方式2:
通过匿名内部类直接创建
new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i <= 100; i++) {
                    System.out.println(Thread.currentThread().getName() +  "\t" +  i);
                }
            }
        }).start();

5、new Thread(dog).start( );

为什么实现了runnable接口却需要创建Thread类来调用start方法?
因为runnable接口中没有start()方法,想开启线程就需要执行start()方法,所以还是要创建Thread类

6、Runnable具有的优势

我们都知道在java中一个类只能继承一个类,但是一个类却可以实现多个接口。所以如果通过继承Thread方式创建线程,那么这个类无法继承别的类,导致资源无法共享。当你通过runable方式创建线程就会不一样了。
总结:如果一个类既要继承别的类也要创建线程,需要使用runnable接口创建线程

7、线程的优先级

涉及的方法:
(1)getPriority() : 返回线程优先值
(2)setPriority(int newPriority) : 改变线程的优先级

线程的优先级等级
(1)MAX_PRIORITY: 10
(2)MIN _PRIORITY: 1
(3)NORM_PRIORITY: 5

注意:低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用

8、守护线程

(1)Java中的线程分为两类:一种是守护线程,一种是用户线程
(2)守护线程是程序运行的时候在后台提供一种通用服务的线程。所有用户线程停止,进程会停掉所有守护线程,退出程序。
(3)守护线程是用来服务用户线程的,通过在start()方法前调用,thread.setDaemon(true)可以把一个用户线程变成一个守护线程。

示例:
public class MyThread03 {
    public static void main(String[] args) {
        Person person = new Person();
        person.setDaemon(true);   //设置守护线程
        person.start();
        System.out.println("主线程执行完毕~");
    }
}

class Person extends Thread{
    @Override
    public void run() {
        while (true){
            System.out.println("一个人在吃饭");
        }
    }
}

通过示例可以发现,当main方法也就是主线程执行完毕后,本该继续无限循环的子线程也随之结束了,这就是守护线程。

9、线程休眠sleep()

(1)线程休眠指的是通过sleep()方法设置一个毫秒数,使线程暂停运行这个毫秒数后继续运行。
(2)sleep()方法是一个静态方法,可以直接通过Thread.sleep()调用。
(3)所有线程都可以设置休眠,包括主线程。
(4)调用sleep()方法会出现编译异常,需要处理,抛出或者捕获。

示例:
public class MyThread03 {
    public static void main(String[] args) {
        Person person = new Person();
        person.start();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程休眠了5秒");
        System.out.println("主线程运行结束~");
    }
}

class Person extends Thread{
    @Override
    public void run() {
            for (int i = 1; i < 11; i++) {
                System.out.println("一个人在吃饭");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("子线程休眠第"+i+"次");
            }
    }
}

10、 线程合并(join)

(1)当某个程序执行流中调用其他线程的 join() 方法时, 调用线程将被阻塞,直到 join() 方法加入的 join线程执行完为止。
(2)可以理解为main线程为主线程,其他开启的线程为子线程,通过join()方法将子线程加入到了主线程,这样程序就只有了一个main线程,代码自然遵循从上到下,从左到右执行
(3)使用join()方法也会出现编译类型异常,需要抛出或捕获

示例:
public class MyThread03 {
    public static void main(String[] args) {
        Person person = new Person();
        person.start();
        try {
            System.out.println("子线程加入了主线程,等待子线程执行完毕再向下执行");
            person.join();
            System.out.println("子线程执行完毕~");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程最后执行完毕~");
    }
}

class Person extends Thread{
    @Override
    public void run() {
            for (int i = 1; i < 11; i++) {
                System.out.println("一个人在吃饭");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    }
}

11、线程结束

(1)stop:强制线程停止执行,一般不推荐使用,传输文件时会造成数据丢失

示例:
public class MyThread03 {
    public static void main(String[] args) {
        Person person = new Person();
        person.start();
        try {
            System.out.println("5秒后子线程结束");
            Thread.sleep(5000);
            person.stop();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程最后执行完毕~");
    }
}

class Person extends Thread{
    @Override
    public void run() {
            for (int i = 1; i < 11; i++) {
                System.out.println("一个人在吃饭");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    }
}

(2)中断信号
发起中断请求:

调用public void interrupt()方法:
中断此线程。
除非当前线程正在中断(始终允许), 否则将调用此线程的 checkAccess 方法,这可能会导致抛
出 SecurityException 。
如果线程在阻塞状态时发出中断请求,那么它的中断状态将被清除,并且将收到InterruptedException 。

接收中断信号两个方法:
调用public static boolean interrupted()方法
测试当前线程是否已被中断。 此方法清除线程的中断状态 。 换句话说,如果连续两次调用此方
法,则第二次调用将返回false(除非当前线程在第一次调用已清除其中断状态之后且在第二次调
用检查之前再次中断)。
线程中断被忽略,因为在中断时线程不活动将被此方法反映返回false。
结果:true如果当前线程已被中断; 否则为false 。

调用public boolean isInterrupted()方法
测试此线程是否已被中断。 线程的中断状态不受此方法的影响。
线程中断被忽略,因为在中断时线程不活动将被此方法反映返回false。
结果:true如果此线程被中断; 否则为false 。

案例:

public class Demo5 {
public static void main(String[] args) {
InterruptThread interruptThread = new InterruptThread();
interruptThread.start();
//让主线程进入IO阻塞
JOptionPane.showMessageDialog(null, "是否确认向下执行...."); //主线程进IO阻塞
interruptThread.interrupt(); //主线程发送中断信号=true
	}
}

class InterruptThread extends Thread {
@Override
	public void run() {
		while (true) {
System.out.println("a");
//正常逻辑,收到中断信号后,退出循环
			if (this.isInterrupted()) { //②
				break;
			}
// try {
// Thread.sleep(1000); //线程中断信号=true,这时执行阻塞方法,抛出
异常,重新设置中断信号=false
// } catch (InterruptedException e) {
// e.printStackTrace();
// this.interrupt(); //因为上面重新设置了中断信号=false,所以这里继续
执行,否则后面3代码不执行
// }
//
// if (this.isInterrupted()) { //①
// break;
// }
		}
	}
}

12、线程常用方法总结

1.如果希望主线程去控制子线程t的停止,可以修改子线程while循环的条件loop,使T的while循环终止后从而退出run方法
2.常用方法:
setName 设置线程名称
getName 返回线程名称
start 线程开始执行,虚拟机底层调用该线程的start0方法
setPriority 更改线程优先级
getPriority 获取线程优先级
sleep 在指定毫秒内让当前线程休眠(暂停执行)
interrupt 中断线程休眠,当该线程执行到一个interrupt方法时,就会catch一个异常,直接跳过休眠执行catch语句,相当于中断了休眠
yield 线程的礼让,礼让有可能不成功,时间因素也不确定,与线程调度器(CPU)有关
join 线程插队,若两个线程同时执行,线程2执行这个方法一旦插队成功,线程1会让线程2先执行完毕后再继续执行

三、 线程原理

1、 线程的生命周期状态

(1)java线程的生命周期状态分为6种,细化来说runnable中又分为就绪状态和真正运行状态,这样也可以说线程生命周期有7种状态。
(2)当创建了线程并调用了start()方法就会进入runnable状态,runnable代表可运行状态,说明线程可以运行但不是真正运行,真正运行要取决于是否获取cpu时间片。
(3) 等待阻塞:运行的线程执行wait()方法,该线程进入等待池中
(4)同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则该线程进入锁池中
(5)其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
(6)死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
在这里插入图片描述

四、线程同步

1、线程同步问题

线程同步:一些敏感数据不允许被多个线程同时访问,就要用到线程同步技术,保证在任意同一时刻最多有一个线程访问,以保证数据的完整性。也可以理解为,当有一个线程在对内存进行操作时,其他线程都不对内存地址进行操作,直到该线程完成操作。

2、Synchronized:锁

解决线程同步问题目前可以通过加锁,以后会有更好的解决方式,先介绍一下synchronized

(1)同步代码块方式加锁,同一时刻代码块只能有一个线程使用此代码块
synchronized(对象){
//需要被同步的代码
}

(2)同步方法加锁,同一时刻只能有一个线程使用此方法
public synchronized void method(){
//需要被同步的代码
}

为什么线程同步需要加锁呢?比如我们想将一个对象中的属性从0自增25000次,这时我们针对这个对象开启了3个线程,理想情况下每个线程各司其职,相互配合的话,每个线程都会自增25000次,总体自增了75000。但结果并不是75000。

案例:

public class MyThread03 {
    public static void main(String args[]){
        EE ee = new EE();    //创建ee对象
        FF ff1 = new FF(ee);    //将ee传入FF中
        FF ff2 = new FF(ee);
        FF ff3 = new FF(ee);
        //开启3个线程
        ff1.start();
        ff2.start();
        ff3.start();
        //主线程休眠2s,目的是等子线程执行完看最终结果
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(ee.getNums());
    }
}

class EE{
    private int nums;

    public void setNums() {
        nums++;
    }

    public int getNums() {
        return nums;
    }
}

class FF extends Thread{
    private EE ee;

    public FF(EE ee) {     //通过构造器得到EE对象实例
        this.ee = ee;
    }

    @Override
    public void run() {
        for (int i = 0; i < 25000; i++) {
            ee.setNums();         //循环执行ee的自增方法
        }
    }
}

结果:
在这里插入图片描述

因为如果不加锁,所有线程可能会同时执行同一个代码,就好比EE中的nums,在某一刻所有线程会同时读取到同一个nums值,又会在某一刻同时对nums自增,这样不管多少线程,在这一刻nums只是自增了1,而通过加锁可以使线程排队执行自增,这样就会避免问题了。

3、Synchronized的使用

1、加锁操作:
对于并发工作, 你需要某种方式来防止两个任务访问相同的资源(其实就是共享资源竞争) 。 防止这种冲突的方法就是当资源被一个任务使用时, 在其上加锁。 第一个访问某项资源的任务必须锁定这项资源, 使其他任务在其被解锁之前, 就无法访问它了, 而在其被解锁之时, 另一个任务就可以锁定并使用它了。
(1)任意对象都可以作为同步锁。 所有对象都自动含有单一的锁(监视器) 。
(2)同步方法的锁:静态方法(类名.class) 、 非静态方法(this)
(3)同步代码块:自己指定, 很多时候也是指定为this或类名.class
(4)锁加在多个线程公共使用的区域
(5)同步代码块要比同步方法范围小、执行效率高,所以实际中优先选择同步代码块

2、释放锁操作
(1)当前线程的同步方法、同步代码块执行结束。
(2)当前线程在同步代码块、同步方法中遇到break、 return终止了该代码块、该方法的继续执行。
(3)当前线程在同步代码块、同步方法中出现了未处理的Error或Exception, 导致异常结束。
(4)当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。

案例:对上面代码做一些小小的改动,然后查看结果

将setNums方法中加上同步代码块
public void setNums() {
        synchronized (this) {
            nums++;
        }
    }

结果:
在这里插入图片描述
synchronized括号里指定的是类锁或对象锁,类锁可以理解为在java中每个类都自带一把锁,对象锁可以理解为每个对象实例都自带一把锁。代码中的this指的是当前类的对象锁,除了this也可以指定其他类的锁,但是每个类和对象实例自身都只有一把锁,当开启多个线程时,其中一个线程拿到你指定类的锁后,其他线程就会排队等待这个锁的释放。

4、死锁

(1)死锁是指两个线程都因互相占用对方需要的资源导致程序不能向下进行
(2)synchronized嵌套容易发生死锁

案例:先用4种不同的类锁来执行代码

public class MyThread03 {
    public static void main(String args[]){
        EE ee = new EE();
        FF ff = new FF(ee);
        GG gg = new GG(ee);
        long start=System.currentTimeMillis();    //开始时间
        ff.start();
        gg.start();
        try {
            ff.join();
            gg.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long end=System.currentTimeMillis();     //结束时间
        System.out.println("执行时间="+(end-start)/1000+"s\t执行结果:"+ee.getNums());
        System.out.println("主线程执行完毕");
    }
}

class EE extends Thread{
    private int nums;

    public int getNums() {
        return nums;
    }

    public void m1() {
        synchronized (Object.class) {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (String.class) {
                nums++;
            }
        }
    }
    public void m2(){
        synchronized (Integer.class){
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (Byte.class){
                nums--;
            }
        }
    }
}

class FF extends Thread{
    private EE ee;

    public FF(EE ee) {
        this.ee = ee;
    }

    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            ee.m1();
        }
    }
}

class GG extends Thread{
    private EE ee;

    public GG(EE ee) {
        this.ee = ee;
    }

    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            ee.m2();
        }
    }
}

结果:
在这里插入图片描述
可以看到4种不同类锁成功执行完毕,用时6s

案例:用2种相同的类所来执行代码

public class MyThread03 {
    public static void main(String args[]){
        EE ee = new EE();
        FF ff = new FF(ee);
        GG gg = new GG(ee);
        long start=System.currentTimeMillis();    //开始时间
        ff.start();
        gg.start();
        try {
            ff.join();
            gg.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long end=System.currentTimeMillis();     //结束时间
        System.out.println("执行时间="+(end-start)/1000+"s\t执行结果:"+ee.getNums());
        System.out.println("主线程执行完毕");
    }
}

class EE extends Thread{
    private int nums;

    public int getNums() {
        return nums;
    }

    public void m1() {
        synchronized (Object.class) {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (String.class) {
                nums++;
            }
        }
    }
    public void m2(){
        synchronized (String.class){
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (Object.class){
                nums--;
            }
        }
    }
}

class FF extends Thread{
    private EE ee;

    public FF(EE ee) {
        this.ee = ee;
    }

    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            ee.m1();
        }
    }
}

class GG extends Thread{
    private EE ee;

    public GG(EE ee) {
        this.ee = ee;
    }
    
    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            ee.m2();
        }
    }
}

结果:
在这里插入图片描述
可以看到,超过6s后,程序依然没执行完毕,因为发生了死锁。

重点在这里:

public void m1() {
        synchronized (Object.class) {       //看这     
            try {
                Thread.sleep(2000);   //停2s为了不让一个线程把两个锁瞬间都占用了
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (String.class) {     //看这
                nums++;
            }
        }
    }
    public void m2(){
        synchronized (String.class){            //看这
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (Object.class){         //看这
                nums--;
            }
        }
    }

m1和m2都因需要对方的锁资源而导致程序不能向下进行,发生了死锁。
所以,在使用内部锁时,为了避免死锁尽量不要嵌套锁,或者使用重入锁。

5、wait和notify

Java 对象中的 wait、 notify 两类方法就如同信号开关,二者之间是有联系的,用来进行等待方和通知方的之间交互。

wait
(1)当线程调用了 locko(某个同步锁对象)的 wait 方法后, JVM 会将当前线程加入 locko监视器的
WaitSet(等待集),等待被其他线程唤醒。
(2)当前线程会释放 locko 对象监视器的 Owner 权利,让其他线程可以抢夺 locko 对象的监视器。
(3)让当前线程等待,其状态变成 WAITING

notify
(1)当线程调用了 locko(某个同步锁对象) 的 notify 方法后, JVM 会唤醒 locko 监视器WaitSet 中
的第一条等待线程。
(2)当线程调用了 locko 的 notifyAll 方法后, JVM 会唤醒 locko 监视器 WaitSet 中的所有等待线程。
(3)等待线程被唤醒后,会从监视器的 WaitSet 移动到 EntryList,线程具备了排队抢夺监视器 Owner
权利的资格,其状态从 WAITING 变成 BLOCKED。
(4) EntryList 中的线程抢夺到监视器 Owner 权利之后,线程的其状态从 BLOCKED 变成,
Runnable,具备重新执行的资格。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值