Java高级编程-多线程(2)

1、线程的生命周期

新建: 当一个 Thread 类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
就绪: 处于新建状态的线程被 start() 后,将进入线程队列等待 CPU 时间片,此时它已具备了运行的条件,只是没分配到CPU 资源
运行: 当就绪的线程被调度并获得 CPU 资源时 , 便进入运行状态, run() 方法定义了线程的操作和功能
阻塞: 在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态
死亡: 线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
2、线程的同步

 2.1 问题的提出

        多个线程执行的不确定性引起执行结果的不稳定

2.2 问题的原因

        当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有

执行完,另一个线程参与进来执行。导致共享数据的错误。
2.3 解决方法
        
        对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以
参与执行。
Java 对于多线程的安全问题提供了专业的解决方式: 同步机制
(1) Synchronized 的使用方法
        1) 同步代码块:
                
        synchronized (对象 ){
                // 需要被同步的代码;
        }
        2) synchronized 还可以放在方法声明中,表示整个方法为 同步方法
        
        public synchronized void show (String name){
        ….
        }
(2) 同步机制中的锁
        1) synchronized 的锁是什么?
任意对象都可以作为同步锁。所有对象都自动含有单一的锁(监视器)。
同步方法的锁:静态方法(类名 .class )、非静态方法( this
同步代码块:自己指定,很多时候也是指定为 this 或类名 .class
        2) 注意:
必须确保使用同一个资源的 多个线程共用一把锁 ,这个非常重要,否则就无法保证共享资源的安全
一个线程类中的所有静态方法共用同一把锁(类名 .class ),所有非静态方法共用同一把锁(this ),同步代码块(指定需谨慎)
(3)同步的范围
        1) 如何找问题,即代码是否存在线程安全?(非常重要)
(1)明确哪些代码是多线程运行的代码
(2)明确多个线程是否有共享数据
(3)明确多线程运行代码中是否有多条语句操作共享数据
        2) 如何解决呢?(非常重要)
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其 他线程不可以参与执行。 即所有操作共享数据的这些语句都要放在同步范围中
        3)注意:
范围太小:没锁住所有有安全问题的代码
范围太大:没发挥多线程的功能。
(4)释放锁的操作
当前线程的同步方法、同步代码块执行结束。
当前线程在同步代码块、同步方法中遇到 break return 终止了该代码块、该方法的继续执行。
当前线程在同步代码块、同步方法中出现了未处理的 Error Exception ,导致异常结束。
当前线程在同步代码块、同步方法中执行了线程对象的 wait() 方法,当前线程暂停,并释放锁。
(5) 不会释放锁的操作
线程执行同步代码块或同步方法时,程序调用 Thread.sleep() 、Thread.yield() 方法暂停当前线程的执行
线程执行同步代码块时,其他线程调用了该线程的 suspend() 方法将该线程挂起,该线程不会释放锁(同步监视器)。
应尽量避免使用 suspend() resume() 来控制线程
2.4  单例设计模式之懒汉式 ( 线程安全 )
class Singleton {
    private static Singleton instance = null;
    private Singleton(){}
    public static Singleton getInstance(){
        if(instance==null){
            synchronized(Singleton.class){
                if(instance == null){
                    instance=new Singleton();
} } }
        return instance;
} }
public class SingletonTest{
    public static void main(String[] args){
        Singleton s1=Singleton.getInstance();
        Singleton s2=Singleton.getInstance();
        System.out.println(s1==s2);
} }

2.5 线程的死锁问题

死锁
        不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
        出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
解决方法
        专门的算法、原则
        尽量减少同步资源的定义
        尽量避免嵌套同步
2.6  Lock( )
JDK 5.0 开始, Java 提供了更强大的线程同步机制 ——通过显式定义同步锁对象来实现同步。同步锁使用 Lock 对象充当。
java.util.concurrent.locks.Lock 接口是控制多个线程对共享资源进行访问的工具。 锁提供了对共享资源的独占访问,每次只能有一个线程对 Lock 对象加锁,线程开始访问共享资源之前应先获得Lock 对象。
ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock ,可以显式加锁、释放锁。
class A{
private final ReentrantLock lock = new ReenTrantLock();
public void m(){
lock.lock();
try{
//保证线程安全的代码; }
finally{
lock.unlock(); 
} } }
注意:如果同步代码有异常,要将unlock()写入finally语句块

2.7 synchronized Lock 的对比

1) Lock 是显式锁(手动开启和关闭锁,别忘记关闭锁), synchronized 是隐式锁,出了作用域自动释放
2) Lock 只有代码块锁, synchronized 有代码块锁和方法锁
3.) 使用 Lock 锁, JVM 将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
优先使用顺序:
Lock -->   同步代码块(已经进入了方法体,分配了相应资源) -->   同步方法
(在方法体之外)
3、 线程的通信
3.1  wait() notify() notifyAll()
wait() :令当前线程挂起并放弃 CPU 、同步资源并等待,使别的线程可访问并修改共享资源,而当 前线程排队等候其他线程调用notify() notifyAll() 方法唤醒,唤醒后等待重新获得对监视器的所有 权后才能继续执行。
notify() :唤醒正在排队等待同步资源的线程中优先级最高者结束等待
notifyAll () :唤醒正在排队等待资源的所有线程结束等待 .
这三个方法只有在 synchronized 方法或 synchronized 代码块中才能使用,否则会报
java.lang.IllegalMonitorStateException 异常。
因为这三个方法必须有锁对象调用,而任意对象都可以作为 synchronized 的同步锁,
因此这三个方法只能在 Object 类中声明。
(1) wait() 方法
在当前线程中调用方法: 对象名 .wait()
使当前线程进入等待(某对象)状态 ,直到另一线程对该对象发出 notify (或 notifyAll) 为止。
调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)
调用此方法后,当前线程将释放对象监控权 ,然后进入等待
在当前线程被 notify 后,要重新获得监控权,然后从断点处继续代码的执行。
(2) notify()/notifyAll()
在当前线程中调用方法: 对象名 .notify()
功能:唤醒等待该对象监控权的一个 / 所有线程。
调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)
4、 JDK5.0 新增线程创建方式
4.1  新增方式一:实现 Callable 接口
与使用 Runnable 相比, Callable 功能更强大些
         相比 run() 方法,可以有返回值
         方法可以抛出异常
         支持泛型的返回值
         需要借助 FutureTask 类,比如获取返回结果
Future 接口        
         可以对具体 Runnable Callable 任务的执行结果进行取消、查询是否完成、获取结果等。
         FutrueTask Futrue 接口的唯一的实现类
         FutureTask 同时实现了 Runnable, Future接口。它既可以作为Runnable 被线程执行,又可以作为 Future 得到 Callable 的返回值
4.2  新增方式二:使用线程池
1、 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
2、 好处:
         提高响应速度(减少了创建新线程的时间)
         降低资源消耗(重复利用线程池中线程,不需要每次都创建)
         便于线程管理
3、 线程池相关 API
JDK 5.0 起提供了线程池相关 API ExecutorService Executors
ExecutorService :真正的线程池接口。常见子类 ThreadPoolExecutor        
         void execute(Runnable command) :执行任务 / 命令,没有返回值,一般用来执Runnable
         <T> Future<T> submit(Callable<T> task) :执行任务,有返回值,一般又来执行 Callable
         void shutdown() :关闭连接池
Executors :工具类、线程池的工厂类,用于创建并返回不同类型的线程池
         Executors.newCachedThreadPool() :创建一个可根据需要创建新线程的线程池
         Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
         Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
         Executors.newScheduledThreadPool(n) :创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值