java-线程基础

本文详细介绍了Java中线程的三种创建方式:继承Thread类、实现Runnable接口和实现Callable接口。对比了run()与start()方法的区别,并探讨了后台线程的概念。接着讲解了线程调度、优先级、休眠、让步和插队的实现。此外,文章深入讨论了多线程同步的重要性,包括线程安全、同步代码块、同步方法、死锁问题和同步锁的使用。最后,举例说明了线程间的通信机制,解决供求关系不匹配的问题。
摘要由CSDN通过智能技术生成

1-线程创建的三种方法

1-1.继承Thread类(重点)

创建步骤:

  1. 自定义线程类继承Thread类
  2. 重写Run()方法,编写线程方法体
  3. 创建线程对象,调用start()方法
public class ThreadTest {
    //下方创建了4个线程,4个线程不会共享100张票,每张票都会被打印4次,请看Runnable解决
    public static void main(String[] args) {
        //3.创建线程对象,调用start方法,这里创建了4个线程
        new TicketWindow().start();
        new TicketWindow().start();
        new TicketWindow().start();
        new TicketWindow().start();
    }
}
//1.自定义线程类继承Thread类
class TicketWindow extends Thread{
    private int tickets=100;
    @Override
    //2.重写run方法,编写方法体
    public void run(){
        while (true){
            if (tickets>0){
                System.out.println(Thread.currentThread().getName()+"正在发售第"+tickets--+"张票");
            }
        }
    }
}

局限性:

  • 自定义类要继承Thread,Java只支持单继承,万一student extends person,那么student类就没办法继承Thread,使用不了这种方法了,悲
  • 而且下方的代码中100张票是不被共享的,下方创建了4个线程,4个线程不会共享100张票,每张票都会被打印4次

1-2.实现Runnable接口(重点)

Runable接口内部只有一个抽象的run()方法

创建步骤:

  1. 定义MyRunnable类实现Runable接口
  2. 实现Run()方法,编写线程执行体
  3. 创建线程对象,使用new Thread()构造方法传入自定义类,同时参数2可以给个别名
public class RunnableTest {
    //只创建了一个自定义类,然后创建了4个线程,此时4个线程此时会共享100张票
    public static void main(String[] args) {
        //3.创建线程对象,使用new Thread()构造方法传入自定义类,同时参数2可以给个别名  
        TicketWindow2 tw2=new TicketWindow2();
        new Thread(tw2,"窗口1").start();
        new Thread(tw2,"窗口2").start();
        new Thread(tw2,"窗口3").start();
        new Thread(tw2,"窗口4").start();
    }
}
//1.定义自定义类实现Runable接口
class TicketWindow2 implements Runnable{
    private int tickets=10000;
    @Override
    //2.实现Run()方法,编写线程执行体
    public void run() {
        while (true){
            if (tickets>0){
                System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets--+"张票");
            }
        }
    }
}

1-3.实现Callable接口(了解)

创建步骤:

  1. 自定义类实现Callable接口
  2. 重写Call()方法
  3. 创建目标对象
  4. 使用futureTask构造方法
  5. 再使用Thread()构造方法再创建线程
public class CallableTest {
    public static void main(String[] args) {
        TicketWindow3 tw3=new TicketWindow3();
        //futuretask创建多少就是多少个线程
        FutureTask<Object> ft1=new FutureTask<Object>(tw3);
        FutureTask<Object> ft2=new FutureTask<Object>(tw3);
        FutureTask<Object> ft3=new FutureTask<Object>(tw3);
        FutureTask<Object> ft4=new FutureTask<Object>(tw3);
        new Thread(ft1,"窗口1").start();
        new Thread(ft2,"窗口2").start();
        new Thread(ft3,"窗口3").start();
        new Thread(ft4,"窗口4").start();
        //System.out.println(ft1.get());
    }
}
class TicketWindow3 implements Callable{
    private int tickets=100;
    @Override
    public Object call() throws Exception {
        while (true){
            if (tickets>0){
                System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets--+"张票");
            }
        }
    }
}

