java中的线程

Java中的线程

Java中,线程以对象的形式存在。

Thread表示线程类。

获取线程对象

  • 获取当前正在运行的线程对象

    //获取当前运行的线程对象
    Thread ct = Thread.currentThread();
  • 创建一个线程对象

构造方法

常用构造方法说明
Thread()创建一个默认的线程对象
Thread(String name)创建一个指定线程名的线程对象
Thread(Runnable target)将一个Runnable对象包装为线程对象
Thread(Runnable target,String name)将一个Runnable对象包装为线程对象同时命名

常用方法

常用方法作用
setName(String str)设置线程名称
setPriority(int i)设置线程优先级(1~10),数字越大优先级越高,线程越先执行完
setDaemon(boolean f)是否将该线程设置为守护线程
getId()获取线程ID
getName()获取线程名,主线程名默认main,自定义线程名默认Thread-N
getPriority()获取线程优先级
getState()获取线程状态
isDaemon()判断该线程是否属于守护线程
start()启动线程
run()线程启动后执行的方法
Thread.currentThread()获取当前运行的线程对象
Thread.sleep(long m)设置当前线程休眠m毫秒
Thread.yield()线程让步,让其他线程执行

实现多线程

方式一:继承Thread类

  • 1.让某个类成为Thread类的子类

  • 2.重写Thread类中的run方法,将要让该线程执行的内容写在该方法中

  • 3.创建Thread类的对象后,调用start()方法,启动线程

自定义线程Thread类的子类

package com.hqyj.ThreadTest;
/*
* 自定义线程,让其循环打印1~100
* 1.继承Thread类
* 2.重写Thread类中的run方法,将要让该线程执行的内容写在该方法中
* 3.创建Thread类的对象后,调用start()方法,启动线程
* */
public class MyThread extends Thread{
​
    //如果在创建线程对象时需要设置线程名,这里调用父类的构造方法
    public MyThread(String name){
        super(name);
    }
​
    /*
    * 希望多线程环境下执行的代码,写在run方法中
    * */
    @Override
    public void run() {
        //获取当前线程对象
        Thread mt = Thread.currentThread();
        for (int i = 0; i < 100; i++) {
            //让当前线程休眠,让其他线程有机会在当前线程休眠的时间执行
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(mt.getName()+"--"+i);
        }
    }
}
​

main类

package com.hqyj.ThreadTest;
​
public class MainThread {
​
    public static void main(String[] args) throws InterruptedException {
        //先创建自定义线程对象,让该线程启动
        Thread mt1 = new MyThread("线程A");
        Thread mt2 = new MyThread("线程B");
​
        //不能调用run(),必须要调用start()方法才能启动线程
        mt1.start();
        mt2.start();
​
    }
}

方式二:实现Runnable接口(建议使用)

由于Java中是单继承,所以如果某个类已经使用了extends关键字去继承了另一个类,这时就不能再使用extends继承Thread类实现多线程,就需要使用实现Runnable接口的方法实现多线程。

  • 1.自定义一个类,实现Runnable接口

  • 2.重写run方法,将要多线程执行的内容写在该方法中

  • 3.创建Thread线程对象,将自定义的Runnable接口实现类作为构造方法的参数

  • 4.调用线程对象的start()方法启动线程

自定义Runnable接口的实现类

package com.hqyj.RunnableTest;
​
/*
 * 如果一个类已经使用了extends,就不得不使用implements Runnable实现该接口
 * 再通过创建Thread时,将该类进行包装,实现多线程
 * */
public class MyRunnableThread implements Runnable {
​
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}

main类

package com.hqyj.RunnableTest;
​
public class Main {
    public static void main(String[] args) {
​
        //依然要创建Thread对象才能使用多线程
        // 这里用的是Thread(Runnable target)构造方法
        // 或Thread(Runnable target,String name)构造方法
        //将Runnable接口的实现类对象包装为一个线程对象
        MyRunnableThread myRunnableThread = new MyRunnableThread();
​
        Thread t1 = new Thread(myRunnableThread,"线程A");
        //调用start()方法时,如果被分配到CPU时间片,会自动执行run()方法
        t1.start();
        Thread t2 = new Thread(myRunnableThread,"线程B");
        t2.start();
​
    }
}

