java-多线程

多线程

解释

  • 进程-是一个应用程序
  • 线程-是一个进程中的执行单元,一个进程可以启动多个线程
  • 两个进程,A和B的内存独立不共享
  • 栈内存是独立的,一个线程一个栈,但是堆内存和方法区只有一个
  • 假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行各自的,这就是多线程并发
  • 使用多线程,主线程结束,子线程不一定结束
    java-多线程_第1张图片

实现线程的两种方式

  • 继承线程类->java.lang.Thread,重写run方法
public class MyThread {
    public static void main(String[] args) {
        myThread myThread = new myThread();
        //start方法的作用,启动子线程,在JVM中开辟一个新的栈空间,这段代码执行后立即结束
        //这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开辟处理,start()方法就结束了,线程启动成功
        //启动成功的线程会自动调用run,并且run方法在分支栈的栈底部(压栈)
        //run方法在分支栈的栈底部,main方法在主栈的栈底部,run和main是平级的,它不会启动线程,不会分配栈空间
        //不用start函数而直接使用run方法实际使用的是单线程
        myThread.start();
        //主线程执行
        for (int i=0;i<1000;i++){
            System.out.println("主线程--->"+i);
        }
    }
}


//编写一个类继承Thread类,重写run方法
class myThread extends Thread{
    @Override
    public void run() {
        super.run();
        for (int i=0;i<100;i++){
            System.out.println("分支线程--->"+i);
        }
    }
}
  • 编写类,实现java.lang.Runnable接口
public class MyThread {
    public static void main(String[] args) {
        myRunnable myRunnable = new myRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start();
        for (int i=0;i<100;i++){
            System.out.println("主线程--->"+i);
        }
    }
}


//编写一个类显示Runable接口
class myRunnable implements Runnable{
    @Override
    public void run() {
        for (int i=0;i<100;i++){
            System.out.println("分支线程--->"+i);
        }
    }
}
  • 建议使用第二种,面向接口编程,还可以继承其它的类,更灵活
  • 使用匿名内部类实现
public class MyThread {
    public static void main(String[] args) {
        //创建匿名内部类
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i=0;i<100;i++){
                    System.out.println("分支线程--->"+i);
                }
            }
        });
        thread.start();
        for (int i=0;i<100;i++){
            System.out.println("主线程--->"+i);
        }
    }
}

线程生命周期

java-多线程_第2张图片

常用方法

  • 获取当前线程对象
  • 获取线程对象的名字
  • 修改线程对象的名字
public class MyThread {
    public static void main(String[] args) {
        //创建线程对象
        Thread t = new myThread();
        //设置线程的名字
        t.setName("tttt");
        //获取线程名字
        String tName = t.getName();

        //获取当前线程对象,这段代码出现在哪个线程,获得的就是哪个线程
        //如现在这个指的就是主线程,如果这段代码出现在子线程中,获取的就是子线程
        Thread currentThread = Thread.currentThread();
    }
}

class myThread extends Thread{
    @Override
    public void run() {
        super.run();
        //当前获取的就是子线程
        Thread currentThread = Thread.currentThread();
    }
}

线程的sleep方法

  • 静态方法
  • 参数是毫秒
  • 让当前线程进入休眠,进入“阻塞状态”,放弃占用CPU时间片
    • Thread.sleep()
