个人java学习路线-多线程

什么是多线程

简单的说单线程就是一条路走到死,多线程就是多条路一起走,多线程是实现并发机制的一种有效手段。 值得一提的是在多核计算机出现以前,多线程并发是一种伪并发,是多个线程频繁抢夺cpu时间片造成假象。
再简单说说java中的线程, java程序运行时有几个进程呢?第一时间想到的肯定是main主线程以及GC回收线程。
其他的感兴趣自己百度了解了解。
我们学习需要知道多线程在java种有两个实现方法,一是继承Thread类,二是实现Runnable接口(以及JDK1.5后加入的Callable接口)。
然后掌握下面这张关于线程的生命周期图就行(来自百度,线程学习图都一样)
在这里插入图片描述

一.先来看看线程实现

1.继承Thread

结合代码解释:

public class ExtendsTest {
    public static void main(String[] args) {
        MyThread myThread=new MyThread();
        myThread.start();//线程开始运行,固定格式
        for (int i = 0; i <1000 ; i++) {//main方法中的进程
            System.out.println("主线程----->"+i);
        }
    }
}
class MyThread extends Thread{
    @Override
    public void run() {//run()方法必须复写,线程主体必须写在run()方法中
        for (int i = 0; i <1000 ; i++) {
            System.out.println("分支线程----->"+i);
        }
     }
}

看看运行结果:
在这里插入图片描述
可见,主线程和分支线程一直在重复抢夺CPU时间片,哪个抢到是随机的。

2.实现Runnable接口

public class RunnableTest {
    public static void main(String[] args) {
        Thread thread=new Thread(new MyRunnable());
        thread.start();
        for (int i = 0; i <1000 ; i++) {
            System.out.println("主线程----->"+i);
        }
    }
}
class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i <1000 ; i++) {
            System.out.println("分支线程----->"+i);
        }
    }
}

运行结果都差不多。

3.为什么有两种方法

这个当然是因为两个方法有不同的作用。
如果仔细看上面代码就会发现,继承Thread类的方法,每次创建新进程都有一个MyThread类被创建,而实现Runnable接口的方法,每次创建进程,MyRunnable是被共用的。即Runnable接口可以资源共享。(Callable最后再说)

二.线程操作相关方法

1.设置线程名称

public class Test {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName());//获取当前线程名称,这里就是main线程
        MyThread myThread=new MyThread();
        myThread.setName("一号线程");//myThread线程设置名称为一号线程
        System.out.println(myThread.getName());//输出myThread线程名称
        myThread.start();
    }
}
class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i <100 ; i++) {
            System.out.println("mt线程---->"+i);
        }
    }
}

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

2.线程睡眠