方式三:使用匿名内部类

如果不想创建一个Runnable接口的实现类,就可以使用匿名内部类充当Runnable接口的实现类

package com.hqyj.UnnameInnerClassThreadTest;
/*
* 使用匿名内部类
* 本质还是使用Thread(Runnable target)
* 并没有创建一个Runnable接口的实现类,而是直接用一个匿名内部类作为参数,包装成一个线程对象
* */
public class Test {
    public static void main(String[] args) {
        //newThread()小括号中的参数就是一个匿名内部类
        //创建线程对象,将匿名内部类包装成Thread对象后直接start()启动
        new Thread(new Runnable() {
            //多线程做的事情依然写在run方法中
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":" + i);
                }
            }
        }, "线程A").start();
​
​
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":" + i);
                }
            }
        }, "线程B").start();
    }
}

线程的生命周期

线程的初始化到终止的整个过程,称为线程的生命周期

新生状态

当线程对象被实例化后,就进入了新生状态。new Thread()

就绪状态

当某个线程对象调用了start()方法后,就进入了就绪状态。

在该状态下,线程对象不会做任何事情,只是在等待CPU调用。

运行状态

当某个线程对象得到运行的机会后,则进入运行状态,开始执行run()方法。

不会等待run()方法执行完毕,只要run()方法调用后,该线程就会再进入就绪状态。这时其他线程就有可能穿插其中。

阻塞状态

如果某个线程遇到了sleep()方法或wait()等方法时,就会进入阻塞状态。

sleep()方法会在一段时间后自动让线程重新就绪。

wait()方法只有在被调用notify()或notifyAll()方法唤醒后才能进入就绪状态。

终止状态

当某个线程的run()方法中所有内容都执行完,就会进入终止状态,意味着该线程的使命已经完成。

多线程访问同一资源

可能出现的问题

如多线程卖票,多线程取钱,类似的多个线程在操作同一个资源时,实际操作的结果和预想结果不符。

如票实际卖出的大于原本的,取出的钱大于本金等。

出现问题的原因

由于线程调用start()方法后,就进入就绪状态,如果获得了CPU的使用权,开始调用run()方法,调用run方法后,马上就会重新回到就绪状态,所以不会等待run()方法执行结束,在执行run()方法的时候,可能其他线程也可能获取CPU的使用权,从而开始执行run()方法。

当前所有线程是在异步执行。

如何解决

让线程同步执行(排队)即可。就能在某个线程执行run方法的时候,让其他线程等待run()方法执行完毕

synchronized关键字

这个关键字可以修饰方法或代码块。

修饰方法

写在方法的返回值前。这样该方法就称为同步方法,在执行的时候,其他线程要排队等待该方法执行完毕后才执行。

public synchronized void fun(){
    //要同步的代码
}

修饰代码块

写在{}前,这样这段{}中的内容称为同步代码块,在执行的时候,其他线程要排队等待该方法执行完毕后才执行。

synchronized(要同步的对象或this){
    //要同步的代码
}

原理

每个对象都默认有一把"锁",当某个线程运行到被synchronized修饰的方法时,该对象就会拥有这把锁,在拥有锁的过程中,其他线程不能同时访问该方法,只有等待其执行结束后,才会释放这把锁。

使用synchronized修饰后拥有的锁称为"悲观锁",在拥有的时候,不允许其他线程访问。

方法被synchronized修饰后,成为了同步方法,就会让原本的多线程成为了单线程(异步变为同步),虽然效率变低,但是数据安全是首位。

多线程卖票

package com.hqyj.SellTicket;
​
/*
 * 定义自动售票机,创建多个线程模拟卖10张票
 * */
