• JAVA基础_多线程


        进程:正在执行中的程序。每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫控制单元。

            线程:就是进行中的一个独立的控制单元。线程在控制这进程的执行。

            一个进程至少有个一线程。

            

            Java VM 启动的时候会有一个进程java.exe

            该进程中至少有一个线程负责java程序的执行,而这个线程运行的代码存在于main方法中。该线程称之为主线程。

            扩展:其实更细节说明JVM,JVM启动不止一个线程,还有负责垃圾回收机制的线程。

            Java中main函数就是一个线程。

        创建多线程:

        如何在自定义的代码中创建多线程?

            通过对API的查找,java已经提供了对这类事情的描述,就是Thread类。

        一、创建线程的方法:继承Thread类,覆盖run方法。

            步骤:

            1、定义类继承Thread。

            2、复写Thread类中的run方法。

                    目的:将自定义的代码存储在run方法中,让线程运行。

            3、调用线程的start方法。该方法有两个作用:启动线程,调用run方法。

            

            发现运行结果每次都不同。因为多个线程都获取cpu的执行权,CPU执行到谁,谁就运行。明确一点,在某一时刻,只能有一个程序在运行。(多核除外)。cpu在做着快速的切换,已达到看上去是同时运行的效果。

            我们可以形象的把多线程的运行行为看做是在互相抢夺cpu的执行权。这就是多线程的一个特性:随机性。

           

            为什么要覆盖run方法呢?

                Thread类用于描述线程。该类就定义了一个功能,用于存储线程要运行的代码。该存储功能就是run方法。也就是说Thread类中的run方法,用于存储线程要运行的代码。       

    class Demo extends Thread{

    @Override

    public void run() {

    System.out.println("demo run");

    }

    }

    public class ThreadDemo01 {

    public static void main(String[] args) {

    Demo d = new Demo();//就创建好一个线程。

    d.start();//开启线程并执行该线程的run方法

    d.run();//仅仅是对象调用方法,而线程创建了,并没有运行

    }

    }          

          

          线程运行的状态:        

                被创建        运行      【sleep(time),放弃了执行资格】冻结        【wait()】等待       【notify()】唤醒       消亡【stop(),run方法结束】    临时状态(阻塞)【具备运行资格,但没有执行权】

            获取线程对象及名称:                

            原来线程都有自己默认的名称 。Thread-编号   该编号从零开始。

          Thread类中的常用方法:     

           public final String getName()返回该线程的名称。

            public final void setName(String name)改变线程名称,使之与参数 name 相同。首先调用线程的 checkAccess 方法,且不带任何参数。

            public static Thread currentThread()返回对当前正在执行的线程对象的引用。

           多线程----售票的例子;    

    /*

    * 需求:简单的买票程序:

    * 多个窗口买票。

    */

    class Ticket extends Thread{

    //private static int ticket = 100;

    private int ticket = 10;

    @Override

    public void run() {

    while(true){

    if(ticket>0)

    System.out.println("sale..."+ticket--);

    }

    }

    }

    class Tickets implements Runnable{

    private int ticket = 10;

    @Override

    public void run() {

    while(true){

    if(ticket>0)

    System.out.println(Thread.currentThread().getName()+"..sale..."+ticket--);

    }

    }

    }

    public class TicketsThread {

    public static void main(String[] args) {

    /*Ticket t1 = new Ticket();

    Ticket t2 = new Ticket();

    Ticket t3 = new Ticket();

    Ticket t4 = new Ticket();

    t1.start();

    t2.start();

    t3.start();

    t4.start();*/

    Tickets t = new Tickets();

    Thread t1 = new Thread(t);

    Thread t2 = new Thread(t);

    Thread t3 = new Thread(t);

    Thread t4 = new Thread(t);

    t1.start();

    t2.start();

    t3.start();

    t4.start();

    }

    }

        二、创建线程的方法:实现Runnable接口:

        步骤:

                1、定义类实现Runnable接口。

                2、覆盖Runnable接口中的run方法。将线程要运行的代码放在该run方法中。

                3、通过Thread类建立线程对象。

                4、将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。

                        为什么要将Runnable接口的子类对象传递给Thread的构造函数。因为,自定义的run方法所属的对象 是Runnable接口的子类对象,所以要让线程去执行指定对象的run方法。就必须明确该run方法所属的对象。

                5、调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。

            实现方式和继承方式的区别:

            实现方式好处:避免了单继承的局限性,子啊定义线程,建议使用实现方式。

            继承Thread类:线程代码存放在Thread子类run方法中

            实现Runnable:线程代码存放在接口子类的run方法中  

            多线程的安全问题:       

    /*

    * 通过分析,发现,打印出0,-1,-2等错票

    *

    * 多线程的运行发现了安全问题。

    * 问题的原因:

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

    * 还没有执行完另一个线程参与进来,导致共享数据的错误。

    * 解决办法:

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

    *    

    * Java对多线程的安全问题提供了专业的解决方式:

    * 就是同步代码块:

    *   synchronize(对象){

    *   需要被同步的代码块。

    *   }

    * 对象如同锁。持有锁的线程可以在同步中执行

    * 没有持有锁的线程即使获取了CPU的执行权,也进不去,因为没有获取锁。

    * 同步的例子---火车上的卫生间

    *

    * 同步的前提:

    * 1、必须要有两个或两个以上的线程。

    * 2、必须是多个线程使用同一个锁。

    *

    * 必须保证同步中只能有一个线程在运行。

    *

    * 好处:解决了多线程的安全问题。

    * 弊端:多个线程都需要判断锁,较为消耗资源。

    */

    class Tickets implements Runnable {

    private int ticket = 100;

    Object obj = new Object();

    @Override

    public void run() {

    while(true){

    synchronized(obj){

    if(ticket>0){

    try{Thread.sleep(10);}catch(Exception e){}

    System.out.println(Thread.currentThread().getName()+"..sale.."+ticket--);

    }

    }

    }

    }

    }

    public class TicketsThread {

    public static void main(String[] args) {

    Tickets t = new Tickets();

    Thread t1 = new Thread(t);

    Thread t2 = new Thread(t);

    Thread t4 = new Thread(t);

    t1.start();

    t2.start();

    t3.start();

    t4.start();

    }

    }

                同步函数:      

    /*

    * 需求:

    * 银行有一个金库。

    * 有两个储户分别存300元,每次存100,存3次。

    *

    * 目的:该程序是否有安全问题,如果有,如何解决?

    *

    * 如何找问题:

    * 1、明确那些代码是多线程运行代码。

    * 2、明确共享数据

    * 3、明确多线程运行代码中那些是操作共享数据的。

    *

    * 同步函数用的是那个锁呢?

    * 函数需要被对象调用,那么函数都有一个所属对象引用,就是this。

    * 所以同步函数的锁是this

    *

    * 如果同步函数被静态修饰后,使用的锁是什么?

    * 通过验证,发现不再是this,因为静态方法中不可以定义this。

    *

    * 静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。

    * 类名.class 该对象的类型是Class

    *

    * 静态的同步方法使用的锁是该方法坐在类的字节码文件对象。类名.class

    */

    class Bank{

    private int sum;

    private Object obj = new Object();

    //同步函数

    public synchronized void add(int n){

    //synchronized(obj){

    sum = sum + n;

    try {Thread.sleep(10);} catch (InterruptedException e) {}

    System.out.println("sum="+sum);

    //}

    }

    }

    class Cus implements Runnable{

    private Bank b = new Bank();

    @Override

    public void run() {

    for(int x = 0;x<3;x++)

    b.add(100);

    }

    }

    public class BankDemo {

    public static void main(String[] args) {

    Cus c = new Cus();

    Thread t1 = new Thread(c);

    Thread t2 = new Thread(c);

    t1.start();

    t2.start();

    }

    }

            单例设计模式:

    /*

    * 单例设计模式。

    * 饿汉式:

    * class Single{

    * private static final Single s = new Single();

    * private Single(){}

    * public static Single getInstance(){

    * return s;

    * }

    * }

    */

    //懒汉式

    class Single{

    private static Single s = null;

    private Single(){}

    //延迟加载

    //懒汉式同步函数,会比较低效。

    public static Single getInstance(){

    if(s==null){

    synchronized(Single.class){

    if(s==null)

    s = new Single();

    }

    }

    return s;

    }

    }

                死锁:

                出现的情况一般是,同步中嵌套同步,但锁不同。

    class DeadLock implements Runnable{

    private boolean flag;

    public DeadLock(boolean flag){

    this.flag = flag;

    }

    @Override

    public void run() {

    if(flag){

    synchronized(MyLock.locka){

    System.out.println("if locka");

    synchronized(MyLock.lockb){

    System.out.println("if lockb");

    }

    }

    }else{

    synchronized(MyLock.lockb){

    System.out.println("else lockb");

    synchronized(MyLock.locka){

    System.out.println("else locka");

    }

    }

    }

    }

    }

    class MyLock{

    static Object locka = new Object();

    static Object lockb = new Object();

    }

    public class DeadLockDemo {

    public static void main(String[] args) {

    Thread t1 = new Thread(new DeadLock(true));

    Thread t2 = new Thread(new DeadLock(false));

    t1.start();

    t2.start();

    }

    }

            线程池:

           线程匿名内部类:

            1、匿名Thread类;

            格式:

    new Thread() {

    public void run() {

    for (int x = 0; x < 40; x++) {

    System.out.println(Thread.currentThread().getName()

    + "...X...." + x);

    }

    }

    }.start();

            2、匿名Runnable接口

    new Thread(

        new Runnable(){

            public void run(){

                任务代码...

            }

        }

    ).start();

            3、线程池的概念:

                线程池,其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁的创建线程对象的操作,无需反复创建线程而消耗过多的资源。

                3.1、使用线程池的方式-----Runnable接口

                Executors:线程池创建工厂类:

                        public static ExecutorService newFixedThreadPool(int nThreads):返回线程池对象。

          ExecutorService:线程池类

              Future<?> submit(Runnable task)获取线程池中的某一个线程对象,并执行。

         Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用

         void shutdown()启动一次顺序关闭,执行以前提交的任务,但不接受新任务

    class MyRunnable implements Runnable{

    @Override

    public void run() {

    System.out.println(Thread.currentThread().getName()+"正在运行");

    try {

    Thread.sleep(1000);

    } catch (InterruptedException e) {

    e.printStackTrace();

    }

    System.out.println(Thread.currentThread().getName()+"返回线程池");

    }

    }

    public class ExecutorServiceDemo01 {

    public static void main(String[] args) {

    //创建线程池对象

    ExecutorService service = Executors.newFixedThreadPool(2);

    //创建Runnabel对象

    MyRunnable r = new MyRunnable();

    //从线程池中获取线程对象,并执行MyRunnable中的run()

    service.submit(r);

    //再获取两个

    service.submit(r);

    service.submit(r);

    service.shutdown();

    }

    }

                3、2使用线程池方式---Callable接口。

                Callable接口:与Runnable接口功能相似,用来指定线程的任务。其中的call()方法,用来返回线程任务执行完毕后的结果,call方法可抛出异常。

            步骤和3、1中的一样,只是自己的线程变成了实现Callable接口的子类对象。

                线程间通信,其安全问题的解决和等待唤醒机制

    package Demo04;

    /*

    * 线程间通讯:

    * 其实就是多个线程在操作用一个资源,但是操作的动作不同

    *

    * wait();

    * notify();

    * notifyAll();

    * 都使用在同步中,因为要对持有锁的线程操作。

    * 所以要使用在同步中,因为只有同步才具有锁。

    *

    * 为什么这些操作线程的方法要定义在Object类中呢?

    * 因为这写方法在操作同步中线程时,都必须标识他们所操作的线程只有的锁

    * 只有同一个锁上的被等待线程,可以被同一个锁上的notify唤醒。

    * 也就是说,等待和唤醒必须是同一个锁。

    */

    public class Test {

    public static void main(String[] args) {

    Resource r = new Resource();

    Input in = new Input(r);

    Output out = new Output(r);

    Thread t1 = new Thread(in);

    Thread t2 = new Thread(out);

    t1.start();

    t2.start();

    }

    }

    package Demo04;

    public class Resource {

    String name;

    String sex;

    boolean flag = false;

    }

    package Demo04;

    public class Input implements Runnable{

    private Resource r;

    public Input(Resource r){

    this.r = r;

    }

    @Override

    public void run() {

    int x = 0;

    while(true){

    synchronized(r){

    if(r.flag)

    try {

    r.wait();

    } catch (InterruptedException e) {

    e.printStackTrace();

    }

    if (x == 0) {

    r.name = "mike";

    r.sex = "male";

    } else {

    r.name = "丽丽";

    r.sex = "女";

    }

    x = (x + 1) % 2;

    r.flag = true;

    r.notify();

    }

    }

    }

    }

    package Demo04;

    public class Output implements Runnable{

    private Resource r;

    public Output(Resource r){

    this.r = r;

    }

    @Override

    public void run() {

    while(true){

    synchronized(r){

    if(!r.flag)

    try {

    r.wait();

    } catch (InterruptedException e) {

    e.printStackTrace();

    }

    System.out.println(r.name+":"+r.sex);

    r.flag = false;

    r.notify();

    }

    }

    }

    }

    JDK1.5中的java.util.concurrent.Lock包

    实例是Eclipse中ThreadTest项目下的Demo05包中的Demo05文件。

    此外,在其中,锁最后必须被释放。

    多线程状态图:

  • 相关阅读:
    Java 日期字符串与日期类型转换
    Android 开发笔记“关闭默认键盘”
    MySql 日期转字符串
    Android 开发笔记“调用.net webservice遇到的问题”
    远程连接MySQL 不允许
    未能启用约束。一行或多行中包含违反非空、唯一或外键约束的值。
    Android 开发笔记“浅谈DDMS视图”
    Android 开发笔记“Eclipse 调试和快捷键”
    Android 开发笔记“程序安装包APK的制作”
    第四周进度条
  • 原文地址:https://www.cnblogs.com/gaodq-blogs/p/10763923.html
Copyright © 2020-2023  润新知