java多线程 下

目录

线程的生命周期

 线程的同步

 Synchronized的使用方法

同步机制中的锁

同步的范围

 单例设计模式之懒汉式(线程安全)

 线程的死锁问题

Lock(锁)

synchronized 与 Lock 的对比 

线程的通信

JDK5.0 新增线程创建方式

新增方式一:实现Callable接口

新增方式二:使用线程池

总结


线程的生命周期

JDK中用Thread.State类定义了线程的几种状态

要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类 及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的

种状态

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

java多线程 下_第1张图片

 线程的同步

问题的提出
  • 多个线程执行的不确定性引起执行结果的不稳定
  • 多个线程对账本的共享,会造成操作的不完整性,会破坏数据
class Ticket implements Runnable {
    private int tick = 100;
    public void run() {
        while (true) {
            if (tick > 0) {
                System.out.println(Thread.currentThread().getName() + "售出车票,tick为:"+                                                     tick--);
                }else
                 break;
        }
    }
}

class TicketDemo {
    public static void main(String[] args) {
        Ticket t = new Ticket();
        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        Thread t3 = new Thread(t);
        t1.setName("t1窗口");
        t2.setName("t2窗口");
        t3.setName("t3窗口");
        t1.start();
        t2.start();
        t3.start();
    }
}

java多线程 下_第2张图片java多线程 下_第3张图片

 

 

2. 问题的原因:

当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。

3. 解决办法:

对多条操作共享数据的语句,.只能让一个线程都执行完,在执行过程中,其他线程不可以 参与执行

 

 Synchronized的使用方法

  Java对于多线程的安全问题提供了专业的解决方式: 同步机制
1. 同步代码块:
synchronized (对象){
// 需要被同步的代码;
}
2. synchronized还可以放在方法声明中,表示整个方法为 同步方法
例如: public synchronized void show (String name){ …. }  

同步机制中的锁

同步锁机制:

在《Thinking in Java》中,是这么说的:对于并发工作,你需要某种方式来防止两个任务访问相同的资源(其实就是共享资源竞争)。 防止这种冲突的方法就是当资源被一个任务使用时,在其上加锁。第一个访问某项资源的任务必须锁定这项资源,使其他任务在其被解锁之前,就无法访问它了,而在其被解锁之时,另一个任务就可以锁定并使用它了。

synchronized的锁是什么?

  • 任意对象都可以作为同步锁。所有对象都自动含有单一的锁(监视器)。
  • 同步方法的锁:静态方法(类名.class)、非静态方法(this
  • 同步代码块:自己指定,很多时候也是指定为this或类名.class

注意:

  •  必须确保使用同一个资源的多个线程共用一把锁,这个非常重要,否则就无法保证共享资源的安全
  • 一个线程类中的所有静态方法共用同一把锁(类名.class),所有非静态方 法共用同一把锁(this),同步代码块(指定需谨慎)

同步的范围

1 、如何找问题,即代码是否存在线程安全?(非常重要)
  • (1)明确哪些代码是多线程运行的代码
  • (2)明确多个线程是否有共享数据
  • (3)明确多线程运行代码中是否有多条语句操作共享数据
2 、如何解决呢?(非常重要)
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其 他线程不可以参与执行。 即所有操作共享数据的这些语句都要放在同步范围中
3 、切记:
  • 范围太小:没锁住所有有安全问题的代码
  • 范围太大:没发挥多线程的功能。

释放锁的操作

当前线程的同步方法、同步代码块执行结束。

当前线程在同步代码块、同步方法中遇到breakreturn终止了该代码块、 该方法的继续执行。

当前线程在同步代码块、同步方法中出现了未处理的ErrorException,导致异常结束。

当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁

不会释放锁的操作

线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行

线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)。 应尽量避免使用suspend()resume()来控制线程

 单例设计模式之懒汉式(线程安全)

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);
        }
     }

 线程的死锁问题

死锁

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁

出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续

解决方法

  • 专门的算法、原则
  • 尽量减少同步资源的定义
  • 尽量避免嵌套同步 

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语句块

synchronized Lock 的对比 

相同:二者都可以解决线程安全问题
不同 synchronized机制在执行完相应的同步代码以后,自动释放同步监视器
     lock 需要手动的启动同步lock,同时结束也需要手动释放unlock

线程的通信

 class Communication implements Runnable {
    int i = 1;
    public void run() {
        while (true) {
            synchronized (this) {
                notify();
                if (i <= 100) {
                    System.out.println(Thread.currentThread().getName() +":" + i++);
                    } else
                     break;
                    try {
                        wait();
                      } catch (InterruptedException e) {
                        e.printStackTrace();
                }
            }
        }
    }
}
wait() notify() notifyAll()
  • wait():令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当 前线程排队等候其他线程调用notify()notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有 权后才能继续执行。
  • notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待
  • notifyAll ():唤醒正在排队等待资源的所有线程结束等待.