public class TicketMachine implements Runnable {
    //假设多线程卖10张票
    private int ticket = 10;
​
    /*
     * 卖票的方法
     * */
    public synchronized void sell() {
        if (ticket > 0) {
            System.out.print(Thread.currentThread().getName() + "售出一张");
            ticket--;
            System.out.println(Thread.currentThread().getName() + "剩余" + ticket);
        } else {
            System.out.println(Thread.currentThread().getName() + "已售罄");
        }
    }
​
    @Override
    public void run() {
        while (ticket > 0) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            sell();
        }
    }
​
    public static void main(String[] args) {
        //创建一个售票机对象
        TicketMachine ticketMachine = new TicketMachine();
        //将其使用3个线程类进行包装,让其在多线程环境下卖票
        Thread t1 = new Thread(ticketMachine,"窗口A");
        Thread t2 = new Thread(ticketMachine,"窗口B");
        Thread t3 = new Thread(ticketMachine,"窗口C");
​
​
        //线程就绪
        t1.start();
        t2.start();
        t3.start();
​
        //如果没有将卖票的方法sell()使用synchronized修饰,
        //就有可能在线程A执行的途中,线程B也开始执行,
        //从而线程A中原本要打印3句话的,只打印了1句,线程B就抢进来执行
        //最终导致后续流程出错
​
        //所以对方法sell()使用synchronized修饰后,
        //在某个线程执行sell()方法时,其他线程就会排队等待其执行完毕
​
    }
}
​

多线程取钱

Account类

package com.hqyj.Withdrawing;
/*
* 定义一个多线程共同访问的一个类:账户类
* */
public class Account {
    private double balance;
​
    public Account(double balance) {
        this.balance = balance;
    }
​
    public double getBalance() {
        return balance;
    }
​
    public void setBalance(double balance) {
        this.balance = balance;
    }
}
​

ATM类

package com.hqyj.Withdrawing;
​
public class ATM implements Runnable {
    private Account account;
​
    public ATM(Account account) {
        this.account = account;
    }
​
    /*
     * 取钱
     * */
    public void getMoney(int money) {
        //同步代码块,在操作account对象时,其他线程不能也同时操作该对象
        synchronized (account) {
            double v = account.getBalance() - money;
            System.out.println("ATM取出" + money);
            account.setBalance(v);
            System.out.println("ATM显示剩余" + account.getBalance());
        }
    }
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        getMoney(5000);
    }
}
​

Bank类

package com.hqyj.Withdrawing;
​
public class Bank implements Runnable {
    private Account account;
​
    public Bank(Account account) {
        this.account = account;
    }
​
    /*
     * 取钱的方法,在操作account对象时,不允许别的线程访问该对象
     * */
    public void getMoney(int money) {
        synchronized (account) {
            double v = account.getBalance() - money;
            System.out.println("银行取出" + money);
            account.setBalance(v);
            System.out.println("银行显示剩余" + account.getBalance());
        }
​
    }
​
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        getMoney(5000);
    }
}
​

main类

package com.hqyj.Withdrawing;
​
public class Main {
    public static void main(String[] args) {
        //创建多线程要访问的同一对象
        Account account = new Account(10000);
        //创建两个线程
        Thread t1 = new Thread(new ATM(account));
        Thread t2 = new Thread(new Bank(account));
        //线程就绪
        t1.start();
        t2.start();
    }
}
​

多线程相关面试题

  • 实现多线程的方式?

    • 继承Thread类

    • 实现Runnable接口后,包装为Thread对象

    • 使用匿名内部类

  • 为什么说StringBuilder是非线程安全的

    package com.hqyj.ThreadSafeTest;
    ​
    public class Test {
    ​
        public static void main(String[] args) throws InterruptedException {
            // StringBuilder sb = new StringBuilder();
            StringBuffer sb = new StringBuffer();
            //模拟10个线程,每个线程都往可变字符串中添加内容
            //如果是StringBuilder,最终可能没有添加指定的次数
            //如果是StringBuffer,最终一定会添加指定的次数
            for (int i = 0; i < 10; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for (int j = 0; j < 100; j++) {
                            try {
                                Thread.sleep(1);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            sb.append("a");
                        }
                    }
                }).start();
            }
            Thread.sleep(5000);
    ​
            System.out.println(sb.length());
    ​
        }
    }
  • 什么叫死锁?怎么产生?如何解决?

    如有两个人吃西餐,必须要有刀和叉才能吃饭,只有一副刀叉。

    如果A拿到了刀,B拿到了叉,互相都在等待另一个工具,但都不释放自己的,

    这时就会造成死锁的局面,既不结束,也不继续。