2-Run方法和Strat方法的区别

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pDCDoeFe-1626702069138)(04-线程.assets/image-20210414201823237.png)]

3-后台线程

  1. 为什么使用

    • 上方的例子中,main方法的代码执行完了,方法结束了,线程也会自动结束

    • main线程结束了,但是Java程序(进程)没有结束

    • 对Java程序来说只要有一个前台线程在运行进程就不会结束,如果一个进程只有后台线程运行,这个进程就会结束

    • 前台线程后台线程相对概念新创建的线程就是前台线程,一个线程在start方法执行前,setDaemon就会变成后台线程

  2. 使用方法

    • 在start()方法之前,使用setDaemon()方法设置一个后台线程即可,setDaemon(true)表示设置为后台线程
public class Example06 {
    public static void main(String[] args) {
        System.out.println("main线程是后台线程吗?" + Thread.currentThread().isDaemon());
        DamonThread dt = new DamonThread();
        Thread td = new Thread(dt, "线程/后台线程");
        System.out.println("thread线程默认是后台线程吗?" + td.isDaemon());
//        将线程td线程对象设置为后台线程
        td.setDaemon(true);
        System.out.println("thread线程默认是后台线程吗?" + td.isDaemon());
        td.start();
//        模拟主线程main的执行任务
        for (int i = 0; i < 5; i++) {
            System.out.println(i);
        }
    }
}
class DamonThread implements Runnable {
    @Override
    public void run() {
        while (true) {
            System.out.println(Thread.currentThread().getName() + "---在运行");
        }
    }
}

4-线程的调度,优先级

  1. 定义:

    线程优先级表示获得CPU的执行几率大小

  2. 使用方法

    • 在线程start()方法开启之前使用setPriority()方法传入整数即可,整数取值为1-10,越大几率越大
public class Example07 {
    public static void main(String[] args) {
        Thread thread1=new Thread(()->{
            for (int i=0;i<10;i++){
                System.out.println(Thread.currentThread().getName()+"正在输出i:"+i);
        }
        });
        Thread thread2=new Thread(()->{
            for (int j=0;j<10;j++){
                System.out.println(Thread.currentThread().getName()+"正在输出j:"+j);
        }
        },"优先级更高的线程");
	    //设置优先级(也是指概率),通过setPriority()方法设置可以写入数字或者提供的常量
        thread1.setPriority(5);
	    //thread2获得运行的机会更大
        thread2.setPriority(10);
        thread1.start();
        thread2.start();
    }
}

5-线程休眠

  1. 定义

    人为控制线程,可以使当前在执行的线程暂停,进入休眠等待状态

  2. 使用方法:

    在start()方法之后,使用sleep()方法传入数字即可,单位为毫秒,如sleep(2000),为休眠2秒

public class Example08 {
    public static void main(String[] args) {
        Thread thread1=new Thread(()->{
            for (int i=0;i<10;i++){
                System.out.println(Thread.currentThread().getName()+"正在输出i:"+i);
                if (i==2){
                    try {
                        Thread.sleep(500);
                    }catch (Exception e){
                    }
                }
            }
        });
        Thread thread2=new Thread(()->{
            for (int j=0;j<10;j++){
                System.out.println(Thread.currentThread().getName()+"正在输出j:"+j);
            }
        },"优先级更高的线程");
        thread1.start();
        thread2.start();
    }
}

6-线程让步

  1. 定义

    与sleep相似,让当前的正在运行的线程暂停,区别在yield()方法不会阻塞线程,而是处于就绪状态(与其他线程回到起跑线),让系统重新调度一次,让步不一定成功,看CPU心情

  2. 使用方法

    yield()方法

