Java线程

概论

进程是一个程序
线程是一个进程中的执行场景/执行单元
一个进程可以启动多个线程
(详见操作系统)
对于Java来说,当在DOS命令窗口输入:
Java HelloWorld 回车之后,会先启动JVM。JVM就是一个进程
JVM在启动主线程,调用main方法。在启动一个垃圾回收线程负责看护。现在的Java程序最少两个线程

进程A和进程B内存独立不共享
同进程的线程之间堆内存和方法区内存共享,栈内存独立

单核CPU不能真正多线程,多核才有可能。但是单核可以做到“多线程并发的感觉”(通过时间片)

Java语言中,实现线程两种方式
1、编写一个类,继承java.lang.Thread,重写run方法
*2、编写一个类实现java.lang.Runnable接口(主要)

线程的实现方法

第一种方法:类继承java.lang.Thread,重写run方法

线程对象通过.start()方法启动线程

public class ThreadTest2 {
    public static void main(String[] args) {
        //创建:
        MyThread mt=new MyThread();

        //启动:
        //start()方法的作用是启动一个分支线程,在JVM中开辟新的栈空间,这段代码瞬间结束
        //这段代码任务是开辟一个新的栈空间,开辟之后start()方法就结束了,线程启动成功
        //启动成功的线程会自动调用run()方法,并且run()在分支栈最底部
        mt.start();
        //mt.start();先执行完了之后,后面的程序才能执行。mt.start()瞬间执行
        //此后主线程和分支线程同时运行
        
        //这里的代码还是运行在主线程中
        for(int i=0;i<1000;i++){
            System.out.println("主线程--->"+i);
        }
    }
}
class MyThread extends Thread{
    @Override
    public void run() {
        super.run();
        //编写程序,运行在分支线程中
        for(int i=0;i<1000;i++){
            System.out.println("分支线程--->"+i);
        }
    }
}

第二种方法:类实现java.lang.Runnable接口(推荐)

也需重写run()方法

public class ThreadTest3 {
    public static void main(String[] args) {
        //创建一个可运行的对象
        MyRunnable r=new MyRunnable();
        //将可运行的对象封装成一个线程对象
        Thread t=new Thread(r);

        Thread tt=new Thread(new MyRunnable());//合并

        //启动线程
        t.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);
        }
    }
}

线程的生命周期

新建态–>就绪态<–>运行态–>死亡态
^ v
阻塞态

线程的常用方法

1.getName(),.setName(“xxx”)

/*
获取当前线程对象:(返回Thread) static Thread currentThread();静态方法
获取线程对象名字:.getName()
修改当前线程对象名字:.setName("xxx")
默认名字Thread-0、Thread-1......
 */
public class THreadTest4 {
    public static void main(String[] args) {
        //ct 当前线程对象,代码出现在main中,当前线程就是主线程
        Thread ct=Thread.currentThread();
        MyThread2 mt=new MyThread2();
        //设置名字,默认名字Thread-0
        mt.setName("mt");
        //获取名字
        String mtName=mt.getName();
        System.out.println(mtName);

        //获取另一线程的名字
        //System.out.println(new MyThread2().getName());合并
        MyThread2 mt2=new MyThread2();
        mt2.setName("mt2");
        mt2.start();

        mt.start();
    }
}
class MyThread2 extends Thread{
    public void  run(){
        for(int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+"分支线程-->"+i);
        }
    }
}

线程睡眠和唤醒

睡眠static void sleep(long millis)静态方法
package com.xianchen.java.thread;
/*
static void sleep(long millis)方法:静态,参数是毫秒,作用是让当前线程进入阻塞状态,放弃cpu时间片,让给其他线程
出现在哪个线程,哪个线程就休眠
可以做到间隔特定的时间去执行特定的代码
 */
public class ThreadTest5 {
    public static void main(String[] args) {
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("hello wordl");
    }
}
唤醒.interrupt() 实例方法

不是终断线程执行,是终止线程的睡眠。.interrupt()干扰 通过异常机制实现。

