0
点赞
收藏
分享

微信扫一扫

JavaSE进阶第十七天——多线程

小北的爹 2022-03-24 阅读 56

文章目录

多线程

什么是程序?
程序:程序员编写的功能代码就是程序。
进程:正在运行中的程序就是进程。
线程:进程的执行单元/场景。

线程的常用方法

  1. Thread.sleep(long time):让线程进入指定的休眠时间
  2. yield():让线程放弃当前CUP时间片,回到可运行状态
  3. join():让当前线程暂停执行进入阻塞状态,等待其它线程执行完毕后再执行
  4. interrupt():终止线程的睡眠
  5. stop():强行终止线程
  6. wait():让当前对象线程进入等待
  7. notify():通知当前任意一个线程对象可以释放锁(即让wait()方法退出等待)
  8. notifyAll():通知当前线程对象的所有等待线程释放锁
  9. start():启动线程

创建线程对象

创建线程对象的方法有三个:

  1. 继承Thread类,并重写run方法
class MyThread extends Thread{
    @Override
    public void run() {
       for (int i = 0; i < 1000; i++) {
            System.out.println("分支线程---->"+i);
        }
    }
}
public class ThreadTest02 {
    public static void main(String[] args) {
        //创建分支线程对象
        MyThread myThread = new MyThread();
        //启动线程 调用start方法
        myThread.start();
        for (int i = 0; i < 1000; i++) {
            System.out.println("主线程---->"+i);
        }

    }
}
  1. 实现Runnable接口,重写run方法
public class ThreadTest03 {
    public static void main(String[] args) {
        //创建可运行对象
        MyRunnable myRunnable = new MyRunnable();
        //将可运行对象封装程一个线程对象
        Thread myThread = new Thread(myRunnable);
        //合并以上代码
        Thread myThread = new Thread(new MyRunnable());
        //启动线程
        myThread.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);
        }
    }
}

  1. 实现Callable类,再创建FutureTask对象在构造方法中传入Callable实现类对象,然后创建Thread对象在构造方法中传入FutureTask对象
/*
实现线程的第三种方式:
    实现Callable接口
 */
public class ThreadTest15 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
			 //第一步:产生Callable实现类对象
			 Callable c = new MyCallable();
        //第二步:创建一个“未来任务“类对象,传入Callable实现类对象
        FutureTask task = new FutureTask(c);
        //创建线程对象
        Thread t = new Thread(task);
        t.start();
        //这里是main方法,这是在主线程中
        //主线程怎么获取t线程的返回结果?
        //get()方法的执行会导致当前线程阻塞。
        Object obj = task.get();
        System.out.println("线程执行结果:"+obj);
        //main方法这里的程序要想执行必须等待get()方法的结束
        //而egt()方法可能需要很久。因为get()方法是为了拿另一个线程的执行结果
        //另一个线程执行是需要时间的
    }
}
class MyCallable implements Callable{
   @Override
     public Object call() throws Exception {//   call()方法就相当于run方法只不过这个有返回值。
         //线程执行一个任务,执行之后可能会有一个执行结果
         //模拟执行
         System.out.println("call method begin");
         Thread.sleep(1000*10);
         System.out.println("call method end");
         int a =100;
         int b =100;
         return a+b;
     }
}

线程的调度模式

线程的调度采用的是抢占式调度模式。什么意思呢?就是说线程之间需要抢夺cup时间片的使用权。
cup时间片:cup分配给线程的使用时间。
线程也是有优先级的。在java中主线程(main方法)和子线程(自定义线程)的默认优先级都是一样的,都是5。而且并不是优先级高的就一定先抢到cup时间片。而是抢到的几率比较高而已。

线程的状态

线程的状态:

  1. 新建状态(准备/初始状态)(new线程对象)
  2. 可运行状态(start方法)
  3. 运行状态(被CUP选中)
  4. 终止状态(stop方法)
  5. 阻塞状态(sleep/join方法)
  6. 等待状态(wait方法)

线程的分类

线程的分类:

  1. 用户线程:
    <1>主线程
    <2>子线程
  2. 守护线程:
    作用:在后台默默的为用户线程提供服务。例如:垃圾回收线程
    设置线程为守护线程方法:setDaemon(true)
    守护线程的生命周期:当所有的用户线程结束,守护线程也结束

定时器

定时器Timer:间隔特定的时间,执行特定的程序。

//假设这是一个记录日志的定时任务
class LogTimerTask extends TimerTask {
    public void run(){
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        String strTime = sdf.format(new Date());
        System.out.println(strTime+"成功完成了一次数据备份");
    }
}
/*
使用定时器指定定时任务
 */