这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报java.lang.IllegalMonitorStateException异常。

因为这三个方法必须有锁对象调用,而任意对象都可以作为synchronized的同步锁,因此这三个方法只能在Object类中声明

wait() 方法

  • 在当前线程中调用方法: 对象名.wait()
  • 使当前线程进入等待(某对象)状态 ,直到另一线程对该对象发出 notify(或notifyAll) 为止。
  • 调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)
  • 调用此方法后,当前线程将释放对象监控权 ,然后进入等待
  • 在当前线程被notify后,要重新获得监控权,然后从断点处继续代码的执行。

notify()/notifyAll()

  • 在当前线程中调用方法: 对象名.notify()
  • 功能:唤醒等待该对象监控权的一个/所有线程。
  • 调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)

JDK5.0 新增线程创建方式

新增方式一:实现Callable接口

与使用Runnable相比, Callable功能更强大些

  • 相比run()方法,可以有返回值
  • 方法可以抛出异常
  • 支持泛型的返回值
  • 需要借助FutureTask类,比如获取返回结果

Future 接口
  • 可以对具体RunnableCallable任务的执行结果进行取消、查询是否完成、获取结果等。
  • FutrueTaskFutrue接口的唯一的实现类
  • FutureTask 同时实现了Runnable, Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值

新增方式二:使用线程池

背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。

思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。

 好处:

  • 提高响应速度(减少了创建新线程的时间)
  • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
  • 便于线程管理  corePoolSize:核心池的大小   maximumPoolSize:最大线程数   keepAliveTime:线程没有任务时最多保持多长时间后会终止

线程池相关API

JDK 5.0起提供了线程池相关APIExecutorService Executors

  • ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
  • void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
  • Future submit(Callable task):执行任务,有返回值,一般又来执行 Callable
  • void shutdown() :关闭连接池
  • Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
  • Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
  • Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
  • Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
  • Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行

总结

package com.jyc.p1;

import java.util.concurrent.*;
import java.util.concurrent.locks.ReentrantLock;