public class MyThread {
    public static void main(String[] args) {
        //创建线程对象
        Thread t = new myThread();
        //设置线程的名字
        t.setName("tttt");
        //获取线程名字
        String tName = t.getName();
        //主线程调用子线程对象的sleep方法不会使子线程阻塞,它是一个静态方法
        //这段代码的作用,让当前线程进入休眠,也就是说main线程进入休眠
        try {
            t.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class myThread extends Thread{
    @Override
    public void run() {
        super.run();
        for (int i=0;i<100;i++){
            System.out.println("子线程"+i);
        }
    }
}

线程手动终止睡眠

  • 注意是终止线程睡眠,不是终止线程执行
public class MyThread {
    public static void main(String[] args) {
        //创建线程对象
        Thread t = new myThread();
        //设置线程的名字
        t.setName("tttt");
        //获取线程名字
        String tName = t.getName();
        //主线程调用子线程对象的sleep方法不会使子线程阻塞,它是一个静态方法
        //这段代码的作用,让当前线程进入休眠,也就是说main线程进入休眠
        try {
            t.sleep(1000*60*60);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //中断睡眠(这种中断睡眠的方式依靠java的异常处理机制)
        //它会导致子线程抛出异常,所有sleep方法使用时都需要try/catch,
        //这样中断睡眠时会捕获异常并继续执行,而不会中断线程
        //注意这个方法是可以在主线程调用中断子线程睡眠
        t.interrupt();
    }
}

class myThread extends Thread{
    @Override
    public void run() {
        super.run();
        try {
            Thread.sleep(1000*60*60);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i=0;i<100;i++){
            System.out.println("子线程"+i);
        }
    }
}

强行终止一个线程

//5s后强制中断线程,这种方法是直接杀死线程,会导致数据丢失
t.stop(); //已过时,不建议使用

合理终止线程

public class MyThread {
    public static void main(String[] args) {
        //创建线程对象
        myThread t = new myThread();
        try {
            t.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //5s后强制中断线程,这种方法是直接杀死线程,会导致数据丢失
        //t.stop(); //已过时,不建议使用

        //合理中断线程,改变子线程的标志状态
        t.run = false;
    }
}

class myThread extends Thread{
    boolean run = true; //设置线程执行的标志,方便终止线程
    @Override
    public void run() {
        for (int i=0;i<100;i++){
            if (run){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else {
                //终止当前线程
                return;
            }
        }
    }
}

线程调度

  • 线程调度常见模式
    • 抢占式调度模型-哪个线程的优先级高,抢到cpu时间片的概率就高,java采用的就是抢占式
    • 均分式调度模型-平均分配cpu时间片,每个线程占有cpu时间片的时间长度一样
  • java提供的常见线程优先级有关的方法
    • void setPriority(int newPriority) 设置线程的优先级
    • int getPriority() 获取线程优先级
    • 最低优先级1->Thread.MAX_PRIORITY
    • 默认优先级5->Thread.MIN_PRIORITY
    • 最高优先级10->Thread.NORM_PRIORITY
    • yield() 让位方法,暂停当前正在执行的线程对象,它不是阻塞,它会让线程从运行状态回到就绪状态,继续参与cpu时间片的抢夺,优先级可能会降一些
    • void join() 当前线程进入阻塞状态
public class MyThread {
    public static void main(String[] args) throws InterruptedException {
        //创建线程对象
        myThread t = new myThread();
        try {
            t.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //这个方式会使当前线程进入阻塞,等待t线程执行结束后再继续执行当前线程
        //注意它阻塞的是当前线程,而不是t线程
        t.join();
    }
}

class myThread extends Thread{
    boolean run = true;
    @Override
    public void run() {
        for (int i=0;i<100;i++){
            if (run){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else {
                //终止当前线程
                return;
            }
        }
    }
}

线程优先级

  • 最低优先级1->Thread.MAX_PRIORITY
  • 默认优先级5->Thread.MIN_PRIORITY
  • 最高优先级10->Thread.NORM_PRIORITY
  • int getPriority() 获取线程优先级
  • 优先级高,只是抢到cpu时间片相对多一些,是处于运行的时间的多一些,而不是谁先抢到

线程安全

  • 多线程修改共享数据时存在安全问题
  • 解决方案:存在安全问题时使用线程同步机制
  • 异步线程模型
  • 同步编程模型

线程同步机制-synchronized

  • synchronized(){}
  • synchronized三种写法
    • 同步代码块: synchronized(线程共享对象){同步代码块}
    • 在实例方法上使用:对象锁,创建多少就有多少锁,所以共享一定要用this关键字修饰,public synchronized void sett(){} 此时一定锁的是this,不能是其他对象,这种形式不灵活,且整个方法都需要同步
    • 在静态方法上使用:表示找类锁,类锁永远只有一把
  • 方法区越小,运行效率越高
  • 在java语言中,任何一个对象都有一把锁,100个对象,100把锁,当执行过程中,遇到了synchronized时,该线程会占有这把锁,只有同步代码块执行完毕后,这把锁才可以重新被获取
  • 最好不要嵌套使用,容易造成死锁,具体看死锁案例
public void Money(double m){
        //以下几行代码必须是线程排队的,不能并发
        //一个线程把这里的代码全部执行结束之后,另一个线程才能进来
        /*
        线程同步机制的语法是:
            synchronized (){
                //线程同步代码
            }
            synchronized后面小括号中传的这个“数据”是相当关键的
            这个数据必须是多线程共享的数据。才能达到多线程排队
            如:
            假设t1,t2,t3,t4,t5,有5个线程
            你只希望t1,t2,t3排队,t4,t5不需要排队
            你一定要在()中写一个t1,t2,t3共享的对象,而这个对象对应t4,t5来说不是共享的
            此处传入this是因为我们假设新建了一个账户对象,并且在这个对象中实现了取款方法,所以
            这个账户对象就是我们的共享对象,所以添this,具体参数请根据实际情况填写
         */
        synchronized (this){
            double before = this.getMoney();
            double after = before - m;
            this.setMoney(after);
        }

    }

java中三大变量的线程共享问题

  • 实例变量->在堆空间中
    • 堆只有一个,存在共享问题
  • 静态变量->在方法区中
    • 方法区只有一个,存在共享问题
  • 局部变量->在栈中
    • 局部变量用于都不会存在线程安全问题,因为局部变量不共享(一个线程一个栈),局部变量不会共享。

死锁

  • 案例
public class DeadLock {
    public static void main(String[] args) {
        Object o1 = new Object();
        Object o2 = new Object();
        Thread t1 = new MyThread1(o1,o2);
        Thread t2 = new MyThread2(o1,o2);
    }
}

class MyThread1 extends Thread{
    Object o1;
    Object o2;

    public MyThread1(Object o1, Object o2){
        this.o1 = o1;
        this.o2 = o2;
    }
    public void run(){
        synchronized (o1){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized(o2){

            }
        }
    }
}

class MyThread2 extends Thread{
    Object o1;
    Object o2;

    public MyThread2(Object o1, Object o2){
        this.o1 = o1;
        this.o2 = o2;
    }
    public void run(){
        synchronized (o1){
            synchronized (o1){
                synchronized(o2){

                }
            }
            synchronized(o2){

            }
        }
    }
}

其他

  • java语言中线程分为两大类
    • 用户线程
    • 守护线程
  • 守护线程->代表:垃圾回收线程
    • 一般守护线程是一个死循环,用户线程结束,守护线程自动结束
public class DeadLock {
    public static void main(String[] args) {
        Thread t = new yourThread();
        //设置成守护线程
        t.setDaemon(true);
        t.start();
    }
}


class yourThread extends Thread{
    @Override
    public void run() {
        super.run();
    }

    public void go(){
        
    }
}

  • 定时器->间隔一定时间执行
    • 自定义sleep方法,太low,一般不用
    • 使用开发好的方法:java.util.Timer
    • 使用框架自带方法,底层一般也是使用的java.util.Timer
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.SimpleFormatter;

public class TimerTest {
    public static void main(String[] args) throws Exception{
        //创建定时器对象
        Timer timer = new Timer();

        //指定定时任务schedule(定时任务,第一次执行时间,间隔多久执行)
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date fisterTime = sdf.parse("2020-03-14 09:30:00");

        timer.schedule(new LogTimerTask(),fisterTime,1000*10);
    }
}

//编写定时任务
class LogTimerTask extends TimerTask{
    @Override
    public void run() {
        //编写需要执行的任务
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String strTime = sdf.format(new Date());
        System.out.println(strTime+":成功完成任务");
    }
}

  • FutrueTask方式,实现Callable接口实现线程(java8新特性)
    • 这种方法可以获取线程的返回值
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class TimerTest {
    public static void main(String[] args) throws Exception{
        //创建一个“未来任务类”对象,参数需要给衣蛾Callable接口实现类对象
        FutureTask task = new FutureTask(new Callable() { //这个方法返回一个泛型数据
            @Override
            public Object call() throws Exception { //call方法就相当于run方法
                //线程执行一个任务,返回结果
                System.out.println("call");
                Thread.sleep(1000*10);
                System.out.println("结束");
                int a = 100;
                return a;
            }
        });

        //创建线程对象
        Thread t = new Thread(task);
        t.start();

        //在主线程获取t线程返回结果,它会导致当前线程阻塞,直到拿到线程返回值
        Object obj = task.get();
    }
}

  • Object类中的wait和notify方法(生产者消费者模型)
    • wait和notify方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为两个方式是Object类自带的
    • wait方法的作用,会让调用此方法的对象进入等待状态
    • notify 唤醒调用wait方法的对象
    • notifyAll()方法,唤醒所有等待的线程
      java-多线程_第3张图片

你可能感兴趣的:(JAVA学习之身在迷途,java,多线程)