java学习笔记(6)-多线程(1)

标签(空格分隔): 笔记


一、基本概念

1.1 程序、进程、线程

  • 程序(program):是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
  • 进程(process):是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期
  1. 如运行中的QQ,运行中的MP3播放器
  2. 程序是静态的,进程是动态的
  3. 进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域

    • 线程(thread):,进程可进一步细化为线程,是一个程序内部的一条执行路径。
  4. 若一个进程同一时间并行执行多个线程,就是支持多线程的
  5. 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
  6. 一个进程中的多个线程共享相同的内存单元/内存地址空间--->它们从同一堆中分配对象,可以 访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资 源可能就会带来安全的隐患。

1.2 并行与并发

  • 并行:许多cpu同时执行多个任务
  • 并发:一个cpu同时执行多个任务

1.3 使用多线程的优点

  1. 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
  1. 提高计算机系统CPU的利用率
  2. 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改

1.4 何时需要多线程

  • 程序需要同时执行两个或多个任务。
  • 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
  • 需要一些后台运行的程序时。

二、线程的创建和使用

1.继承Thread类,然后重写run函数,不需要直接调用他,这个函数是JVM自动调用的。

public class MyThread1Test extends Thread{
    @Override
    public void run() {
        for(int i=0;i<2000;i++) {
            System.out.println("Threa666666666666");
        }
    }
}

2.实现Runnable接口,重写run函数,同理也不需要调用这个run函数,如果调用了系统就会把它当成普通函数。

public class MyRunnableTest implements Runnable{
    @Override
    public void run() {
        // TODO Auto-generated method stub
        for(int j=0;j<2000;j++) {
            System.out.println("Runnable********执行");
        }
    }
}

3.调用上述两个方法(普通外部类),启动多线程

    Thread t1=new MyThread1Test();
    t1.start();//
    Thread t2=new Thread(new MyRunnableTest()); 
    t2.start();

4.通过匿名内部类实现

new Thread() {
            public void run() {
                for(int i=0;i<500;i++) {
                    System.out.println("Threa666666666666");
                }
            }
        }.start();
        
new Thread(new Runnable() {
            
        @Override
        public void run() {
            // TODO Auto-generated method stub
            for(int j=0;j<500;j++) {
                Thread.yield();
                System.out.println("Runnable********执行");
            }
        }
    }).start();

5.通过成员内部类实现

public class th3 extends Thread{
        @Override
        public void run() {
            // TODO Auto-generated method stub
            for(int i=0;i<2000;i++) {
                System.out.println("Threa666666666666");
            }
        }
        
    }//把这个类放在一个类的内部,然后调用,调用过程略,之后直接start就行。

6.注意点:

  • 如果自己手动调用run()方法,那么就只是普通方法,没有启动多线程模式。
  • run()方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU 调度决定。
  • 想要启动多线程,必须调用start方法。
  • 一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出以上 的异常“IllegalThreadStateException”。

7.线程的调度
调度策略

  • 时间片策略
  • 抢占式:高优先级的线程抢占CPU ,可以设置优先级,默认是5,自己可以设置1-10,数字越大优先级越高,但是并不意味着优先级越高就一定会先执行,只是先执行的概率要大一点

Java的调度方法

  • 同优先级线程组成先进先出队列(先到先服务),使用时间片策略
  • 对高优先级,使用优先调度的抢占式策略

8.线程的分类

  • 用户线程
  • 守护线程,(例如java的垃圾回收)是伴随着用户线程的存活而存活 当用用户线程死亡的时候 守护线程不论是否执行结束 都会死亡(不会立即死亡,而是运行一段时间再死亡),调用的时候在start前声明setDaemon(true)
  • 它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。

三、线程的生命周期

  • 新建:当创建了一个线程之后 new Thread 则当前线程就处于新建状态
  • 就绪:当调用了线程的start方法 则线程就进入到就绪状态 准备着与其他线程抢夺CPU的执行权
  • 运行:当线程获得CPU的执行全 处于执行中的线程 就是运行状态
  • 阻塞:线程在执行过程中,因为某些原因 失去了CPU的执行权 暂时被挂起 但是线程还没有结束 则线程就处于阻塞状态
  • 死亡:线程任务执行完成 或者被 强制终止 则线程进入到死亡状态

线程的五种状态时可以相互转换的

四、线程的同步

例如:使用多线程模拟火车站售票程序,开启三个窗口售票。

public class Tick implements Runnable{
    private  static int  num = 100;
    @Override
    public void run() {
         while(true) {
             if(num > 0) {
                 try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                 System.out.println(Thread.currentThread().getName()+"销售车票座位号为:" + num--); 
             }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(); 
    } 
}

备注:上述代码会出现很多问题,比如会出现卖的票数编号重复,甚至出现负数编号的车票,例如当票数只剩一张的时候,一个线程执行,判断票数大于零之后进入了休眠状态,此时二号线程也进入了,因为一号线程休眠中,并未对票数进行修改,所以二号线程判断票数也是大于0,依次类推,当这些线程依次休眠结束后,自然会产生0,-1,-2这些不符合常理的票数编号。