public class Example09 {
    public static void main(String[] args) {
        Thread thread1=new YieldThread("thread1");
        Thread thread2=new YieldThread("thread2");
        thread1.start();
        thread2.start();
    }
}
class YieldThread extends Thread{
    public YieldThread(String name){
        super(name);
    }
    @Override
    public void run(){
        for (int i=0;i<5;i++){
            System.out.println(Thread.currentThread().getName()+"----"+i);
            if (i==2){
			//yield方法会让优先级高于自己的先运行
                System.out.print("线程让步:");
                Thread.yield();
            }
        }
    }
}
/*
结果为:
thread1----0
thread1----1
thread1----2
线程让步:thread2----0
thread2----1
thread2----2
线程让步:thread1----3
thread1----4
thread2----3
thread2----4
分析:
看到线程1在i==2的时候让步了,然后线程2跑了,然后线程2跑到i==2又让步
*/

7-线程插队

  1. 插入某线程,使其阻塞,然后插入的线程运行完毕后,被插入的才能运行

  2. 使用在main线程中使用thread1.join()方法的线程,插进当前main线程中,thread1完全执行完毕后main才继续执行

  3. 使用方法

    在start之后,主要使用join()方法

public class Example10 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread1=new Thread(new EmergencyThread(),"thread1");
        thread1.start();
        for (int i=1;i<6;i++){
            System.out.println(Thread.currentThread().getName()+"输入:"+i);
            if (i==2){
                //在main线程中调用thread1线程,使得thread1插队运行,main要等到thread1运行完再运行
                thread1.join();
            }
        }
//        thread1.start();
    }
}
class EmergencyThread implements Runnable{
    @Override
    public void run(){
        for (int i=1;i<6;i++){
            System.out.println(Thread.currentThread().getName()+"输入:"+i);
        }
    }
}

8-多线程同步

限制某个资源(如:100张票)在同一时刻只能被一个线程访问

8.1-线程安全

问题引出:

结果会出现负数,主要是因为sleep,让其他的线程通过判断条件,进入了run方法内,这样的线程是不安全的,我们得想些方法去处理共享资源的代码同时只能有一个线程使用

public class Example11 {
    public static void main(String[] args) {
        TicketWindow ticketWindow = new TicketWindow();
        Thread thread1 = new Thread(ticketWindow, "thread1");
        Thread thread2 = new Thread(ticketWindow, "thread2");
        Thread thread3 = new Thread(ticketWindow, "thread3");
        Thread thread4 = new Thread(ticketWindow, "thread4");
        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
    }
}

class TicketWindow implements Runnable {
    private int tickets = 100;

    @Override
    public void run() {
        while (true) {
            if (tickets > 0) {
                try {
                    Thread.sleep(100);
                }catch (Exception e){

                }
                System.out.println(Thread.currentThread().getName() + "出售第" + tickets-- + "张票");
            }
        }
    }
}
/*结果:出现负数,主要是因为sleep,让其他的线程通过判断条件,进入了run方法内
thread2出售第2张票
thread1出售第2张票
thread3出售第1张票
thread4出售第1张票
thread2出售第0张票
thread1出售第-1张票
*/

8.2-同步代码块

  1. 作用:

    使得共享资源的代码在任何时候只能有一个线程访问

  2. 使用方法:

    1. 随便定义一个对象,作为同步代码块的锁
    2. 将锁对象参数给同步代码块,且将关键代码放入同步代码块中
    //定义任意一个对象,用作同步代码块的锁
    Object lock=new Object();
    //lock参数是一个锁对象,默认锁对象标志位是1,此时线程可以进入运行,标志位变化为0,其他线程无法进入。待之前的线程出来后,标志位变为1,其他线程才可以进入哦
    synchronized (lock){
    	//关键代码存放位置
    }
    

    示例代码如下:

public class Example12 {
    public static void main(String[] args) {
        SaleThread2 saleThread2=new SaleThread2();
        new Thread(saleThread2,"thread1").start();
        new Thread(saleThread2,"thread2").start();
        new Thread(saleThread2,"thread3").start();
        new Thread(saleThread2,"thread4").start();
    }
}
class SaleThread2 implements Runnable{
    private int tickets=100;
    //1.定义任意一个对象,用作同步代码块的锁
    Object lock=new Object();
    @Override
    public void run() {
        while (true){
            //2.锁对象放入,代码放入,nice!
            synchronized (lock){
                if (tickets>0){
                    try{
                        Thread.sleep(100);
                    }catch (Exception e){

                    }
                    System.out.println(Thread.currentThread().getName()+"正在发售第"+tickets--+"张票");
                }
            }
        }
    }
}

8.3-同步方法

  1. 定义:

    synchronized 返回值类型 方法名(参数 1,…)

    主要是使用synchronized修饰的方法,synchronized就是同步代码块使用的那个修饰符啦

  2. 作用:

    使得共享资源的代码在任何时候只能有一个线程访问

    synchronized修饰的方法,同时只允许一个线程访问,其他线程来的都会阻塞

  3. 使用方法:

    重写的run()方法里边的,有线程安全需求的方法,使用synchronized修饰就可以了,简单得一批

  4. 扩展思考:

    同步方法也有锁,它的锁就是当前调用该方法的对象,也就是this指向的对象,确保了唯一性

    静态方法的锁是该方法所在类的class对象,该对象可以使用"类名.class"取出

public class Example13 {
    public static void main(String[] args) {
        SaleThread3 saleThread3 = new SaleThread3();
        new Thread(saleThread3, "thread1").start();
        new Thread(saleThread3, "thread2").start();
        new Thread(saleThread3, "thread3").start();
        new Thread(saleThread3, "thread4").start();

    }
}

class SaleThread3 implements Runnable {
    private int tickets = 100;
    @Override
    public void run() {
        while (true) {
            sale();
        }
    }
    //这里就是核心了synchronized修饰的方法
    private synchronized void sale() {
        if (tickets > 0) {
            try {
                Thread.sleep(100);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "出售的票为第" + tickets-- + "张");
        }
    }
}

8.4-死锁问题

线程需要对方的锁,但是都拿不到,死锁就形成了

public class Example16 {
    public static void main(String[] args) {
        //线程1,flag为true,运行if中的内容
        DeadLockThread thread1 = new DeadLockThread(true);
        //线程2,flag为false,运行else中的内容
        DeadLockThread thread2 = new DeadLockThread(false);
        new Thread(thread1, "Chinese").start();
        new Thread(thread2, "American").start();
    }
}
class DeadLockThread implements Runnable {
    //定义两个类作为锁
    static Object chopsticks = new Object();
    static Object knifeAndFork = new Object();
    private boolean flag;
    //有参构造方法,给flag赋值
    DeadLockThread(boolean flag) {
        this.flag = flag;
    }
    @Override
    public void run() {
        if (flag) {
            while (true) {
                //线程1,不断循环,一直拿两个锁对象,运行
                synchronized (chopsticks) {
                    System.out.println(Thread.currentThread().getName() + "---if---chopsticks");
                    synchronized (knifeAndFork) {
                        System.out.println(Thread.currentThread().getName() + "---if---knifeAndFork");
                    }
                }
            }
        } else {
            while (true) {
                //线程2,受到影响,被线程1拿了锁对象,然后两个线程GG,死锁了
                synchronized (knifeAndFork) {
                    System.out.println(Thread.currentThread().getName() + "---else---knifeAndFork");
                    synchronized (chopsticks) {
                        System.out.println(Thread.currentThread().getName() + "---else---chopsticks");
                    }
                }
            }
        }
    }
}

8.5-同步锁

使用方法:

//1.定义一个Lock锁对象
private final Lock lock = new ReentrantLock();
//2.显示的加锁
lock.lock();
//这里就放你要锁住的代码部分
//3.解锁
lock.unlock();
//注意:一般配合try与catch,finally使用

示例:

public class Example14 {
    public static void main(String[] args) {
        LockThread lockThread=new LockThread();
        new Thread(lockThread,"thread1").start();
        new Thread(lockThread,"thread2").start();
        new Thread(lockThread,"thread3").start();
        new Thread(lockThread,"thread4").start();
    }
}
class LockThread implements Runnable {
    private int tickets = 100;
    //定义一个Lock锁对象
    private final Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            //对代码块进行加锁
            lock.lock();
            if (tickets > 0) {
                try {
                    Thread.sleep(100);
                    System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets--+"张票");
                } catch (Exception e) {
                    e.printStackTrace();
                }finally {
                    //执行完代码块后释放锁
                    lock.unlock();
                }
            }
        }

    }
}

9-多线程通信

线程与线程之间的通信问题,比如生产者和消费者相互通信

以下方法一般用作线程通信,且是"锁对象.方法":

  • wait()方法:使当前线程放弃同步锁并进入等待,直到其他线程进入此同步锁,并调用notify()方法,或者notifyAll()方法唤醒线程为止。wait()会释放锁,sleep()不会释放锁
    • 传入参数,可以指定等待秒数
    • 不传参数,线程一直等待,直到其他线程通知
  • notify()方法:唤醒此同步锁上等待的第一个调用wait()方法的线程
  • notifyAll()方法:唤醒此同步锁上调用的wait()方法的所有线程

问题引入,未使用线程通信:

/**
 * 问题就是供求关系不匹配,生产和消费对不上,这时候就需要用线程通信
 */
public class Problem {
    public static void main(String[] args) {
        //定义商品
        List<Object> goods = new ArrayList<>();
        long start = System.currentTimeMillis();
        //定义线程1,来生产商品
        Thread thread1 = new Thread(() -> {
            int num = 0;
            while (System.currentTimeMillis()-start<=100){
                goods.add("商品" + ++num);
                System.out.println("生产商品"+num);
            }
        },"生产者");
         //定义线程2,来消费商品
        Thread thread2 = new Thread(() -> {
            int num = 0;
            while (System.currentTimeMillis()-start<=100){
                goods.remove("商品"+ ++num);
                System.out.println("消费商品"+num);
            }
        },"生产者");
//        定义了两个线程,分别是消费者和生产者,同时运行100毫秒,看看供求关系
        thread1.start();
        thread2.start();
    }
}
//结果:生产和消费不能够统一,有时候才生产1000个东西,消费都达到1200了,显然离谱,虚空来的200个

现在使用线程的通信,解决问题:

public class Solve {
    public static void main(String[] args) {
        List<Object> goods = new ArrayList<>();
        long start = System.currentTimeMillis();
        //线程1,来生产商品
        Thread thread1 = new Thread(() -> {
            int num = 0;
            while (System.currentTimeMillis() - start <= 100) {
                //使用goods作为锁对象
                synchronized (goods) {
                    //有商品就让生产者进入等待状态
                    if (goods.size() > 0) {
                        try {
                            goods.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else {
                        goods.add("商品" + ++num);
                        System.out.println("生产商品" + num);
                    }
                }
            }

        }, "生产者");
        Thread thread2 = new Thread(() -> {
            int num = 0;
            while (System.currentTimeMillis() - start <= 100) {
                //使用goods作为锁对象
                synchronized (goods) {
//                    商品不足就唤醒生产者生产
                    if (goods.size() <= 0) {
                        goods.notify();
                    } else {
                        goods.remove("商品" + ++num);
                        System.out.println("消费商品" + num);
                    }
                }

            }
        }, "生产者");
//        定义了两个线程,分别是消费者和生产者,同时运行100毫秒,看看供求关系
        thread1.start();
        thread2.start();

    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值