Java多线程及线程安全,时间片,锁,守护线程,定时器,wait,notify

一、定义:

  • 进程是一个应用程序(软件)。
  • 线程是一个进程中的执行单元,一个进程有多个线程。
  • 例如:进程是公司,线程是公司中的员工。main方法线程,gc垃圾回收器线程。
  • 不同进程的内存独立,不共享。
  • 不同线程,堆内存和方法区内存共享,但栈内存不共享,一个线程独享一个栈,栈之间互不干扰,叫多线程并发
  • 多线程是为了提高程序的处理效率。

二、实现线程的方法:

第一种:继承Thread类,重写run()方法,调用start()方法开启线程。
(1)start()方法的作用:在JVM中开辟一个新的栈空间,启动线程。启动的线程会在栈的底部自动调用run()方法。如果不用start而是用run,则不会开辟内存空间。
(2)代码:

public class Test{
    public static void main(String args[]){//main线程,主线程
        TestThread test=new TestThread();//创建线程对象
        test.start();//start方法开启线程
        for(int i=0;i<10;i++)
            System.out.println("主线程:"+i);
    }
}
class TestThread extends Thread {
    public void run(){//run方法线程体
        for(int i=0;i<10;i++){
            System.out.println("分支线程:"+i);
        }
    }
}
//可以看到主线程和run线程同时执行,而不是顺序执行

(3)执行结果:

在这里插入图片描述
(4)内存图:

在这里插入图片描述

第二种:实现runnable接口,重写run方法,执行线程需要丢入runnable接口实现类,调用start方法。
(1)start()方法的作用:在JVM中开辟一个新的栈空间,启动线程。启动的线程会在栈的底部自动调用run()方法。如果不用start而是用run,则不会开辟内存空间。
(2)代码:

public class Test{
    public static void main(String args[]){//main线程,主线程
        TestThread test=new TestThread();//创建runnable接口实现类对象
        Thread thread = new Thread(test);//创建线程对象(代理)
        thread.start();//start方法开启线程
        for(int i=0;i<100;i++)
            System.out.println("主线程:"+i);
    }
}
//可以看到主线程和run线程同时执行,而不是顺序执行
//runnable接口优点:所有线程可以共享一个类

class TestThread implements Runnable {
    public void run(){//run方法线程体
        for(int i=0;i<10;i++){
            System.out.println("分支线程:"+i);
        }
    }
}
//可以看到主线程和run线程同时执行,而不是顺序执行

(3)运行结果:

在这里插入图片描述

三、线程生命周期:引入时间片概念

在这里插入图片描述

四、常用方法:

  1. 获取线程对象的名字:

线程对象.getName();

  1. 修改线程对象的名字:

线程对象.setName(name);

  1. 获取“当前”线程对象:

Thread t = Thread.currentThread();

是一个静态方法,不需要用线程对象.currentThread(),直接用Thread.currentThread()。

4.“当前”线程进入休眠:

Thread.sleep(time);
是一个静态方法,所以说如果在main方法中调用支线程.sleep(time),休眠的是main线程而不是支线程。

5.终止线程的休眠:

线程对象.Interrupt();
终止休眠靠的是try…catch的异常处理机制,Interrupt()方法执行后try语句块中还在进行sleep()的代码会报错,并终止sleep()执行,进入catch语句块。

6.终止线程的执行:

线程类中加一个成员变量boolean flag=true,run()方法中加一个if(flag==false){return;}条件判断,如果需要终止线程,调用setFlag()方法将flag置false即可。

五、线程调度:

  1. 常见的线程调度模型:
    (1)抢占式调度模型:
    线程优先级高的线程抢到CPU时间片的概率就高一些。java采用的就是抢占式模型。
    (2)均分式调度模型:
    平均分配CPU时间片,每个线程占有CPU时间片的时间一样。
    2.Java中与线程调度有关的方法:

(1)void serPriority(int newPriority)设置线程优先级

(2)int getPriority()获取线程优先级
最低优先级1
默认优先级5
最高优先级10