public class ThreadTest6 {
    public static void main(String[] args) {
        Thread t=new Thread(new MyRunnable2());
        t.setName("t");
        t.start();

        //5秒后t线程醒来(5秒后主线程任务完成)
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //终断t线程睡眠
        t.interrupt();//干扰会让Thread.sleep(1000860*60*24*365);异常,
                        //这种终断方法依靠异常,结束try也就结束睡眠
    }
}
class MyRunnable2 implements Runnable{
    @Override
    public void run() {
        //**run()只能try/catch,不能抛出。因为子类重写父类方法不能抛出更宽泛的异常。run()在父类中没有抛出异常
        //其他方法可以throws,不影响。
        System.out.println(Thread.currentThread().getName()+"--->begin");
        try {
            //睡一年
            Thread.sleep(1000860*60*24*365);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"--->end");
    }
}

强行终止线程执行

.stop()方法

线程 .stop()存在缺点:容易损坏、丢失数据,这种方式直接杀死线程,线程没有保存的数据会丢失

public class ThreadTest7 {
    public static void main(String[] args) {
        Thread t=new Thread(new MyRunnable3());
        t.setName("t");
        t.start();

        //模拟睡眠5秒
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //5秒后强行终止t线程
        t.stop();//已过时,不建议使用
    }
}

class MyRunnable3 implements Runnable{
    @Override
    public void run() {
        for(int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"---->"+i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                //e.printStackTrace();
            }
        }
    }
}
改进方法 通过打标记的方法
public class ThreadTest8 {
    public static void main(String[] args) {
        MyRunnable4 r=new MyRunnable4();
        Thread t=new Thread(r);
        t.setName("t");
        t.start();

        //模拟睡眠5秒
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //5秒后强行终止t线程
        r.run=false;//在想终止的时候改false
    }
}

class MyRunnable4 implements Runnable{
    //打一个boolean标记
    boolean run =true;

    @Override
    public void run() {
        for(int i=0;i<10;i++){
            if (run) {
                System.out.println(Thread.currentThread().getName() + "---->" + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    //e.printStackTrace();
                }
            }else{
                //终止当前线程
                return;//return之前可以保存数据
            }
        }
    }
}

线程调度

1、线程调度模型
抢占调度:优先级高,抢到时间片多。Java采用
均分调度:平均分配时间片

2、Java线程调度方法
实例方法
int getPriority()//获取线程优先级,最低优先级是1.默认优先级是5,最高是10
void setPriority(int)//修改线程优先级
void join()//合并线程
例class MyThread1 extends Thread{
public void doSome(){
MyThread2 t=new NyThread2();
t.jion();//当前线程阻塞,t线程执行,直到t线程结束,当前线程继续
}
}
class MyThread2 extends Thread{
}

静态方法
static void yield()当前线程让位,yield()方法不是阻塞 方法,是让当前线程从运行态到就绪态

多线程

存在安全问题的条件:多线程并发、有共享数据、共享数据有修改行为。
解决线程安全问题:线程排队执行,不能并发。——线程同步机制。 注:会牺牲部分效率

同步:线程t1执行的时候必须等线程t2结束。线程之间有等待关系。也就是线程排队。
异步:线程之间各自执行自己的,谁也不需要管谁。也就是多线程并发。

无线程同步机制

例如:
账户类:

import java.util.Objects;

public class Account {
    private String actno;
    private double balance;

    public Account() {
    }

    public Account(String actno, double balance) {
        this.actno = actno;
        this.balance = balance;
    }

    public String getActno() {
        return actno;
    }

    public double getBalance() {
        return balance;
    }

    public void setActno(String actno) {
        this.actno = actno;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Account)) return false;
        Account account = (Account) o;
        return Double.compare(account.getBalance(), getBalance()) == 0 &&
                Objects.equals(getActno(), account.getActno());
    }

    @Override
    public int hashCode() {
        return Objects.hash(getActno(), getBalance());
    }

    @Override
    public String toString() {
        return "Account{" +
                "actno='" + actno + '\'' +
                ", balance=" + balance +
                '}';
    }

    //取款方法
    public  void  withdraw(double money){
        double before =this.balance;
        double after=before-money;

        //模拟网络延时
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            //e.printStackTrace();
        }
        setBalance(after);
    }
}

线程:

public class AccountThread extends Thread{
    //两个线程共享一个账户
    private Account act;
    //通过构造方法传入

    public AccountThread(Account act) {
        this.act = act;
    }

