zoukankan      html  css  js  c++  java
  • 线程笔记

    基本概念

    1.什么是进程?

      一个进程对应一个应用程序,例如,在Windows下启动一个word,在java开发环境下启动JVM,就表示启动了一个进程。现代计算机都是支持多进程的,在一个操作系统下可以同时启动多个进程。

    2.多进程有什么用?

      单进程计算机只能做一件事

      一边玩游戏(游戏进程)一边听歌(音乐进程),对于单核计算机,游戏进程和音乐进程是同时运行的吗?不是。因为CPU在某一个时间点上只能做一件事情,计算机在两个进程间频繁的切换执行,切换速度极快,让人感觉是在同时运行。

      多进程不是提高执行速度,而是提高CPU的使用率。

      进程和进程间的内存是独立的

    3.什么是线程?

      线程是一个进程中的执行场景,一个进程可以启动多个线程。

      多线程不是为了提高执行速度,是为了提高应用程序的使用率

      线程和线程共享“堆内存和方法区内存”,栈内存是独立的,一个线程一个栈

    4.java程序的运行原理?

      java命令会启动java虚拟机,等同于启动了一个应用程序,表示启动了一个进程。该进程会自动启动一个主线程,然后主线程会自动调用某个类的main方法,所以main方法运行在主线程中

    线程的创建和启动

    1.第一种方式

      继承Thread——>重写run方法

    public class ThreadTest01 {
    
        public static void main(String[] args) {
            Thread t = new Thread01();  //创建线程
            //启动线程
            t.start(); //这段代码执行瞬间结束,告诉JVM再分配一个栈给t线程
                            //run()不需要程序员去调用,线程启动后自动调用run,如果直接调用run则不会开启新的线程
            
            for (int i = 0 ; i < 100 ; i++){
                System.out.println("main--->" + i);
            }
            //在多线程中,main方法结束只是主线程栈中没有方法栈帧了,
            //但是其他线程或者其他栈中可能还有
            //所以,main方法结束,程序可能还在运行
        }
        
    }
    class Thread01 extends Thread{
        @Override
        public void run() {
            for (int i = 0 ; i<100 ; i++){
                System.out.println("run--->" +i);
            }
        }
    }

    一部分结果

    main--->61
    run--->70
    main--->62
    run--->71
    main--->63
    run--->72
    run--->73
    run--->74

    2.第二种方式

      写一个Runnable接口的实现类——>实现run方法

    public class ThreadTest02 {
        public static void main(String[] args) {
            Thread t = new Thread(new Thread02());
            t.start();
        }
    }
    class Thread02 implements Runnable{
        @Override
        public void run() {
            //do something
        }
    }

    推荐第二种方式,因为实现接口还可以保留类的继承,因为java是单继承的

     线程的生命周期

    1.新建。new出线程

    2.就绪。t.start(),该状态表示该线程有权利去获得CPU的时间片,当拿到时间片后就马上执行run方法,这个时候就进入运行状态。

    3.运行。run方法执行。

    4.阻塞。运行中可能遇到了阻塞时间,进入阻塞状态,等到阻塞接触后,会回到就绪状态

    5.消亡。run方法执行结束

     线程的调度与控制

      JVM负责线程的调度,取得CPU的使用权。目前有两种调度模型,分时调度和抢占式调度,java采用抢占式调度。

      分时调度:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片

      抢占式:优先级高的线程获得的CPU的使用权相对多些,如果优先级相同,则随机选一个

    1.优先级

      从1~10,默认5

    2.sleep()

      是一个静态方法,阻塞当前线程,腾出CPU让给其他线程

      这个方法要捕获异常,注意Thread的run方法不抛出异常,所以在复写run方法之后,在run方法的声明位置不能使用throws关键字,所以run中的异常只能try/catch

       如何打断线程的休眠? 

    public class SleepTest {
        
        public static void main(String[] args) throws Exception{
            //依靠异常处理机制来终止了一个线程的休眠
            //启动线程5s后打断休眠
            Thread t = new Thread(new T());
            
            t.start();
            t.setName("t");
            Thread.sleep(5000);
            
            t.interrupt();
            System.out.println("sleep over");
        }
    }
    
    class T implements Runnable{
        public void run(){ 
            try{
                Thread.sleep(100000);
                
                System.out.println("hello world~");
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            
            for (int i=0 ; i < 10 ; i ++){
                System.out.println(Thread.currentThread().getName() + "---->" + i);
            }
        }
    }

    依靠异常处理机制来终止了一个线程的休眠,run里的hello world不会输出,下面的for会正常输出

    一个终止线程的方法?

    public class SleepTest {
        
        public static void main(String[] args) throws Exception{    
            //线程运行5s后终止
            Processor p = new Processor();
            Thread t = new Thread(p);
            t.setName("t");
            t.start();
            Thread.sleep(5000);
            p.run = false;
            System.out.println("main over");
        }
    }
    
    class Processor implements Runnable{
        boolean run = true;
        public void run(){
            for(int i = 0 ; i<10 ; i++){
                if (run){
                    try{
                        Thread.sleep(1000);
                    }catch(Exception e){
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "---->" + i);
                }else {
                    return;
                }
            }
            System.out.println("for over");
        }
    }
    3.yield

    静态方法,给同一个优先级的线程让位,但是让位之间不固定,和sleep不同的是不能跟指定时间。

    4.join

    成员方法,线程的合并。

    public class JoinTest {
        public static void main(String[] args) throws InterruptedException{
            Thread t = new Thread(new JoinThread());
            t.setName("t");
            t.start();
            //合并线程
            t.join(); //t和主线程合并,也就是说两个栈空间变成了一个栈空间,变成单线程了
            
            for (int i = 0 ; i < 10 ; i++){
                System.out.println(Thread.currentThread().getName() + "--->" + i);
            }
        }
    }
    
    class JoinThread implements Runnable{
        @Override
        public void run() {
            for (int i = 0 ; i<5 ; i++){
                try{
                    Thread.sleep(1000);
                }catch (Exception e){
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()    + "--->" + i);
            }
        }
    }

    t--->0
    t--->1
    t--->2
    t--->3
    t--->4
    main--->0
    main--->1
    main--->2
    ……

     线程的同步!!!!!

      关于异步编程模型和同步编程模型的区别

      异步:t1执行t1的,t2执行t2的,两个线程谁也不等谁

      同步:两个线程执行,t1必须等t2执行结束才能执行

    1.为什么要引入线程同步?

      为了数据安全,尽管应用程序的使用率降低,线程同步机制使程序变成了单线程

    2.什么时候要同步?

      1.多线程环境。2.多线程环境共享同一数据。3.共享的数据设计修改操作。<三个条件缺一不可>

    3.synchronized
    synchronized(this){  //括号里面放同步对象
                    //将需要同步的代码放在这里,这块代码只会有一个线程执行
                }

    原理:t1、t2两个线程,当t1遇到synchronized的时候就会去找()里面对象的对象锁,任何一个java对象上都有对象锁,其实就是每个对象都有1和0这个标识,如果找到了则进入代码块执行代码,代码块中代码执行完毕时归还对象锁,如果在还没执行完毕的时候,t2也执行到这里,遇到了这个关键字,但是找不到对象锁,所以只能等待对象锁的归还。

    synchronized加到成员方法上,线程拿走的也是this的对象锁。不过这种写法就意味着整个方法里的代码都需要同步,可能造成不应该同步的代码也同步了,进一步降低了执行效率,所以不如上面那种方式控制得精确。

    public synchronized void fun(){
    
        
    }

    查看源码可以发现StringBuffer的每个方法都有synchronized,所以StringBuffer是线程安全的。Vector,HashTable都是线程安全的。

    类锁:类只有一个,所以类锁也只有一个

    当synchronized添加到静态方法上,线程执行到此方法的时候会找类锁。

    public class ThreadTest03 {
    
        public static void main(String[] args) throws Exception{
            Thread t1 = new Thread(new Thread03());
            Thread t2 = new Thread(new Thread03());
            t1.setName("t1");
            t2.setName("t2");
            
            t1.start();
            //保证t1先执行
            Thread.sleep(1000);
            t2.start();
        }
    }
    
    class Thread03 implements Runnable {
        
        @Override
        public void run() {
            if (Thread.currentThread().getName().equals("t1"))
                MyClass.fun1();  //通过类名直接调用,当然这里如果用对象的引用来调用,还是一样的结果,因为是静态方法
            if (Thread.currentThread().getName().equals("t2"))
                MyClass.fun2();
        }
    }
    
    class MyClass{
        public synchronized static void fun1(){//类锁
            try {
                Thread.sleep(5000);
            } catch (Exception e){
                e.printStackTrace();
            }
            System.out.println("fun1---->");
        }
        
        //没加synchronized,所以不需要类锁,所以t2在执行fun2是不会等fun1的锁(类锁)
        //但是如果加上的话,就需要等t1执行完毕才能执行,因为类锁只有一个!
        public static void fun2() {  
            System.out.println("fun2---->");
        }
    }
    4.死锁

    比如说有一种情况,两个线程t1,t2。两个对象o1,o2。两个线程都要锁这两个对象,t1锁掉o1后,t2把o2锁了,之后t1想继续锁o2,就得等t2归还o2的锁,但是t2也想在锁到o2后继续锁o1,在没锁到o1的的时候是不会交出o2的锁的,于是这个局面就僵持住了。

    public class ThreadTest04 {
    
        public static void main(String[] args) {
            Object o1 = new Object();
            Object o2 = new Object();
            //两个线程共享两个对象
            Thread t1 = new Thread(new Thread05(o1 , o2));
            Thread t2 = new Thread(new Thread06(o1 , o2));
            
            t1.start();
            t2.start();
        }
    }
    
    class Thread05 implements Runnable {
        Object o1 ;
        Object o2 ;
        public Thread05(Object o1 , Object o2){
            this.o1 = o1;
            this.o2 = o2;
        }
        @Override
        public void run() {
            synchronized (o1){
                try {
                    Thread.sleep(1000);  //休眠保证o2被锁到
                } catch (Exception e){
                    e.printStackTrace();
                }
                
                synchronized (o2){
                    System.out.println("no dead~");
                }
            }
        }
    }
    
    class Thread06 implements Runnable {
        Object o1 ;
        Object o2 ;
        public Thread06(Object o1 , Object o2){
            this.o1 = o1;
            this.o2 = o2;
        }
        @Override
        public void run() {
            synchronized (o2){
                try {//这里如果不休眠的话,不能保证一定死锁!
                    Thread.sleep(1000);
                } catch (Exception e){
                    e.printStackTrace();
                } 
                synchronized (o1){
                    System.out.println("no dead~");
                }
            }
        }
    }
    5.举个同步的例子
    public class TicketDemo {
    
        public static void main(String[] args) {
            SaleTicket saleTicket = new SaleTicket();
            Thread t1 = new Thread (saleTicket , "t1");
            Thread t2 = new Thread (saleTicket , "t2");
            
            t1.start();
            t2.start();
        }
    
    }
    
    class SaleTicket implements Runnable {
        int num = 50;
        
        @Override
        public void run() {
            
            while (true){
                try {
                    Thread.sleep(50);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                
                if ( num <= 0)
                    break;
                
                System.out.println(Thread.currentThread().getName() + "---已卖出--->" + num);
                num--;
            }
        }
    }

    简单实现一个卖票的程序,两个线程共享一个资源---票,会出现类似这样的输出

    窗口t2---卖出一张--->26
    窗口t1---卖出一张--->24
    窗口t2---卖出一张--->24
    窗口t1---卖出一张--->22
    窗口t2---卖出一张--->21
    窗口t1---卖出一张--->20

    有两个24显然是不对的,这时就需要将要卖票部分的代码做一个同步

    synchronized (this){
                    if ( num <= 0)
                        break;
                    
                    System.out.println("窗口" + Thread.currentThread().getName() + "---卖出一张--->" + num);
                    num--;
                }

    窗口t1---卖出一张--->7
    窗口t1---卖出一张--->6
    窗口t2---卖出一张--->5
    窗口t2---卖出一张--->4
    窗口t1---卖出一张--->3
    窗口t1---卖出一张--->2
    窗口t2---卖出一张--->1

    这样就正常了,下面换一个Lock的实现方法,lock定义成SaleTicket的成员变量

    lock.lock();
                    if ( num <= 0)
                        break;
                    
                    System.out.println("窗口" + Thread.currentThread().getName() + "---卖出一张--->" + num);
                    num--;
                lock.unlock();

    输出是正常的,但是!!!有一个严重的问题,程序没有终止!!!为什么呢?考虑这样一种情况,t1锁上后开始往下执行,t2在锁那里等着,t1发现num<=0了,跳出循环,锁呢?并没有释放掉,那t2就要等到海枯石烂了。

    所以这里要保证在需要同步的代码块执行完毕之后一定释放掉锁!!!这也是和synchronized的一个区别,synchronized是默认执行完后归还锁,是一种隐式的释放,lock是显示的。所以这种写法是不行的,要用 finally!

    lock.lock();
                try {
                    if ( num <= 0)
                        break;
                    
                    System.out.println("窗口" + Thread.currentThread().getName() + "---卖出一张--->" + num);
                    num--;
                } finally {
                    lock.unlock();
                }

     这样程序就正常结束了

    比较下Lock和synchronized:

    synchronized有两种用法:

    1.同步代码块的形式

    synchronized(对象){ //锁上(隐式)
        //要同步的代码块
    }//释放锁(隐式)

    2.同步方法

    public synchronized void method(){
      //this锁上(隐式)
    //要同步的代码  
    }//this释放(隐式)

     

    守护线程

    线程分两种:用户线程、守护线程。通常用到的就是用户线程,而例如java的垃圾回收器就是一个守护线程。只有全部用户线程都结束了,守护线程才会结束。

    JVM启动一个主线程,还有一个垃圾回收线程,两个不同的栈。

    public class DaemonThread {
        public static void main(String[] args) throws Exception{
            Thread t = new Thread(new Daemon());
            t.setName("t");
            t.setDaemon(true);
            t.start();
            
            for(int i = 0 ; i < 10 ; i++){
                System.out.println(Thread.currentThread().getName() + "--->" + i);
                Thread.sleep(1000);
            }
        }
        
    }
    
    class Daemon implements Runnable{
        
        public void run(){
            int i = 0;
            while(true){
                System.out.println(Thread.currentThread().getName() + "--->" + i++);
                try{
                    Thread.sleep(1000);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    }

    上面代码中,线程t里面的run()是一个无限循环,但是这里将他设置成一个守护线程,而用户线程只有主线程,所以当主线程结束后,守护线程会自动结束,不会无限循环下去。

    定时器

    作用:每隔一段固定的时间执行一段代码

    public class TimerTest {
    
        public static void main(String[] args) throws Exception{
            Timer t = new Timer();
            t.schedule(
                    new LogTask(),
                    new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2016-09-27 21:22:00")
                    , 3*1000);
        }
    }
    
    class LogTask extends TimerTask{
            @Override
            public void run() {
                System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
            }
    }

    2016-09-27 21:22:00
    2016-09-27 21:22:03
    2016-09-27 21:22:06
    2016-09-27 21:22:09

    sleep(),wait(),notify(),yield()总结

    sleep不会归还对象锁,需要捕获异常,可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的线程有执行的机会。

    yield与sleep类似,只是不能指定时间,只能让同优先级的线程有执行的机会

    wait当前线程暂停并释放对象锁,当前线程被放入对象等待池中,当调用notify()方法后,将从对象的等待池中移走一个任意的线程并放到锁标志等待池中,只有锁标志等待池中线程能够获取锁标志。如果锁标志等待池中没有线程,则notify()不起作用。

    wait()和sleep()都可以通过interrupt()方法 打断线程的暂停状态 ,从而使线程立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程。

    Thread类的方法:sleep(),yield()
    Object的方法:wait()和notify()
    wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,否则会抛出异常,而sleep可以在任何地方使用 
    sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常 
     
  • 相关阅读:
    基于Form组件实现的增删改和基于ModelForm实现的增删改
    Git和Github的基本操作
    如果获取的数据不是直接可以展示的结构---三种操作方式
    可迭代对象和迭代器生成器
    django之整体复习
    权限管理之大致流程
    kindedit编辑器和xxs攻击防护(BeautifulSoup)的简单使用
    博客系统之评论树与评论楼相关操作
    中介模型以及优化查询以及CBV模式
    angularjs中ajax请求时传递参数的方法
  • 原文地址:https://www.cnblogs.com/i-love-kobe/p/5904303.html
Copyright © 2011-2022 走看看