Java并发-多线程(2)

本文详细探讨了Java中线程的同步、并发协作、线程池及定时器的原理与应用,包括同步机制、死锁避免、线程间通信、线程池优势与工作原理、ThreadLocal类使用场景及定时器的多种调度方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、线程的同步

1.什么是线程的同步

线程同步(线程安全):多个线程访问同一份资源的时候要考虑这个问题。

同步方法:

使用同步代码块:synchronized(要加锁的变量){作用的代码块范围}

      // 子线程类

      public static class SonThread extends Thread {

           private Object account;

          

           @Override

           public void run() {

                 synchronized(account) {

                      account.addMoney(100);

                 }

           }

      }

被加锁部分,直到运行的线程结束后解锁。

synchronized(Object obj){}括号里面跟的是一个地址不变的object,如果对象地址变了,就解锁了。一般是将各线程的共享对象放入锁内。

注意:Integer、String等类型一变化就会生成新的对象,所以要特别注意能不能用来做锁。

各线程共享的对象放到synchronized(各线程共享的对象){}后面的括号里(即同步监视器),对于Runnable,共享的对象是Runnable(因为Runnable对象作为参数传入了各个线程的构造方法)。对于另外一种直接用Thread实现的方法,共享的是跟main()同级的类属性(对象)

 

2.同步方法

即用synchronized关键字修饰的方法。与前文修饰代码块的方式比起来,同步方法更多用一些。同步方法无需指定同步监视器,同步监视器默认为this,也就是对象本身

与前文代码块的方式不同,锁从run()方法移到了具体的同步方法上,也更合理一些。

public class Access {

    private double money;


    public synchronized void subMoney(double money) {

        if (this.money > money) {

            this.money -= money;

        }

    }

}

上面的同步方法相当于以下写法:

public synchronized void subMoney(double money) {

    synchronized(this) {

        if (this.money > money) {

            this.money -= money;

        }

    }   

}

 

3.死锁

当两个线程相互等待对方释放同步监视器时就会发生死锁(同步监视器即同步方法锁定的this,或者同步代码块中的共享的对象),而且Java虚拟机并没有检测死锁的机制,也不会采取任何措施来处理死锁。

 

二、线程的并发协作

1.什么是线程的并发协作

在JVM中,线程的调度有一定的随机性,但是我们可以通过一些机制来保证线程的协调运作。

 

2.线程间通信

1)wait()方法

该方法导致当前线程等待。()里可以加时间也可以不加时间(不加时间会一直等下去)。直到其他线程调用该同步锁对象的notify()方法或者notifyAll()方法来唤醒该线程

2)notify()方法

唤醒该同步锁对象上等待的某个线程,如果有多个线程在此同步锁对象上等待,则随机唤醒其中一个。(由于随机性,少用一些)

3)notifyAll()方法

唤醒在此同步锁对象上等待的所有线程。多个线程的协调通过他们共享的对象(即同步锁)的属性判断来进行。

4)注意

wait、notify、notifyAll三个方法必须被在同步代码中被调用,并且只能由同步锁对象来调用。

wait()和sleep()最重要的区别:wait()会释放同步锁对象。wait、notify、notifyAll三个方法是Object提供的方法,sleep()是Thread提供的静态方法。

 

三、线程池

1.为什么要用线程池

系统启动一个新线程的成本是比较高的,因为它涉及与操作系统的交互。在这种情形下,使用线程池可以很好的提高性能,尤其是当程序中需要创建大量生存周期很短的线程时,更应该考虑使用线程池。

 

2.线程池的工作机制

Java1.4之后加入

线程池在系统启动是即创建大量的空闲的线程,程序将一个Runnable对象传给线程池,线程池就会调用一个线程来执行它们的run方法,当run方法执行完成后,该线程也不会死亡,而是再次返回线程池中称为空闲线程,等待执行下一个Runnabe对象中的run方法

 

3.Java提供的线程池

a.调用Executors类的静态工厂方法创建一个ExecutorService对象,该对象代表一个线程池

b.创建Runnable实现类作为线程执行对象

c.调用ExecutorService对象的submit()方法来提交Runnable实现类对象,交给一个线程池中线程执行

d.在需要的时候调用shutdown()方法关闭线程池

ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(3);

newFixedThreadPool.submit(new Runnable() {

@Override

      public void run() {

          // do something

}

});

newFixedThreadPool.shudown();

实际运用中,线程池常搭配工厂模式和单例模式使用。

 

三、ThreadLocal

1. ThreadLocal类的作用

也是用来处理两个线程共享同一个变量的情况,和同步锁给共享数组上锁不一样,这个ThreadLocal类是复制很多个副本,每个线程一个副本

 

2.什么时候用ThreadLocal

根据具体情况。如果多个线程之间需要共享资源,以达到线程之间的通信功能,就使用同步机制;如果需要隔离多个线程之间的共享冲突,则可以使用ThreadLocal。

 

3.常用方法

T get();  返回此线程中的变量副本值

void remove(); 删除此线程局部变量中当前线程的值

void set(T t); 设置此线程局部变量中当前线程副本中的值

 

四、Java中的定时器

就是Timer类。通过调用不同的schedule方法,定义不同的执行时间。可以循环执行。

new Timer().schedule(TimerTask task, Date time);

 

常用方法:

schedule(TimerTask task, Date time):执行任务,执行时间。

schedule(TimerTask task, long delay):过long毫秒后执行。

schedule(TimerTask task, Date firstTime, long period):执行任务,执行时间,循环周期。

schedule(TimerTask task, long delay, long period):执行任务,多长时间后执行,循环周期。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值