    @Override
    public void run() {
        super.run();
        //取款操作
        double money=5000;
        act.withdraw(5000);

        System.out.println(Thread.currentThread().getName()+"取款成功,余额:"+act.getBalance());
    }
}

主程序:

public class Test {
    public static void main(String[] args) {
        //创建账户对象
        Account act=new Account("zhangsan",10000);
        Thread t1=new AccountThread(act);
        Thread t2=new AccountThread(act);

        t1.setName("t1");
        t2.setName("t2");
        
        t1.start();
        t2.start();
    }
}

会出现
t1取款成功,余额:5000.0
t2取款成功,余额:5000.0

总结:不使用 线程同步机制,多线程对同一账户进行取款,出现线程安全问题

改进 使用线程同步机制 synchronized

线程同步机制语法:
语法:
synchronized(共享对象) {
//线程同步代码块
}
synchronized()括号中的数据是多线程共享的数据。才能达到多线程排队
()中写什么,要看想让那些线程同步。
假设t1 t2 t3 t4 t5,只希望t1 t2 t3排队,t4 t5不需要排队。
一定要在synchronized()括号中写t1 t2 t3共享的对象,这个对象对于t4 t5不共享

上述代码只需改写 Account类,就能完成线程同步

import java.util.Objects;
public class Account {
    private String actno;
    private double balance;

    public Account() {
    }

    public Account(String actno, double balance) {
        this.actno = actno;
        this.balance = balance;
    }

    public String getActno() {
        return actno;
    }

    public double getBalance() {
        return balance;
    }

    public void setActno(String actno) {
        this.actno = actno;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Account)) return false;
        Account account = (Account) o;
        return Double.compare(account.getBalance(), getBalance()) == 0 &&
                Objects.equals(getActno(), account.getActno());
    }

    @Override
    public int hashCode() {
        return Objects.hash(getActno(), getBalance());
    }

    @Override
    public String toString() {
        return "Account{" +
                "actno='" + actno + '\'' +
                ", balance=" + balance +
                '}';
    }

    //取款方法
    public  void  withdraw(double money){
        //以下代码必须线程排队,不能并发
        /*
        线程同步机制
        语法:
            synchronized(共享对象) {
                //线程同步代码块
            }
            synchronized()括号中的数据是多线程共享的数据。才能达到多线程排队
            ()中写什么,要看想让那些线程同步。
                假设t1 t2 t3 t4 t5,只希望t1 t2 t3排队,t4 t5不需要排队。
                一定要在synchronized()括号中写t1 t2 t3共享的对象,这个对象对于t4 t5不共享

         */

        //这里的共享对象是账户对象本身
        synchronized(this) {
            double before = this.balance;
            double after = before - money;

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                //e.printStackTrace();
            }
            setBalance(after);
        }
    }
}

输出
t1取款成功,余额:5000.0
t2取款成功,余额:0.0

总结:在Java语言中,任何对象都有一把锁,也就是一个标记。
上述代码原理:假设t1和t2并发,开始执行代码的时候,肯定有一个先一个后。
假设t1先执行,遇到了synchronized,这是会自动找后面共享对象的对象锁,找到后占有这把锁,然后执行同步代码块中的程序,在程序执行过程中一直占有这把锁,直到代码结束,释放这把锁。
假设t1已经占有了这把锁,此时t2也遇到了synchronized关键字,也会去占有后面共享对象的对象锁。但是这把锁被t1占有了。t2只能在同步代码块外等待t1完成之后,才能进入同步代码块
从此达到线程同步。

注:
java三大变量中,局部变量在栈中,是线程独享不会共享,也就不会有安全问题。
静态变量(方法区)和对象(堆)都不是线程安全的。

synchronized( ){
同步代码块
}
同步代码块越小,效率越高

synchronized三种用法:
第一种
synchronized(共享数据){
同步代码块
}

第二种
修饰实例方法

第三种
修饰静态方法

synchronized 关键字其他用法

在实例方法上使用 synchronized 关键字

缺点:1、锁的是this,且不能修改
2、整个方法题都会同步,可能会扩大同步范围,导致效率降低
优点:1、代码写得少,简洁了

public  synchronized void  withdraw(double money){
       
            double before = this.balance;
            double after = before - money;

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                //e.printStackTrace();
            }
            setBalance(after);
        
    }
