> 文档中心 > JavaSE进阶第十七天——多线程

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

文章目录

  • 线程
    • 线程的常用方法
    • 创建线程对象
    • 线程的调度模式
    • 线程的状态
    • 线程的分类
    • 定时器
    • 线程同步
    • 高级线程同步(可以了解也可以不了解)

多线程

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

线程的常用方法

  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. 用户线程:
    主线程
    子线程
  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.0t2对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。
这里建议各位查文档研究一番。当然不研究也行,我只是想工资高亿点点所以才研究了一下下。
在这里插入图片描述

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

闲鱼礼物网