  • 上述问题的原因: 当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有 执行完,另一个线程参与进来执行。导致共享数据的错误。
  • 解决办法: 对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。这就叫线程同步

1.线程同步实现
1.1同步代码块
同步代码块用synchronized这个关键字去实现,注意实现的时候不要和static搭配。同步代码块可以放在静态方法中和非静态方法中。

放在非静态方法中,此时的锁对象可以是任意对象 只需要保证所有的线程使用的是同一个锁对象 this也可以充当锁对象,但是必须提前创建好这个对象,如下所示

//------------------------------------------------------------
public class PrintTest {
    public static void main(String[] args) {
        PrintTest p1=new PrintTest();//如果可以将此行注释掉,然后把下面的两行注释解除,在看看执行效果。
        new Thread() {
            @Override
            public void run() {
            //  PrintTest p1=new PrintTest();//解除此行注释看看效果
            
                p1.print1();
            }
        }.start();
        //----------------------------
        new Thread() {
            @Override
            public void run() {
            //  PrintTest p2=new PrintTest();////解除此行注释看看效果,但要把下面的p1换成p2.
                p1.print2();
            }
        }.start();
    }
    //----------------------------------
    public  void print1() {
        while(true) {
            synchronized(this) {
        System.out.print("中");
        System.out.print("国");
        System.out.print("科");
        System.out.print("技");
        System.out.print("大");
        System.out.print("学");
        System.out.println();
        }
        }
    }
    //------------------------------------
    public  void print2() {
        while(true) {
        synchronized(this) {
        System.out.print("北");
        System.out.print("大");
        System.out.print("青");
        System.out.print("鸟");
        System.out.println();
    }
    }
    }
}

静态代码块放在静态方法中 可以使用静态对象来作为锁对象,例如字符串,本类的class对象,保证所有的线程使用的是同一个锁对象

public class PrintTest {
    public static void main(String[] args) {
        new Thread() {
            @Override
            public void run() {
                print1();
            }
        }.start();
        new Thread() {
            @Override
            public void run() {
                
                print2();
            }
        }.start();
    }
    
    
    public  static void print1() {
        while(true) {
            synchronized(PrintTest.class) {
        System.out.print("中");
        System.out.print("国");
        System.out.print("科");
        System.out.print("技");
        System.out.print("大");
        System.out.print("学");
        System.out.println();
        }
        }
    }
    
    public static void print2() {
        while(true) {
        synchronized(PrintTest.class) {
        System.out.print("北");
        System.out.print("大");
        System.out.print("青");
        System.out.print("鸟");
        System.out.println();
    }
    }
    }
}

对于非静态同步方法 锁对象为this,可以通过下面的同步代码块和非静态同步方法证明

public class PrintTest {
    
    public static void main(String[] args) {
        PrintTest p1=new PrintTest();
        new Thread() {
            @Override
            public void run() {
                p1.print1();
            }
        }.start();
        new Thread() {
            @Override
            public void run() {
                while(true) {
                p1.print2();
                }
            }
        }.start();
    }
    
//处于非静态方法中的同步代码块    
    public  void print1() {
        while(true) {
            synchronized(this) {
        System.out.print("中");
        System.out.print("国");
        System.out.print("科");
        System.out.print("技");
        System.out.print("大");
        System.out.print("学");
        System.out.println();
        }
        }
    }
    //非静态同步方法
    public synchronized void print2() {
        System.out.print("北");
        System.out.print("大");
        System.out.print("青");
        System.out.print("鸟");
        System.out.println();
    }
    
}

对于静态同步方法 锁对象为字节码对象 ,本类的class对象,可以通过下面来验证:

public class PrintTest02 {
    
    

    public static void main(String[] args) {
        PrintTest02 p2=new PrintTest02();
        Thread t1=new Thread() {
            @Override
            public void run() {
                while(true) {
                print1();
                }
            }
        };
        Thread t2=new Thread() {
            public void run() {
                while(true) {
                p2.print2();
            }
            }
        };
        
        t2.start();
        t1.start();

    }
    //静态同步方法
    public static synchronized void print1() {
        System.out.print("中");
        System.out.print("国");
        System.out.print("科");
        System.out.print("技");
        System.out.print("大");
        System.out.print("学");
        System.out.println();   
    }
    //下面这个函数加不加staic结果都一样
    public   void print2() {
        while(true) {
        synchronized(PrintTest02.class) {
        System.out.print("北");
        System.out.print("大");
        System.out.print("青");
        System.out.print("鸟");
        System.out.println();
    }
    }
    }
    }

猜你喜欢

转载自www.cnblogs.com/starstrrys/p/11951854.html
今日推荐