模拟死锁出现的原因

定义两个线程类,线程A先获取资源A后,再获取资源B;线程B先获取资源B后,再获取资源A。

如果对A和B对使用了synchronized进行同步,就会在线程A获取资源A时候,线程B无法获取资源A,

相反线程B在获取资源B的时候,线程A无法获取资源B,所以两个线程都不会得到另一个资源。

package com.hqyj.DeadLock;
public class MyThread1 implements  Runnable{
    //定义两个成员变量:刀、叉
    private Object knife;
    private Object fork;
​
    public MyThread1(Object knife, Object fork) {
        this.knife = knife;
        this.fork = fork;
    }
​
    //当前线程执行run方法的顺序是:先获取knife对象,等待3s后获取fork对象
    //由于对knife使用了synchronized关键字,表示在使用该knife对象期间,其他线程无权使用
    //只有等待同步代码块执行结束后,其他线程才有机会获取knife对象
    @Override
    public void run() {
        synchronized (knife){
            System.out.println(Thread.currentThread().getName()+"获得了刀,3s后获得叉");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (fork){
                System.out.println(Thread.currentThread().getName()+"获得了叉,可以吃饭了");
            }
        }
    }
}
​
package com.hqyj.DeadLock;
​
public class MyThread2 implements  Runnable{
    //定义两个成员变量:刀、叉
    private Object knife;
    private Object fork;
​
    public MyThread2(Object knife, Object fork) {
        this.knife = knife;
        this.fork = fork;
    }
​
      //当前线程执行run方法的顺序是:先获取fork对象,等待3s后获取knife对象
    //由于对fork使用了synchronized关键字,表示在使用该fork对象期间,其他线程无权使用
    //只有等待同步代码块执行结束后,其他线程才有机会获取fork对象
    @Override
    public void run() {
        synchronized (fork){
            System.out.println(Thread.currentThread().getName()+"获得了叉,3s后获得叉");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (knife){
                System.out.println(Thread.currentThread().getName()+"获得了刀,可以吃饭了");
            }
        }
    }
}​
package com.hqyj.DeadLock;
​
public class Main {
    public static void main(String[] args) {
        Object knife = new Object();
        Object fork = new Object();
​
        Thread mt1 = new Thread(new MyThread1(knife, fork), "小明");
​
        Thread mt2 = new Thread(new MyThread2(knife, fork), "刘涵");
​
        mt1.start();
        mt2.start();
​
    }
}

死锁的解决方式一:

让线程A和线程B获取资源A和资源B的顺序保持一致。

如让两个线程都先获取fork,再获取knife

@Override
public void run() {
    synchronized (fork){
        System.out.println(Thread.currentThread().getName()+"获得了叉,3s后获得叉");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (knife){
            System.out.println(Thread.currentThread().getName()+"获得了刀,可以吃饭了");
        }
    }
}

死锁的解决方式二:

让线程A和线程B获取资源A和资源B之前,再获取第三个资源,并对其使用synchronized进行同步,

这样线程A在获取第三个资源后,将所有事情执行完后,线程B才能继续。

如在获取fork和knife之前,先获取paper对象

@Override
public void run() {
    //先获取paper,在拥有paper对象期间,其他线程排队等待
    synchronized (paper){
        System.out.println(Thread.currentThread().getName()+"获得了餐巾,准备下一步");
        synchronized (fork){
            System.out.println(Thread.currentThread().getName()+"获得了叉,3s后获得叉");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (knife){
                System.out.println(Thread.currentThread().getName()+"获得了刀,可以吃饭了");
            }
        }
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值