Java多线程

本文详细介绍了Java中线程的创建、状态及控制方法,包括继承Thread类与实现Runnable接口两种方式的区别,线程状态的变化及如何通过各种方法如sleep、yield等控制线程的行为。此外还探讨了线程同步机制的重要性及其实现方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

线程创建

  • 自定义线程类继承自Thread类
  • 重写run()方法,编写线程执行体
  • 创建线程对象,调用start()方法启动线程
    /**
     * 创建线程
     */
    public class test_thread extends Thread {
        @Override
        public void run() {
            // run() 方法线程体
            for (int i = 0; i < 200; i++) {
                System.out.println("我在子线程"+i);
            }
        }
        public static void main(String[] args){
            // 创建一个线程对象
            test_thread test_thread1 = new test_thread();
            // 调用start方法开启线程
            test_thread1.start();
    
            // main线程,主线程
            for (int i = 0; i < 200; i++) {
                System.out.println("我在主线程"+i);
            }
        }
    }
    
    
    • 【注】若直接调用子线程的run()方法,则不是多线程交替执行,而会调用run()方法执行
    • 【注】线程开启,不一定立即执行,由cpu调度执行。

实现Runnable

  • 定义类实现Runnable接口
  • 重写run()方法,编写线程执行体
  • 创建线程thread对象,将实现接口放入,调用start()方法启动线程
    package thread;
    
    public class MyRunnable implements Runnable {
        @Override
        public void run() {
            // run() 方法线程体
            for (int i = 0; i < 200; i++) {
                System.out.println("我在子线程"+i);
            }
        }
        public static void main(String[] args){
            // 创建runnable 接口的实现类对象
            MyRunnable myRunnable = new MyRunnable();
            // 创建线程对象,丢入实现类
            Thread thread = new Thread(myRunnable);
            // 开启线程
            thread.start();
            // main线程,主线程
            for (int i = 0; i < 200; i++) {
                System.out.println("我在主线程"+i);
            }
        }
    }
    
    

对比

  • 继承Thread类
    • 子类继承Thread类具备多线程能力
    • 启动,子类对象调用strat方法
    • 不建议用,避免OOP单继承局限性
  • 实现Runnable接口
    • 实现Runnable接口具有多线程能力
    • 启动,传入目标对象+thread对象.start()
    • 推荐用,避免单继承局限性。方便一个对象同时被多个线程使用

初识java并发问题

  • 当多个线程同时操作一个共享资源时,会发生数据紊乱问题。

Lamda表达式

  • 首先介绍函数式接口、
    • 任何接口,如果只包含唯一一个抽象方法,那它就是一个函数式接口(例如Runnable接口,包含一个run的抽象方法)
    • 对于函数式接口,可以和Lamda表达式来创建该接口的对象
  • 推导lamda表达式,在没有lamda表达式时,我们是这样做的
    package lamda;
    
    /**
     * 推导Lamda表达式
     *
     */
    public class TestLamda {
        public static void main(String[] args) {
            Like ilike = new Like();
            ilike.lamda();
    
        }
    }
    // 1.定义一个函数式接口
    interface Ilike{
        void lamda();
    }
    // 2.实现类
    class Like implements Ilike{
    
        @Override
        public void lamda() {
            System.out.println("i like lamda");
        }
    }
    
  • 然后,我们进行修改,使用静态内部类
//3. 静态内部类
    static class Like2 implements Ilike{

        @Override
        public void lamda() {
            System.out.println("i like lamda");
        }
    }
    public static void main(String[] args) {
        Like ilike = new Like();
        ilike.lamda();
        Like2 like2 = new Like2();
        like2.lamda();


    }
  • 再进一步简化,局部内部类
 //4. 局部内部类
        class Like3 implements Ilike{

            @Override
            public void lamda() {
                System.out.println("i like lamda3");
            }
        }
        like = new Like3();
        like.lamda();
  • 再进一步简化,去类名,匿名内部类
 //5 匿名内部类,没有类的名称,借助接口或者父类
        like = new Ilike() {
            @Override
            public void lamda() {
                System.out.println("i like lamda5");
            }
        };
        like.lamda();
  • 再简化,lamda(什么都不要,只保留了方法)
 //6,用lamda简化
        like = ()->{
            System.out.println("i like lamda6");};
        like.lamda();
  • 【注】lamda表达式必须是函数式接口
  • 完整代码(较上有改动)
package lamda;

/**
 * 推导Lamda表达式
 *
 */
public class TestLamda {
    //3. 静态内部类
    static class Like2 implements Ilike{