public class Test2 {
    public static void main(String[] args) {
        for (int i = 0; i <10 ; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i);
            try {
                Thread.sleep(1000);//当前线程睡眠1s=1000毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果就是1秒打印一次。

3.线程睡眠的唤醒

用interrupt()方法来唤醒

public class Test3 {
    public static void main(String[] args) {
        SleepThreadTest stt=new SleepThreadTest();
        stt.setName("stt");
        stt.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        stt.interrupt();//唤醒stt线程
    }
}
class SleepThreadTest extends Thread{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"-->"+"start");
        try {
            Thread.sleep(1000*60*60);//睡1小时
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"end");
    }
}

运行结果:

在这里插入图片描述
红色说明,唤醒进程会产生异常。

4.如何停止线程

那么怎么停止进程呢?
自然有专用的方法来让线程死亡,但这并不安全,那么还有什么方法能怎么停止异常?用if加个判断就行

public class Test4 {
    public static void main(String[] args) {
        StopThread st=new StopThread();
        Thread t=new Thread(st);
        t.start();
        try {
            Thread.sleep(500*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        st.run=false;//停止线程
    }
}
class StopThread implements Runnable{
    boolean run=true;//控制线程的属性
    @Override
    public void run() {
        for (int i = 0; i <10 ; i++) {
            if(run){//默认true,运行线程
                System.out.println("mt线程---->"+i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else {
                return;
            }
        }
    }
}

5.线程优先级

优先级越高,抢占CPU时间片能力越强

public class Test5 {
    public static void main(String[] args) {
        System.out.println(Thread.MAX_PRIORITY);//输出线程优先度最高为10,除此之外还有默认的NORM_PRIORITY优先度为5,和MIN_PRIOROTY优先度为1.
        System.out.println(Thread.currentThread().getPriority());//获取当前线程优先级(这里就是main方法优先级),输出为5,即main方法优先级为5
    }
}

6.后台线程

线程是独立的,在java程序中,只要前台有一个线程在运行,则整个进程都不会消失,所以此时可以设置一个后台线程,这样即使Java进程结束了,此后台线程依然会继续执行。用setDaemon()方法即可设置后台线程。

public class ProtectThreadTest {
    public static void main(String[] args) {
        Thread t=new ProtectThread();
        t.setName("保护线程");
        t.setDaemon(true);//设置后台线程
        t.start();
        for (int i = 0; i <10 ; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class ProtectThread extends Thread{
    @Override
    public void run() {
        int i=0;
        while (true){
            System.out.println(Thread.currentThread().getName()+"-->"+(++i));
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行后,哪怕t线程是死循环也会结束程序。
在这里插入图片描述

7.设置线程"闹钟"

其实就是在固定的时间让线程启动。

public class TimerTest {
    public static void main(String[] args) {
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        Date firstTime=null;
        try {
            firstTime=sdf.parse("2021-7-24 15:41:30 000");//改成你想让线程启动的时间,运行程序,等时间到了自动开始。
        } catch (ParseException e) {
            e.printStackTrace();
        }
        Timer timer=new Timer();
        timer.schedule(new LogTimerTask(),firstTime,1000);
    }
}
class LogTimerTask extends TimerTask{
    public static int i=0;
    @Override
    public void run() {
        System.out.println(i++);
    }
}

三.来说说线程安全

因为多线程是抢夺CPU时间片执行的,这个抢的时候总有各种各样的意外。比如,线程1读取了数据并进行了修改,但在还没修改完时,线程2又读取了数据,这样就会产生安全问题。

1.先来看看一个例子

这是经典的张三取钱的例子:
张三存入10000元,通过线程取钱,一次取5000元。
账户类:

public class Account {
    private String actno;
    private double balance;
    public Account() {
    }
    public Account(String actno, double balance) {
        this.actno = actno;
        this.balance = balance;
    }
    public void withdraw(double money) throws AccountExcption {
        if (this.getBalance()>=money){
            this.setBalance(this.getBalance()-money);
            System.out.println("账户"+this.getActno()+"取款"+money+"成功,账户还剩"+this.getBalance()+"元");
        }else {
           throw new AccountExcption("账户余额不足");
        }
    }
    public String getActno() {
        return actno;
    }
    public void setActno(String actno) {
        this.actno = actno;
    }
    public double getBalance() {
        return balance;
    }
    public void setBalance(double balance) {
        this.balance = balance;
    }
    @Override
    public String toString() {
        return "Account{" +
                "actno='" + actno + '\'' +
                ", balance=" + balance +
                '}';
    }
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Account account = (Account) o;
        return Double.compare(account.balance, balance) == 0 &&
                Objects.equals(actno, account.actno);
    }
    @Override
    public int hashCode() {
        return Objects.hash(actno, balance);
    }
}

异常类:

public class AccountExcption extends Exception{
    public AccountExcption() {
    }
    public AccountExcption(String message) {
        super(message);
    }
}

线程类:

public class AccountThread extends Thread{
    private Account act;
    public AccountThread(){
    }
    public AccountThread(Account act){
        this.act=act;
    }
    @Override
    public void run() {
        double money=5000;
        System.out.println("线程"+Thread.currentThread().getName()+"  ");
        try {
            act.withdraw(money);
        } catch (AccountExcption accountExcption) {
            accountExcption.getMessage();
        }
    }
}

main:

public class Test {
    public static void main(String[] args) {
     Account act=new Account("张三",10000);
        AccountThread at1=new AccountThread(act);
        AccountThread at2=new AccountThread(act);
        at1.start();
        /*try {//这里要是去掉注释运行就正常了,但那就不是多线程并发了。
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }*/
        at2.start();
    }
}

先来看看运行结果:
在这里插入图片描述
明显有问题,Thread-0运行时,账户应该还剩5000的,然而显示还剩0,这就是线程的安全问题,若不解决这个问题,多拿少拿都有可能。

2.用同步代码块,解决问题:

线程同步也有两种方法
1.同步代码块格式:
synchronized(同步对象){
需同步的代码;
}
2.同步方法格式:
synchronized 方法返回值 方法名称(参数列表){
//方法体
}
****同步对象很宽泛,搞不清用this就行。
****线程同步会锁死一段代码,即,同一时间只允许一个线程访问修改这段代码。
****需注意的是,线程同步会让原本并发的操作变回单线程排队运行,运行效率会下降,所以同步的地方越少越好。
账户类:

public class Account {
    private String actno;
    private double balance;
    public Account() {
    }
    public Account(String actno, double balance) {
        this.actno = actno;
        this.balance = balance;
    }
    public void withdraw(double money) throws AccountExcption{
        synchronized (this){
            if (this.getBalance()>=money){
                this.setBalance(this.getBalance()-money);
                System.out.print("线程"+Thread.currentThread().getName()+"  ");
                System.out.println("账户"+this.getActno()+"取款"+money+"成功,账户还剩"+this.getBalance()+"元");
            }else {
                throw new AccountExcption("账户余额不足");
            }
        }
    }
    public String getActno() {
        return actno;
    }
    public void setActno(String actno) {
        this.actno = actno;
    }
    public double getBalance() {
        return balance;
    }
    public void setBalance(double balance) {
        this.balance = balance;
    }
    @Override
    public String toString() {
        return "Account{" +
                "actno='" + actno + '\'' +
                ", balance=" + balance +
                '}';
    }
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Account account = (Account) o;
        return Double.compare(account.balance, balance) == 0 &&
                Objects.equals(actno, account.actno);
    }
    @Override
    public int hashCode() {
        return Objects.hash(actno, balance);
    }
}

其他三个代码都差不多,不用该都行。
看看运行结果:
在这里插入图片描述
这样就能解决多线程产生的安全问题

3.用同步方法,解决问题:

public class Account {
    private String actno;
    private double balance;
    public Account() {
    }
    public Account(String actno, double balance) {
        this.actno = actno;
        this.balance = balance;
    }
    public synchronized void withdraw(double money) throws AccountExcption {
        if (this.getBalance()>=money){
            this.setBalance(this.getBalance()-money);
            System.out.print("线程"+Thread.currentThread().getName()+"  ");
            System.out.println("账户"+this.getActno()+"取款"+money+"成功,账户还剩"+this.getBalance()+"元");
        }else {
            throw new AccountExcption("账户余额不足");
        }
    }
    public String getActno() {
        return actno;
    }
    public void setActno(String actno) {
        this.actno = actno;
    }
    public double getBalance() {
        return balance;
    }
    public void setBalance(double balance) {
        this.balance = balance;
    }
    @Override
    public String toString() {
        return "Account{" +
                "actno='" + actno + '\'' +
                ", balance=" + balance +
                '}';
    }
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Account account = (Account) o;
        return Double.compare(account.balance, balance) == 0 &&
                Objects.equals(actno, account.actno);
    }
    @Override
    public int hashCode() {
        return Objects.hash(actno, balance);
    }
}

差不多,没啥好说的。

四.谈谈死锁

给一个经典例子:
小红说:你先给我苹果,我再给你橘子
小明说:你先给我橘子,我再给你苹果
然后,谁也不给谁僵持住了。
代码:

	public class Test {
    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);
        t1.start();
        t2.start();
    }
}
class MyThread1 extends Thread{
    Object o1;
    Object o2;
    public MyThread1(Object o1,Object o2){
        this.o1=o1;
        this.o2=o2;
    }
    @Override
    public void run() {
        synchronized (o1){
            try {
                Thread.sleep(100);
            } 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;
    }
    @Override
    public void run() {
        synchronized (o2){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o1){
            }
        }
    }
}

这个运行就会卡住,就是上面的例子,两个线程都在等对方运行。
所以设计程序时需要多用心,确保不会产生死锁。

五.案例之生产者和消费者

这个例子是为了用多线程答到生产者生成一个物品,消费者消费一个物品,不产生其他问题,主要运用的就是同步。
简单介绍程序:
生产者生产一个石头,线程睡眠,并唤醒消费者线程,
消费者消费一个石头,线程睡眠,并唤醒生产者线程。
代码:

public class Test {
    public static void main(String[] args) {
        List list=new ArrayList();
        Thread  t1=new Thread(new Producer(list));
        Thread  t2=new Thread(new Consumer(list));
        t1.setName("生产者:");
        t2.setName("消费者:");
        t1.start();
        t2.start();
    }
}
class Producer implements Runnable{
    private List list;
    public Producer() {}
    public Producer(List list) {
        this.list=list;
    }
    @Override
    public void run() {
        while(true){
            synchronized (list){
                if(list.size()>0){
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                list.add("石头");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"石头+1");
                list.notify();
            }
        }
    }
}
class  Consumer implements Runnable{
    private List list;
    public Consumer() {}
    public Consumer(List list) {
        this.list=list;
    }
    @Override
    public void run() {
        while (true){
            synchronized (list){
                if (list.size()==0){
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                list.remove("石头");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"石头-1");
                list.notify();
            }
        }
    }
}

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值