/*
栗子:创建三个窗口卖票,总票为100张,使用实现 Runnable 接口的方式
    1.问题卖票过程中,出现了重票,错票---》线程安全问题
    2.问题出现的原因:在某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票
    3.如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来,直到线程a操作完 ticket 时
其他线程才可以开始操作 ticket,这种情况线程a出现了阻塞,也不能被改变
    4.在java中我们通过同步机制,来解决线程的安全问题
        方式1.同步代码块
            synchronized (同步监视器){
                //需要被同步的代码
            }
          ①.操作共享数据的代码,即为需要同步的代码
          ②.共享数据,多个线程共同操作的变量,比如 ticket
          ③.同步监视器,俗称锁,任何一个类的对象,都可以充当锁(多个线程必须共用一把锁)
          ④ 在实现  Runnable 接口创建多线程的方式中,我们可以考虑使用this充当同步监视器
       方式2.同步方法
            如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明为同步的
            同步方法仍是同步监视器,只是我们不需要显示的声明,
                非静态同步方法,同步监视器是this
                静态同步方法,同步监视器是当前类本身

     同步的方式,解决了线程的安全问题
     操作同步代码时,只能有一个线程参与,其他线程等待,相当于一个单线程的过程,效率低

    synchronized 与 lock 的异同
       相同:二者都可以解决线程安全问题
       不同 synchronized机制在执行完相应的同步代码以后,自动释放同步监视器
       lock 需要手动的启动同步lock,同时结束也需要手动释放unlock


* */
class window implements   Runnable{
    private    int ticket=100;
    Object obj= new Object();
//    方式一 :同步代码块
//    public void run() {
//        //此时的this 唯一的window对象
//        synchronized (this){//synchronized (obj){
//        while (true){
//            if (ticket>0){
//                try {
//                    Thread.sleep(100);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//                System.out.println(Thread.currentThread().getName()+"卖票,票号为:"+ticket);
//                ticket--;
//            }else{
//                break;
//            }
//        }
//    }
//
//    }

//    方式三
//    1.实例化ReentrantLock
    private ReentrantLock lock=new ReentrantLock();
    public void run() {
//        while (true){
//            show();
//        }

        while (true){
            try{
                //调用锁定方法lock()
                lock.lock();

                if (ticket>0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"卖票,票号为:"+ticket);
                    ticket--;
                }else{
                    break;
                }
            }finally {
                //调用解锁的方法unlock
                lock.unlock();
            }
        }
    }

    //方式二 同步方法
    private synchronized void  show(){ //同步监视器 this
        if (ticket>0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"卖票,票号为:"+ticket);
                ticket--;
            }
    }
}
/*
* 单例模式 懒汉式
*
* */
class  Bank{
    private  Bank(){}
    private  static  Bank
            instance=null;
    //方式一.同步监视器为 Bank.class 类也为对象
//    public  static synchronized Bank getInstance(){
//        if (instance==null){
//            instance=new Bank();
//
//        }
//        return  instance;
    public  static  Bank getInstance(){
        if (instance==null){
            synchronized (Bank.class){
             if (instance==null){
                 instance=new Bank();
           }
       }
   }
        return  instance;
    }
}
/*
* 线程通讯的例子
* 涉及到三个方法
* wait() 一但执行此方法线程就进入阻塞状态,并释放同步监视器
* notify() 一但执行次此方法,就会唤醒wait的一个线程,如果有多个线程被wait就唤醒优先级高的那个
* notifyAll 一但执行次此方法,就会唤醒wait的所有线程
* 说明 wait notify notifyAll 这三个方法必须使用在同步代码块或同步方法中
*       三个方法的调用者必须是同步代码块,或同步方法中的同步监视器
*       定义在object中
*
* sleep 和 wait 的异同
*  相同点 一但 执行方法,都可以使当前线程进入阻塞状态,
*  不同点 1.两个方法声明的位置不同,Thread类中声明的sleep,object类中声明的wait
*         2. 调用的要求不同,sleep可以在任何场景下调用,wait方法必须使用在同步代码块或者同步方法中
*         3.关于是否释放同步监视器,如果两个方法都使用在同步代码块,或同步方法中,sleep()不会释放锁
*          wait()会释放锁
*
*
* */
class   Number implements  Runnable{
    private  int number=1;
    public void run() {
        while (true){
           synchronized (this){
               notify();
               if (number<=100){
                   try {
                       Thread.sleep(10);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   System.out.println(Thread.currentThread().getName()+" : "+number);
                   number++;
                   try {
                       //使得调用如下wait()方法的线程进入阻塞状态
                       wait();
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
               }else{
                   break;
               }
           }

        }
    }
}

/*
*创建线程的方式三 实现 Callable 接口
*   如何理解实现 Callable接口的方式 比实现 Runnable 接口创建多线程的方式强大
*      call()可以有返回值
*      call()可以抛出异常
*      Callable 支持泛型
*
*
* */
//创建一个实现了 Callable 的实现类
class NumThread implements Callable{
    //实现call方法 将此线程需要执行的操作声明在call方法中

    @Override
    public Object call() throws Exception {
        int sum=0;
        for (int i = 1; i <=100 ; i++) {
            if(i%2==0){
                sum+=i;
            }
        }
        return  sum;
    }
}
/*

* */
public class Shkstart {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //演示线程安全问题------------
//        window w=new window();
//     Thread t1 =new Thread(w);
//        Thread t2 =new Thread(w);
//        Thread t3 =new Thread(w);
//        t1.setName("窗口一");
//        t2.setName("窗口二");
//        t3.setName("窗口三");
//        t1.start();
//        t2.start();
//        t3.start();
        //线程通讯的演示----------
//        Number number=new Number();
//        Thread t1=new Thread( number);
//        Thread t2=new Thread( number);
//        t1.setName("线程一");
//        t2.setName("线程二");
//        t1.start();
//        t2.start();

//        *创建线程的方式三 实现 Callable 接口---------------
        //3.创建实现Callable接口实现类的对象
        NumThread numThread=new NumThread();
        //4.将此Callable接口实现的对象 传递到 FutureTask 构造器中 创建 FutureTask 对象
        FutureTask futureTask = new FutureTask(numThread);
        //5.将 FutureTask的对象作为参数 传递到 Thread类的构造器中并调用start
       new Thread(futureTask).start();
        Object sum= futureTask.get();
        //6.获取 Callable中 call方法的返回值
        System.out.println(sum);

//        * 创建线程的方式四 使用线程池
        // 提高响应速度(减少了创建新线程的时间)
        // 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
        // 便于线程管理
        // corePoolSize:核心池的大小
        // maximumPoolSize:最大线程数
        // keepAliveTime:线程没有任务时最多保持多长时间后会终止

        //1.提供指定线程数量的线程池
        ExecutorService service=Executors.newFixedThreadPool(10);
//        设置线程池的属性

        //2.执行指定线程的操作实现 Callable或 Runnable 的对象
//        service.submit()
//        service.execute();
        //3.关闭链接池
        service.shutdown();
    }
}

你可能感兴趣的:(java学习,java,jvm,开发语言)