        @Override
        public void lamda() {
            System.out.println("i like lamda2");
        }
    }
    public static void main(String[] args) {

        Ilike like = new Like();
        like.lamda();
        like = new Like2();
        like.lamda();

        //4. 局部内部类
        class Like3 implements Ilike{

            @Override
            public void lamda() {
                System.out.println("i like lamda3");
            }
        }
        like = new Like3();
        like.lamda();


        //5 匿名内部类
        like = new Ilike() {
            @Override
            public void lamda() {
                System.out.println("i like lamda5");
            }
        };
        like.lamda();
        //6,用lamda简化
        like = ()->{
            System.out.println("i like lamda6");};
        like.lamda();

    }
}
// 1.定义一个函数式接口
interface Ilike{
    void lamda();
}
// 2.实现类
class Like implements Ilike{

    @Override
    public void lamda() {
        System.out.println("i like lamda");
    }
}
  • 完整的一个lamda表达式
		Ilike like1;
        like1 =()->{
            System.out.println("i like lamda6");};
        like1.lamda();
  • 带参数的lamda示例
package lamda;

public class test_lamda  {
    public static void main(String[] args) {
        Ilove ilove;
//        ilove = new Love();
//        ilove.love(2);
        ilove= (int a)->{
            System.out.println("i love you 2,"+a);
        };
        ilove.love(4);

    }
}
interface  Ilove{
    void  love(int aa);
}
//class Love implements Ilove{
//
//    @Override
//    public void love(int a) {
//        System.out.println("i love you "+a);
//    }
//}

线程状态

  • 创建状态:Thread t = new Thread()
  • 就绪状态: 调用start() 方法,进入就绪状态,不一定立即执行
  • 运行 :经cpu调度分配资源后
  • 阻塞 :调用sleep,wait,同步锁定,进入阻塞
  • 销毁 :中断,结束
  • java源码中,以枚举的方式列举了6种状态
     public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
        NEW,
    
        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
        RUNNABLE,
    
        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
        BLOCKED,
    
        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * <ul>
         *   <li>{@link Object#wait() Object.wait} with no timeout</li>
         *   <li>{@link #join() Thread.join} with no timeout</li>
         *   <li>{@link LockSupport#park() LockSupport.park}</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called {@code Object.wait()}
         * on an object is waiting for another thread to call
         * {@code Object.notify()} or {@code Object.notifyAll()} on
         * that object. A thread that has called {@code Thread.join()}
         * is waiting for a specified thread to terminate.
         */
        WAITING,
    
        /**
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         * <ul>
         *   <li>{@link #sleep Thread.sleep}</li>
         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
         *   <li>{@link #join(long) Thread.join} with timeout</li>
         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
         * </ul>
         */
        TIMED_WAITING,
    
        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
        TERMINATED;
    }
    
    下文这段为对原文的翻译,来自下方博客链接

https://blog.youkuaiyun.com/weixin_43740223/article/details/120338375?utm_medium=distribute.pc_feed_v2.none-task-blog-personrec_tag-13.pc_personrecdepth_1-utm_source=distribute.pc_feed_v2.none-task-blog-personrec_tag-13.pc_personrec

```java
public enum State {
    /**
     * 尚未启动的线程的线程状态。线程对象已经被创建了,但是还没有启动的状态
     */
    NEW,
    /**
     * 可运行线程的线程状态。表示线程可能正在运行或者在等待CPU资源。
     */
    RUNNABLE,
    /**
     * 等待java monitor对象锁资源而阻塞的线程状态。
     * 1.处于阻塞状态的线程正在等待监视器锁进入synchorinzed代码块/方法
     * 2.在调用Object.wait释放了monitor锁资源后重新进入synchorinzed代码块/方法。
     */
    BLOCKED,
    /**
     * 1.等待线程的线程状态。由于调用以下方法之一,线程处于等待状态:
	 * Object.wait没有超时
	 * Thread.join没有超时
	 * LockSupport.park
	 * 2.处于等待状态的线程正在等待另一个线程执行特定操作。例如,
     * 在对象上调用Object.wait()的线程正在等待另一个线程在该对象上调用Object.notify()或Object.notifyAll()。
     * 调用Thread.join() 的线程正在等待指定的线程终止。
     */
    WAITING,
    /**
     * 具有指定等待时间的等待线程的线程状态。 由于使用指定的正等待时间调用以下方法之一,线程处于定时等待状态:
	 * - Thread.sleep(millis)
	 * - Object.wait(timeout)
	 * - Thread.join(millis)
	 * - LockSupport.parkNanos(blocker, nanos)
	 * - LockSupport.parkUntil(blocker, deadline)
     */
    TIMED_WAITING,
    /**
     * 终止线程的线程状态。 线程已完成执行。
     */
    TERMINATED;
}

```
方法
  • setPriority(int newPriority)更改线程的优先级
  • static void sleep(long millis)指定毫秒数内休眠
  • join() 等待线程终止
  • yield 暂停正在执行,执行其他线程
  • interrupt() 中断线程,不用此方式
  • isAlive() 测试线程是否处于活动状态
  • 停止线程:不推荐系统的方法
    • 推荐自己停止下来
    • 建议使用个标志位,当flag=false,线程终止运行
    package lamda;
    
    public class test_stop implements Runnable {
        private boolean flag = true;
        @Override
        public void run() {
            //线程体使用标识
            while (flag){
                System.out.println("run...thread");
            }
            
        }
        // 对外提供方法改变标识
        public void stop(){
            flag = false;
        }
    }
    
    

线程休眠

  • sleep时间达到后进入就绪状态
  • 每个对象都有一个锁,sleep不会释放锁
  • 利用sleep一直获取系统当前时间
  • 可以模拟网络延时,倒计时等
  • 每一个对象都有一个锁,sleep不会释放锁
    package state;
    
    import javafx.scene.chart.PieChart;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    /**
    * 打印系统当前时间
    */
    public class TestSleep {
    
       public static void main(String[] args) throws InterruptedException {
           Date starttime = new Date(System.currentTimeMillis());
           while (true){
               System.out.println(new SimpleDateFormat("hh:mm:ss").format(starttime));
               Thread.sleep(1000);
               starttime = new Date(System.currentTimeMillis());
           }
       }
    }
    

线程礼让

  • 礼让线程,只是暂停正在执行的当前线程,但是不阻塞
  • 线程从运行状态到就绪状态
  • 让cpu重新调度,礼让不一定成功,只是自己放弃,出去重新竞争(还是由cpu自己选择)
  • 实例
package state;
// 测试礼让

/**
 * 礼让不一定成功,看cpu心情
 */
public class Testyield {
    public static void main(String[] args) {
        MyYield myYield = new MyYield();
        new Thread(myYield,"a").start();
        new Thread(myYield,"b").start();

    }

}
class MyYield  implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"线程开始执行");
        Thread.yield();
        System.out.println(Thread.currentThread().getName()+"线程停止执行");
    }
}