public class TimerTest {
    public static void main(String[] args) throws ParseException {
        //创建定时器对象
        Timer timer = new Timer();
        //Timer timer = new Timer(true); 守护线程的方式
        //指定定时任务
        //timer.schedule(定时任务,第一次执行时间,间隔多久执行一次);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        Date firstTime = sdf.parse("2021-03-27 21:35:00 000");
        //timer.schedule(new LogTimerTask(),firstTime,1000*10);
        //使用匿名内部类的方式
        timer.schedule(new 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+"成功完成了一次数据备份");
            }
        }, firstTime, 1000 * 10);

    }
}

线程同步

线程同步:线程同步是为了解决多线程执行中数据不一致的问题。
例如:

/*
模拟线程并发下的数据安全问题
 */
public class ThreadTest {
    public static void main(String[] args) {
        // 创建账户对象(只创建1个)
        Account act = new Account("act-001", 10000);
        // 创建两个线程
        Thread t1 = new AccountThread(act);
        Thread t2 = new AccountThread(act);
        // 设置name
        t1.setName("t1");
        t2.setName("t2");
        // 启动线程取款
        t1.start();
        t2.start();
    }
}

/*
银行账户
    不使用线程同步机制,多线程对同一个账户进行取款,出现线程安全问题。
 */
 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 void setActno(String actno) {
        this.actno = actno;
    }
    public double getBalance() {
        return balance;
    }
    public void setBalance(double balance) {
        this.balance = balance;
    }
    //取款的方法
    public void withdraw(double money){
        // t1和t2并发这个方法。。。。(t1和t2是两个栈。两个栈操作堆中同一个对象。)
        // 取款之前的余额
        double before = this.getBalance(); // 10000
        // 取款之后的余额
        double after = before - money;
        // 在这里模拟一下网络延迟,100%会出现问题
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 更新余额
        // 思考:t1执行到这里了,但还没有来得及执行这行代码,t2线程进来withdraw方法了。此时一定出问题。
        this.setBalance(after);
    }
}
//取款线程
class AccountThread extends Thread{
    // 两个线程必须共享同一个账户对象。
    private Account act;
    // 通过构造方法传递过来账户对象
    public AccountThread(Account act) {
        this.act = act;
    }
    public void run(){
        // run方法的执行表示取款操作。
        // 假设取款5000
        double money = 5000;
        // 取款
        // 多线程并发执行这个方法。
        act.withdraw(money);
        System.out.println(Thread.currentThread().getName() +
                "对"+act.getActno()+"取款"+money+"成功,余额" + act.getBalance());
    }
}

运行结果:

t1对act-001取款5000.0成功,余额5000.0
t2对act-001取款5000.0成功,余额5000.0

可以看到,我们的账户明明只有10000元,但是取款了10000元却还剩下5000元。这明显是不合理的。那么此时我们就可以使用线程同步机制来使得我们的数据变得合理。

线程同步其实就是一种锁,这种锁叫排他锁。例如:线程t1和线程t2,在线程t1执行的时候必须要等线程t2执行结束,或者线程t2执行的时候必须要等线程t1执行结束。两个线程之间有等待关系。其实就是:线程排队执行。

那么我们怎么实现线程同步呢?

线程同步的关键字是:synchronized
使用方法

  1. 在静态 方法声明的时候加上synchronized关键字。这种方法得到的锁称为类锁
public static synchronized 返回值 方法名(形式参数列表){
	代码...
}

类锁:顾名思义。就是锁住了整一个类。当我们调用的是类锁的时候,那么这个类就只能被一个线程访问,即使这个类中有其它非静态方法也不允许被其它线程访问。就好比一栋房子,房子里很多房间,但是,当一个人进去入房子后其他人不允许再进去了,虽然里面还有很多房间,但是其它人此时无法进入,就是这么霸道。

对象锁:对象锁,正好与类锁相反。当一个人进去之后,其他人也能进去,但是这个人进去后找到一个房间并进去了,此时这个房间是不允许其他人进入的,与类锁相比就没那么霸道。对象所只占据房子的一个房间,类似与类中的一个实例方法,二类锁霸占的是整一个类。
类锁只有一把,就算创建了100个对象,那类锁也只有一把。

  1. 在实例方法上使用synchronized表示共享对象一定是this并且同步代码块是整个方法,这是一个对象锁
    但是这种方法有一个缺点:synchronized出现在实例方法上,表示整个方法体都需要同步,可能会无故扩大同步的范围,**导致程序的执行效率降低。**所以这种方式不常用。
 public synchronized void 方法名(形式参数列表){
	 代码...
 }
  1. 在方法中使用synchronized(要锁的对象){代码…}。这是一个对象锁
public void 方法名(形式参数列表){
	synchronized(对象,可以是this也可以是Object,写你要锁的对象即可){
		代码...
	}
}

高级线程同步(可以了解也可以不了解)

高级线程同步中synchronized被替换成了Lock。
这里建议各位查文档研究一番。当然不研究也行,我只是想工资高亿点点所以才研究了一下下。
在这里插入图片描述

这里就不多做赘述了。多线程基础到此为止。有错请指出。看过给个♥,让我知道我不是一个人~

举报

相关推荐

0 条评论