(3)static void yield()让位方法,暂停当前正在执行的线程对象,从运行状态回到就绪状态。注意:回到就绪状态后还可能会再次抢到时间片。

(4)void join();t.join()当前线程进入阻塞,t线程先执行,t执行完后当前线程再继续执行。

六、线程安全:重点

  1. 什么时候多线程并发会存在安全问题?
    多线程并发且对共享数据有修改的行为。

  2. 如何解决多线程安全问题?
    线程同步机制(线程排队执行)。
    线程同步会牺牲一部分效率。

  3. 两个模型:
    (1)同步线程模型:
    线程t1执行的时候,必须等待线程t2执行结束。属于多线程同步模型,效率较低,安全。
    (2)异步线程模型:
    线程各自执行,互不影响,属于多线程并发模型,效率高,不安全。

  4. 线程同步机制的语法:

synchronized(){

}

  • ()中写需要排队执行的多个线程的共享对象。
    假设t1.t2.t3.t4.t5五个线程。只希望t1.t2.t3排队,那么()写的是t1.t2.t3的共享对象,而这个对象对t4.t5来说不共享。
  • {}中写不同线程需要排队执行的代码。

synchronized 实例方法名(){}

  • 如果共享对象是this,并且需要同步的代码是整个方法体,建议用这种方式。否则用同步代码块的方式。
  1. 线程同步的实现原理:
    (1)java中每个对象都有一把锁。
    (2)假设t1.t2线程并发,开始执行代码的时候,肯定有先有后。
    (3)假设t1抢到时间片,先执行了,遇到synchronized,这个时候自动找()中共享对象的锁,找到之后占有这把锁,然后执行同步代码块中的程序,直到同步代码块执行完,t1才会释放这把锁。
    (4)假设t1已经占有这把锁,此时t2抢到时间片,也遇到synchronized,也会去占有()中共享对象的锁,但是这把锁已经被t1占有了,t2只能在同步代码块外面等待t1结束,直到t1释放锁,此时t2才可能抢到锁,进入自己的同步代码块执行。
    (5)占有到共享对象执行代码块。占有不到共享对象不执行代码块。

  2. 例子:模拟银行取款
    (1)场景:甲乙分别在前台和自助取款机对张三账户取款,丙在自助取款机对李四账户取款。
    (2)代码:

public class Test{
    public static void main(String args[]) {
        Account account1 = new Account("张三",10000);//创建账户对象张三
        AccountThread thread1 = new AccountThread(account1);//创建线程对象,对account1操作
        thread1.setName("自助取款机-001");
        AccountThread thread2 = new AccountThread(account1);//创建线程对象,account1操作
        thread2.setName("前台");
        thread1.start();//开启线程,模拟甲在自助取款机-001对张三的账户取款
        thread2.start();//开启线程,模拟乙在前台对张三的账户取款

        Account account2 = new Account("李四",7000);//创建账户对象李四
        AccountThread thread3 = new AccountThread(account2);//创建线程对象,account2操作
        thread3.setName("自助取款机-002");
        thread3.start();//开启线程,模拟丙在自助取款机-002对张三的账户取款
    }
}
class Account  {
    private String ID;
    private int balance;

    public Account() {
    }

    public Account(String ID, int balance) {
        this.ID = ID;
        this.balance = balance;
    }

    public String getID() {
        return ID;
    }

    public void setID(String ID) {
        this.ID = ID;
    }

    public int getBalance() {
        return balance;
    }

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

    public void drawMoney(int money){
        // synchronized (this) {//this表示当前对象
            int before = this.getBalance();
            int after = before-money;
            this.setBalance(after);
        // }
    }
}
class AccountThread extends Thread{//线程类
    Account account;
    public AccountThread(Account account){
            this.account = account;
    }
    public void run(){
        account.drawMoney(5000);//取款5000
        System.out.println("用户在"+Thread.currentThread().getName()+"对账户"+account.getID()+"取款成功,余额:"+account.getBalance());
    }
}