礼让成功结果:
在这里插入图片描述

  • 测了半天全都礼让成功了,不成功的状态应该是一个线程全部执行完 ,再执行另一个线程。

强制执行Join

  • join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
  • 可以想象成插队(强制执行,霸道儿)
	package state;
	
	/**
	 * 测试Joinf方法
	 *
	 */
	public class TestJoin implements Runnable {
	
	    @Override
	    public void run() {
	        for (int i = 0; i < 200; i++) {
	            System.out.println("线程vip来了"+i);
	        }
	    }
	
	    public static void main(String[] args) throws InterruptedException {
	        //启动我们的线程
	        TestJoin testJoin = new TestJoin();
	        Thread thread = new Thread(testJoin);
	        thread.start();
	        // 主线程
	        for (int i = 0; i < 500; i++) {
	            if (i == 20){
	                thread.join();
	            }
	            System.out.println("主线程来了"+i);
	        }
	    }
	}

线程状态观测

package state;

/**
 * 观察测试线程的状态
 *
 */
public class TestState {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("/");

        });

        // 观察状态
        Thread.State state = thread.getState();
        System.out.println(state);
        //更新启动后
        thread.start();
        state = thread.getState();
        System.out.println(state);
        while (state !=Thread.State.TERMINATED){//只要线程不中止,就一直输出线程的状态
            Thread.sleep(100);
            state = thread.getState();
            System.out.println(state);

        }
    }
}

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

Runnable 运行状态中
-【注】当线程死亡后,不可以再启动,会报错,线程不可以启动两次

为什么启动线程是调用start()方法,而不是调用run方法呢?

  • 这是因为在new了一个线程后,首先进入初始态,调用start方法来到就绪态,并不会立即执行,而会等待系统资源分配,分到时间片就可以运行了。
    start是一个native方法,将启动一个新线程,并执行run方法,而直接执行run,会当作main线程下的一个类内方法,不会在某个线程执行,所以不行

为什么只能调用一次start方法

  • 首先,当线程死亡后,再调用start方法会报IllegalThreadStateException,这是因为在start方法的源码中,首先会判断线程的状态,如果线程不是初始状态,就会抛出异常,所以,再次执行就会报错。

线程优先级

  • java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定哪个线程优先执行
  • 线程优先级用数字表示 范围(1—10)
  • 使用以下方式改变或获取优先级
    • getPriority
    • setPriority(int xxxx)
package state;

/**
 * 测试线程的优先级
 */