死锁
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);
        
        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() {
        super.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;
    }

    @Override
    public void run() {
        super.run();
        synchronized (o2){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o1){
                
            };
        }
    }
}

总结:
synchronized(共享数据){
同步代码块
}
最好不要嵌套使用

守护线程

Java中线程分两类:用户线程和守护线程(后台)
守护线程特点是:一般是死循环。所用用户线程结束,守护线程自动结束
main线程是用户线程
例如
每天0点自动备份数据

public class ThreadTest9 {
    public static void main(String[] args) {
        Thread t=new BakDataThread();
        t.setName("备份数据线程");
        //**启动线程之前,将线程设置为守护线程
        t.setDaemon(true);
        t.start();

        //主线程
        for(int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"--->"+i);
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class BakDataThread extends Thread{
    @Override
    public void run() {
        super.run();
        int i=0;
        while(true){
            System.out.println(Thread.currentThread().getName()+"--->"+i++);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

定时器

作用:间隔特定的时间,执行特定程序。
例如:每天数据备份操作
实现:可以使用sleep实现
java.tutil.Timer
框架中实现,例如Spring的SpringTask框架

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class TimerTest {
    public static void main(String[] args) throws ParseException {
        //创建定时器对象
        Timer t1=new Timer();
        //守护线程的方式
        Timer t2=new Timer(true);

        //指定定时任务t1.schedule(定时任务,第一次时间,间隔多久一次);
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        Date firstTime=sdf.parse("2020-12-14 23:37:00 000");
        t1.schedule(new LogTimerTask(),firstTime,1000*10);

    }
}

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

wait和notify方法

wait和notify是不线程专有,而是每一个Java对象都有的方法。是Object类自带的

wait():
Object o=new Object();
o.wait();
让正在o对象上活动的线程进入等待状态,直到被唤醒。

notify():
Object o=new Object();
o.notify();
让正在o对象上等待的线程进入就绪状态。

notifyAll():
唤醒所有o对象上处于等待的线程

例如:
生产者和消费者模式
一个线程负责生产,一个线程负责消费,中间是仓库。
仓库是多线程共享的。最终保持生产者和消费者平衡问题
wait方法和notify方法建立在synchronized线程同步上面的
o.wait()会让当前正在o对象上活动的当前线程进入等待状态,并且释放o对象锁
o.notify()只会通知,不会释放之前占有的o对象锁

import java.util.ArrayList;
import java.util.List;

/*
wait&notify实现生产者和消费者模式
生产线程负责生成,消费线程负责消费
生成线程和消费线程需要均衡
wait和notify是普通Java对象都有的方法
wait和notify方法建立在线程同步的基础上
因为多线程异步会有线程安全问题

o.wait()会让当前正在o对象上活动的当前线程进入等待状态,并且释放o对象锁
o.notify()只会通知,不会释放之前占有的o对象锁

仓库采用List集合,假设List集合中只能存储一个元素

 */
public class ThreadTest11 {
    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(List list) {
        this.list = list;
    }

    @Override
    public void run() {
        //一直生产,死循环模拟一直生产
        int i=0;
        while(i<100){
            //给仓库加锁
            System.out.println("生产者正在抢锁");
            synchronized (list) {
                if (list.size() > 0) {
                    System.out.println("生产者抢到了但需释放");
                    try {
                        //当前线程进入等待状态,释放锁
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //程序到这里,说明仓库是空的,可以生产
                Object o=new Object();
                list.add(o);
                System.out.println(Thread.currentThread().getName()+"->"+o);
                //唤醒消费者,唤醒不会释放锁
                list.notify();
                i++;
            }
        }
    }
}

//消费线程
class Consumer implements Runnable{
    private List list;

    public Consumer(List list) {
        this.list = list;
    }

    @Override
    public void run() {
        //一直消费
        int i=0;
        while (i<100){
            System.out.println("消费者正在抢锁");
            synchronized (list){
                if(list.size()==0){
                    System.out.println("消费者抢到了但需释放");
                    try {
                        //仓库空了
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //程序执行到这里,说明仓库中有数据可以消费
                Object o=list.remove(0);
                System.out.println(Thread.currentThread().getName()+"->"+o);
                //唤醒生产者生产,唤醒不会释放锁,synchronized代码块执行完才会释放锁
                list.notify();
                i++;
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值