(3)若不用synchronized关键字,可能出现的情况:
甲乙同时取款,甲取完款后余额没有及时更新,乙抢到时间片进入取款代码,即更新余额的代码没有抢到时间片,此时甲乙都是以10000元的余额开始取款,甲乙同时取5000后可能余额还是5000。
在这里插入图片描述
(4)使用synchronized关键字后,就不会出现上述不安全情况。但是注意:线程1和2会线程同步,排队执行,但是线程3不会排队,因为线程1,2是操作的同一个对象张三,线程3是操作的另一个对象李四。
即:若张三账户在前台取款则该账户同一时间不能在取款机取款,只有在前台取完款,余额发生变化后,张三账户才能在取款机取款。而这个机制对李四账户没有影响,李四可以在任何时间取款。
在这里插入图片描述

(7)面试题:

public class Test{
    public static void main(String args[]) {
        Test1 test = new Test1();
        Test1Thread tt1 = new Test1Thread(test);
        Test1Thread tt2 = new Test1Thread(test);
        tt1.setName("t1");
        tt2.setName("t2");
        tt1.start();
        try {
            Thread.sleep(1000);//目的是为了先开启线程tt1
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        tt2.start();
    }
}
class Test1  {
   public synchronized void dosome(){
       System.out.println("dosome begin");
       try {
           Thread.sleep(1000*4);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       System.out.println("dosome end");
   }
    public void doother(){
        System.out.println("doother begin");
        System.out.println("doother end");
    }
}
class Test1Thread extends Thread{
    Test1 test1;
    public Test1Thread(Test1 test1){
            this.test1 = test1;
    }
    public void run(){
        if (Thread.currentThread().getName().equals("t1")){
            test1.dosome();
        }
        if (Thread.currentThread().getName().equals("t2")){
            test1.doother();
        }
    }
}


问题1:doother()方法执行的时候是否需要等待dosome()方法的结束?
答:不需要。因为doother()方法没有synchronized。tt1线程先执行,占有test对象的锁,执行dosome()方法,tt2线程若抢到时间片,则tt2线程执行,tt2执行doother()方法不需要获取对象test的锁,所以不需要等待。

问题2:doother()方法加上synchronized后执行的时候是否需要等待dosome()方法的结束?
答:需要。因为对象才有锁,方法没有锁,锁的是test对象而不是方法。tt1线程先执行,占有test对象的锁,执行dosome()方法,tt2线程若抢到时间片,tt2会去占有test锁,但是锁已经被tt1占了,所以tt2会等待test1锁被释放再执行。

public class Test{
    public static void main(String args[]) {
        Test1 test1 = new Test1();
        Test1 test2 = new Test1();
        Test1Thread tt1 = new Test1Thread(test1);
        Test1Thread tt2 = new Test1Thread(test2);
        tt1.setName("t1");
        tt2.setName("t2");
        tt1.start();
        try {
            Thread.sleep(1000);//目的是为了先开启线程tt1
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        tt2.start();
    }
}
class Test1  {
   public synchronized void dosome(){
       System.out.println("dosome begin");
       try {
           Thread.sleep(1000*4);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       System.out.println("dosome end");
   }
    public synchronized void doother(){
        System.out.println("doother begin");
        System.out.println("doother end");
    }
}
class Test1Thread extends Thread{
    Test1 test1;
    public Test1Thread(Test1 test1){
            this.test1 = test1;
    }
    public void run(){
        if (Thread.currentThread().getName().equals("t1")){
            test1.dosome();
        }
        if (Thread.currentThread().getName().equals("t2")){
            test1.doother();
        }
    }
}

问题3:doother()方法执行的时候是否需要等待dosome()方法的结束?
不需要。因为两个线程是针对不同对象的。

问题4:synchronized 都前加static 变成静态方法后,doother()方法执行的时候是否需要等待dosome()方法的结束?
需要。synchronized出现在静态方法上是类锁,锁的是类,一个类只有一把锁。只有对象和类才有锁,别的没有锁。

(8)死锁:
两个线程互锁,程序一直不能往下执行,应该避免。开发的时候尽量不要synchronized嵌套synchronized。

七、守护线程:

  1. java中线程分为两大类:
    一是:用户线程。
    二是:守护线程(后台线程),如垃圾回收器线程。
  2. 守护线程特点:
    一般守护线程是一个死循环,所有的用户线程只要结束,守护线程就自动结束。
    3.实现代码:
public class Test{
    public static void main(String args[]) {
       Thread t= new TestThread();
       t.setDaemon(true);//将线程变为守护线程
    }
}

class TestThread extends Thread{
    public void run(){
        while(true){
            System.out.println(Thread.currentThread().getName());
        }
    }
}

八、定时器:

timer.schedule(定时任务对象,第一次执行时间,间隔多久执行一次);

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

public class Test{
    public static void main(String args[]) throws ParseException {
        Timer timer = new Timer();
        //Timer timer = new Timer(true);//守护线程的方式
        SimpleDateFormat sdf = new SimpleDateFormat("yy-mm-dd  hh:mm:ss");
        Date firsttime = sdf.parse("2022-8-6  16:15:15");
        timer.schedule(new LogTimerTask(),firsttime,1000*2);
    }
}

class LogTimerTask extends TimerTask{

    @Override
    public void run() {
        //编写定时器需要执行的任务
        SimpleDateFormat sdf = new SimpleDateFormat("yy-mm-dd  hh:mm:ss");
        String s = sdf.format(new Date());
        System.out.println(s+"完成一次两秒内的数据备份");
    }
}

九、实现线程的第三种方式(带返回值):

实现Callable接口,实现call()方法。
优点:可以获取线程的返回值。
缺点:效率低。
使用get()方法获取线程返回值。

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Test{
    public static void main(String args[]) throws ExecutionException, InterruptedException {
        FutureTask fu = new FutureTask(new ThreadTest());//FutureTest类代理
        Thread t = new Thread(fu);
        t.start();
        Object s = fu.get();
        System.out.println(s);
    }
}
class ThreadTest implements Callable{

    @Override
    public Object call() throws Exception {
        int a=10;
        int b=10;
        return a+b;
    }
}

十、Object类的wait和notify方法:

  1. java中任何一个对象都有这两个方法。
  2. wait()方法和notify()方法不是通过线程对象调用。
  3. 两种方法必须建立在线程同步的基础上。
  4. 方法作用:
    (1)wait()方法的作用:让正在o对象上活动的线程进入等待状态,并且释放o的锁,直到被唤醒。
    (2)notify()方法的作用:唤醒o对象上正在等待的线程。notifyAll()唤醒o所有线程。
  5. 生产者和消费者模式:
    (1)什么是生产者消费者模式:
    对同一个仓库对象o,生产线程负责生产,消费线程负责消费,生产线程和消费线程达到均衡。
    (2)案例:
    仓库采用list集合。list集合中智能存储1个元素。1个元素就表示仓库满了。如果list集合中元素个数是0,就表示仓库空了。
import java.util.ArrayList;
import java.util.List;

public class Test{
    public static void main(String[] args) {
        List list = new ArrayList();//仓库
        Thread producer = new Thread(new Producer(list));//生产者线程
        Thread consumer = new Thread(new Consumer(list));//消费者线程
        producer.setName("生产者线程");
        consumer.setName("消费者线程");
        producer.start();
        consumer.start();
    }
}
class Producer implements Runnable{
    List list;

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

    @Override
    public void run() {
        while(true) {//死循环一直生产
            synchronized (list) {
                if (list.size() >=1) {//仓库满
                    try {
                        list.wait();//生产者线程等待
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                else{//仓库未满
                    Object obj = new Object();
                    list.add(obj);
                    System.out.println(Thread.currentThread().getName()+"生产了"+obj.toString());
                    list.notifyAll();//唤醒等待的线程,继续消费
                }
            }
        }
    }
}
class Consumer implements Runnable{
    List list;

    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();
                    }
                }
                else{
                    Object obj = list.remove(0);
                    System.out.println(Thread.currentThread()+"购买了"+obj.toString());
                    list.notifyAll();//唤醒等待的线程,继续生产
                }
            }
        }
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

姓蔡小朋友

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值