public class TestPriority  {
    public static void main(String[] args) {
        // 主线程默认优先级
        System.out.println(Thread.currentThread().getName()+"--->"+Thread.currentThread().getPriority());
         MyPriority myPriority = new MyPriority();
         Thread thread1 = new Thread(myPriority,"t1");
         Thread thread2 = new Thread(myPriority,"t2");
         Thread thread3 = new Thread(myPriority,"t3");
         Thread thread4 = new Thread(myPriority,"t4");
         Thread thread5 = new Thread(myPriority,"t5");

         // 先设置优先级,再启动
        thread1.start();
        
        thread2.setPriority(2);
        thread2.start();

        thread3.setPriority(4);
        thread3.start();

        thread4.setPriority(6);
        thread4.start();

        thread5.setPriority(10);
        thread5.start();

    }
}
class MyPriority implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"--->"+Thread.currentThread().getPriority());
    }
}

在这里插入图片描述

  • 【注】主线程默认优先级5,不能改
  • 【注】优先级 范围 1-10
  • 【注】优先级低只是意味着获得调度的概率低,并不是一定严格按照优先级大小进行调度,(还是全看cpu心情)

守护线程

  • 线程分为用户线程和守护线程
  • 虚拟机必须确保用户线程执行完毕(例如 main线程)
  • 虚拟机不用等待守护线程执行完毕(例如 gc线程)
  • 实例:当用户线程执行完毕后,虚拟机不需要等待守护线程执行完毕,下例,当用户线程执行完毕后,守护线程过一段时间就结束了。
package state;

/**
 * 测试守护线程
 *  案例 上帝守护你
 */
public class TestDaemon {

    public static void main(String[] args) {
        God god = new God();
        You you = new You();
        Thread t1 = new Thread(god);
        t1.setDaemon(true);// 默认是false 表示是用户线程,true是守护线程
        t1.start(); //  守护线程启动
        new Thread(you).start();

    }
}
// 上帝
class God implements Runnable{

    @Override
    public void run() {
        while (true){
            System.out.println("上帝保佑着你");
        }
    }
}

// 你
class  You implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 36500; i++) {
            System.out.println("你一生开心的活着");
        }
        System.out.println("good bye");
    }
}

线程同步机制

问题的发生
  • 多个线程操作同一个资源,就是并发问题,这个时候就需要我们线程同步,本质是一种等待机制,想访问此对象的多个线程进入这个对象的等待池,形成队列,等待前面线程使用完毕,下一个线程再使用。
  • 为了保证数据访问正确性,加入了锁机制,synchronized,线程获得对象的排他锁,独占资源,其他线程必须等待,使用后释放锁即可,但是会存在以下问题:
    • 一个线程持有锁会导致其他所有需要此锁的线程挂起
    • 在多线程竞争下,加锁,释放锁,会导致较多上下文切换和调度延时,引起性能问题
    • 若低优先级进程获得锁,而高优先级进程等待释放锁,会导致优先级倒置,引起性能问题
  • 三大线程不安全案例,syn文件夹下,此处就不贴了
    • 买票
    • 银行取钱
    • 不安全集合,Arraylist
    package syn;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.ThreadLocalRandom;
    
    /**
     * 线程不安全的集合
     */
    public class Unsafelist {
        public static void main(String[] args) throws InterruptedException {
            List<String> list = new ArrayList<>();
            for (int i = 0; i < 10000; i++) {
                new Thread(()->{list.add(Thread.currentThread().getName());}).start();
            }
            // T
            Thread.sleep(3000);
            System.out.println(list.size());
        }
    }
    
    
    因为list集合可能同时操作了一个位置,造成了覆盖,所以数据缺失。
解决方法(同步方法、同步块)
  • synchronized 关键字

    • synchronized控制对“对象的访问”,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到方法返回才释放该锁,后面被阻塞的线程才能获得这个锁,继续执行
    • 缺陷,将一个大的方法声明为sybchronized会影响效率
    • 方法里面修改的内容才需要锁,锁的太多,浪费资源
    • 同步块 :synchronized(obj){} 称之为同步监视器
    • synchronized:默认锁的是对象类本身(this)
    • 同步块可以锁任何对象
    • 锁的对象一定是变化的量,需要增删改查的量
  • 同步块

    • synchronized(obj){}
    • Obj 称之为同步监视器
      • Obj可以是任何对象,但是推荐使用任何资源作为同步监视器
      • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是对象本身,或者是class【反射中讲解】
    • 同步监视器的执行过程
      • 第一个线程访问,锁定同步监视器,执行其中代码
      • 第二个线程访问,发现同步监视器被锁定,无法访问
      • 第一个线程访问完毕,解锁同步监视器
      • 第二个线程访问完毕,发现同步监视器没有锁,然后锁定并访问

生产者消费者问题

  • 两者共享同一个资源,且两个线程需要相互通信(生产者有了通知消费者)
    • 以上分析,仅有synchronized 是不够的
    • synchronized 可阻止并发更新同一个共享资源,实现了同步
    • synchronized 但是不能进行线程之间的消息传递
      -解决方法1:管程法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值