zoukankan      html  css  js  c++  java
  • 基础学习day11--多线程一线程的创建,运行,同步和锁

    一、线程基本概述

    1.1、进程和线程

    进程:一个应用程序一般都是一个进程,正在进行的程序

    每一个进程最少都有一个线程,都有一个执行顺序,该顺序是一个执行路径或者一个控制单元

    线程:进程中一个独立的控制单元,线程控制着进程的执行。

    windows中的任务管理器,可以查看进程,linux下通过ps命令

    线程是进程的最小单位

    线程依赖于进程

    线程随着进程的创建和创建,随着进程的结束而消亡

    如迅雷:可以同时开启多个下载,就是多线程

    多个程序同时执行,时CPU在很快的切换,看上去是同时执行,实际上是在CPU在切换执行。

    多线程存在的意义:可以让程序中的内容同时执行。

    二、继承线程的创建

    2.1、继承Thread,重写run方法

    package com.pb.thread.demo1;
    
    
    
    /**
     * 
     * @author Denny
     * 继承Thread 类并重写run方法来创建线程类
     *
     */
    public class MyThread extends Thread {
        
        private int count;
        
        /*
         * 重写run 方法
         * @see java.lang.Thread#run()
         */
        @Override
        public void run(){
            /*
             * 要运行的代码块或者方法,在这里调用,或者直接写在这里
             */
            while(count<10){
            count++;
            System.out.println("My thread run方法启动了"+count);
            }
        }
       public static void main(String[] args) {
           //声明线程类
           MyThread mt=new MyThread();
           //启动线程,JVM调用run方法
           mt.start();
           //主线程
           for(int x=0;x<10;x++){
              System.out.println("main thread"+x);
           }
    }
        
        
    }

    多个线程都在抢占CPU的执行权,谁抢到,谁就执行

    在某一时刻,只能有一个程序在运行

    CPU在做着快速切换,以达到看上去是同时运行的效果。

    多线程一个特性:随机性,谁抢到谁执行.

     

    三、线程的运行和线程状态

    3.1、调用start()方法

    使该线程开始执行,Java虚拟机调用该线程的run()方法

    为什么要重写run方法.

    Thread类用于描述线程。

    这个类定义一个功能,用于存储线程要运行的代码。该存储功能就是run方法.

    也就是说,Thread类中的run方法是用于存储线程要运行的代码

    如果直接调用run()方法,就是调用对象的普通方法一样.,仅仅是对象调用方法.线程创建了并没有运行。

    只有start()才可以运行线程。

    3.2、线程状态

    新生:创建线程 new Thread()或者子类

    运行:正在运行的线程,调用start()方法

    冻结:已经创建的线程,但非运行状态的线程。sleep(long 毫秒), wait()线程等待,直到notify()唤醒才可以继续运行

    临时状态阻塞:具备运行资格,但没有执行权,就是还没有被CPU切换到.

    消亡:线程死亡stop(),或者线程运行完成。

    四、获取线程对象

    4.1、获取当前线程对象和名称

    继承thread类实现线程,局部变量都有单独的一份不能共享数据

    package com.pb.thread.demo1;
    
    
    
    /**
     * 
     * @author Denny
     * 继承Thread 类并重写run方法来创建线程类
     *Thread.currentThread(),获取当前线程对象
     *继承thread类实现线程,局部变量都有单独的一份不能共享数据
     */
    public class MyThread extends Thread {
        
        public MyThread(String name){
            super(name);
        }
        /*
         * 重写run 方法
         * @see java.lang.Thread#run()
         */
        @Override
        public void run(){
            /*
             * 要运行的代码块或者方法,在这里调用,或者直接写在这里
             */
            for(int x=0;x<10;x++){
            
            System.out.println("My thread run的名字:"+Thread.currentThread().getName()+","+x);
            }
        }
       public static void main(String[] args) {
           //声明线程类
           MyThread mt1=new MyThread("mt1");
          // mt1.setName("张三"); 设置线程名称
           MyThread mt2=new MyThread("mt2");
           //启动线程,JVM调用run方法
           mt1.start();
           mt2.start();
           //主线程
           for(int x=0;x<10;x++){
              System.out.println(currentThread().getName()+","+x);
           }
    }
        
        
    }

    示例:

    五、接口实现线程

    5.1、实现Runnable接口

    重写run方法()

    实现接口的方式最大的好处是可以共享数据

    示例:多窗口同时售票,

    package com.pb.thread.demo1;
    
    /**
     * 
     * @author Administrator 多个窗口同时卖票
     * 
     */
    public class Ticket implements Runnable {
    
        private  int tick = 100;
    
        public void run() {
    
            while (true) {
                if (tick > 0) {
                    System.out.println(Thread.currentThread().getName()+"卖:" + tick--);
                }else{
                    break;
                }
            }
        }
        public static void main(String[] args) {
            //声明线程类
            Ticket ticket=new Ticket();
            //创建线程对象,并将类做为参数
            Thread t1=new Thread(ticket);
            t1.setName("一号窗口,");
            Thread t2=new Thread(ticket);
            t2.setName("二号窗口,");
            Thread t3=new Thread(ticket);
            t3.setName("三号窗口,");
            Thread t4=new Thread(ticket);
            t4.setName("四号窗口,");
            t1.start();
            t2.start();
            t3.start();
            t4.start();
            
            
        }
    
    }

    结果:

      1 一号窗口,卖:100
      2 一号窗口,卖:98
      3 一号窗口,卖:97
      4 一号窗口,卖:96
      5 一号窗口,卖:95
      6 一号窗口,卖:94
      7 二号窗口,卖:99
      8 二号窗口,卖:92
      9 一号窗口,卖:93
     10 一号窗口,卖:88
     11 一号窗口,卖:87
     12 一号窗口,卖:86
     13 二号窗口,卖:89
     14 二号窗口,卖:84
     15 二号窗口,卖:83
     16 二号窗口,卖:82
     17 二号窗口,卖:81
     18 二号窗口,卖:80
     19 二号窗口,卖:79
     20 二号窗口,卖:78
     21 二号窗口,卖:77
     22 二号窗口,卖:76
     23 二号窗口,卖:75
     24 二号窗口,卖:74
     25 二号窗口,卖:73
     26 二号窗口,卖:72
     27 二号窗口,卖:71
     28 二号窗口,卖:70
     29 二号窗口,卖:69
     30 二号窗口,卖:68
     31 二号窗口,卖:67
     32 二号窗口,卖:66
     33 二号窗口,卖:65
     34 二号窗口,卖:64
     35 二号窗口,卖:63
     36 二号窗口,卖:62
     37 二号窗口,卖:61
     38 二号窗口,卖:60
     39 二号窗口,卖:59
     40 二号窗口,卖:58
     41 二号窗口,卖:57
     42 二号窗口,卖:56
     43 二号窗口,卖:55
     44 二号窗口,卖:54
     45 二号窗口,卖:53
     46 二号窗口,卖:52
     47 二号窗口,卖:51
     48 二号窗口,卖:50
     49 二号窗口,卖:49
     50 二号窗口,卖:48
     51 二号窗口,卖:47
     52 二号窗口,卖:46
     53 二号窗口,卖:45
     54 二号窗口,卖:44
     55 二号窗口,卖:43
     56 二号窗口,卖:42
     57 二号窗口,卖:41
     58 二号窗口,卖:40
     59 二号窗口,卖:39
     60 二号窗口,卖:38
     61 二号窗口,卖:37
     62 二号窗口,卖:36
     63 二号窗口,卖:35
     64 二号窗口,卖:34
     65 二号窗口,卖:33
     66 二号窗口,卖:32
     67 二号窗口,卖:31
     68 二号窗口,卖:30
     69 二号窗口,卖:29
     70 二号窗口,卖:28
     71 二号窗口,卖:27
     72 二号窗口,卖:26
     73 二号窗口,卖:25
     74 二号窗口,卖:24
     75 二号窗口,卖:23
     76 二号窗口,卖:22
     77 二号窗口,卖:21
     78 三号窗口,卖:90
     79 四号窗口,卖:91
     80 三号窗口,卖:19
     81 二号窗口,卖:20
     82 一号窗口,卖:85
     83 二号窗口,卖:16
     84 三号窗口,卖:17
     85 四号窗口,卖:18
     86 三号窗口,卖:13
     87 三号窗口,卖:11
     88 三号窗口,卖:10
     89 三号窗口,卖:9
     90 三号窗口,卖:8
     91 三号窗口,卖:7
     92 三号窗口,卖:6
     93 三号窗口,卖:5
     94 三号窗口,卖:4
     95 三号窗口,卖:3
     96 三号窗口,卖:2
     97 三号窗口,卖:1
     98 二号窗口,卖:14
     99 一号窗口,卖:15
    100 四号窗口,卖:12

    两种创建线程方式:

      优点    缺点
    继承Thread类

     1.编写简单  

    2.可以使用this关键字直接访问当前线程

    无法继承其它类

    无法实现数据共享

    实现Runnable接口

    1.可以继承其它类 

    2.多个线程之间可以使用同一个Runnable对象

     3.可以共享数据

    编程方式稍微复杂,如需访问当前线程,需要调用Thread类的currentThread()方法                                                                                                              

                                       
                                     

    六、线程安全

    6.1、还是上面的例子

    加上sleep(1000)睡觉1秒

    package com.day10.thread.demo1;
    
    public class Ticket implements Runnable {
        private int num=100;
    
        @Override
        public void run() {
          
            while(true){
                if(num>0){
                    try {
                        Thread.sleep(1000);  //当前线程睡觉1秒,毫秒单位
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+",卖出"+num--+"号票");
                }
            }
        }
        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);
            Thread t4=new Thread(t);
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    
    }

    结果:

    Picked up JAVA_TOOL_OPTIONS: -javaagent:/usr/share/java/jayatanaag.jar 
    Thread-0,卖出100号票
    Thread-2,卖出99号票
    Thread-1,卖出98号票
    Thread-3,卖出97号票
    Thread-0,卖出96号票
    Thread-2,卖出95号票
    Thread-1,卖出94号票
    Thread-3,卖出93号票
    Thread-0,卖出92号票
    Thread-2,卖出91号票
    Thread-1,卖出90号票
    Thread-3,卖出89号票
    Thread-0,卖出88号票
    Thread-2,卖出87号票
    Thread-1,卖出86号票
    Thread-3,卖出85号票
    Thread-0,卖出84号票
    Thread-2,卖出83号票
    Thread-1,卖出82号票
    Thread-3,卖出81号票
    Thread-0,卖出80号票
    Thread-2,卖出79号票
    Thread-1,卖出78号票
    Thread-3,卖出77号票
    Thread-0,卖出76号票
    Thread-2,卖出75号票
    Thread-1,卖出74号票
    Thread-3,卖出73号票
    Thread-0,卖出72号票
    Thread-2,卖出71号票
    Thread-1,卖出70号票
    Thread-3,卖出69号票
    Thread-0,卖出68号票
    Thread-2,卖出67号票
    Thread-1,卖出66号票
    Thread-3,卖出65号票
    Thread-0,卖出64号票
    Thread-2,卖出63号票
    Thread-1,卖出62号票
    Thread-3,卖出61号票
    Thread-0,卖出60号票
    Thread-2,卖出59号票
    Thread-1,卖出58号票
    Thread-3,卖出57号票
    Thread-0,卖出56号票
    Thread-2,卖出55号票
    Thread-1,卖出54号票
    Thread-3,卖出53号票
    Thread-0,卖出52号票
    Thread-2,卖出51号票
    Thread-1,卖出50号票
    Thread-3,卖出49号票
    Thread-0,卖出48号票
    Thread-2,卖出47号票
    Thread-1,卖出46号票
    Thread-3,卖出45号票
    Thread-0,卖出44号票
    Thread-2,卖出43号票
    Thread-1,卖出42号票
    Thread-3,卖出41号票
    Thread-0,卖出40号票
    Thread-2,卖出39号票
    Thread-1,卖出38号票
    Thread-3,卖出37号票
    Thread-0,卖出36号票
    Thread-2,卖出35号票
    Thread-1,卖出34号票
    Thread-3,卖出33号票
    Thread-0,卖出32号票
    Thread-2,卖出31号票
    Thread-1,卖出30号票
    Thread-3,卖出29号票
    Thread-0,卖出28号票
    Thread-2,卖出27号票
    Thread-1,卖出26号票
    Thread-3,卖出25号票
    Thread-0,卖出24号票
    Thread-2,卖出23号票
    Thread-1,卖出22号票
    Thread-3,卖出21号票
    Thread-0,卖出20号票
    Thread-2,卖出19号票
    Thread-1,卖出18号票
    Thread-3,卖出17号票
    Thread-0,卖出16号票
    Thread-2,卖出15号票
    Thread-1,卖出14号票
    Thread-3,卖出13号票
    Thread-0,卖出12号票
    Thread-2,卖出11号票
    Thread-1,卖出10号票
    Thread-3,卖出9号票
    Thread-0,卖出8号票
    Thread-2,卖出7号票
    Thread-1,卖出6号票
    Thread-3,卖出5号票
    Thread-0,卖出4号票
    Thread-2,卖出3号票
    Thread-1,卖出2号票
    Thread-3,卖出1号票
    Thread-0,卖出0号票
    Thread-2,卖出-1号票
    Thread-1,卖出-2号票

    发现多卖票了,

    为什么?因为卖票当前线程睡眠了1秒,其它线程就可以把这张票已经卖出了,

    条件检查1>0,如果4个人同时检查,条件成立,就继续卖票就出现了,负数

    怎么解决?给线程加锁

    线程安全问题产生的原因:

        1. 多个线程在操作共享的数据。
        2. 操作共享数据的线程代码有多条。
        当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生

    解决方式:就是将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程不可以参与运算。必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。

    看下面的同步synchronized

    七、同步代码块

    7.1、synchronized

    同步代码块的格式:
        synchronized(对象){
               需要被同步的代码;
        } 
        同步的好处:解决了线程的安全问题。
        同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
        同步的前提:必须有多个线程并使用同一个锁。

    修改上面的代码

    package com.day10.thread.demo1;
    
    public class Ticket implements Runnable {
        private int num=100;
    
        @Override
        public void run() {
          
            while(true){
                synchronized(this){
                if(num>0){
                    try {
                        Thread.sleep(1000);  //当前线程睡觉1秒,毫秒单位
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+",卖出"+num--+"号票");
                }else{
                    break;
                }
                }
            }
        }
        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);
            Thread t4=new Thread(t);
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    
    }

    八、同步函数 

    8.1、同步方法

    package com.day10.thread.demo2;
    
    public class Account {
        
        //余额
        private double blance=500;
        
        
        //取款
        public void withDraw(int num){
            
            blance=blance-num; 
            
        }
    
        public double getBlance() {
            return blance;
        }
    
        public void setBlance(double blance) {
            this.blance = blance;
        }
    
        
        
    }
    package com.day10.thread.demo2;
    
    public class AccountThread implements Runnable {
    
        private Account account=new Account();
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                if(account.getBlance()<0){
                    System.out.println("透支了!!");
                }
                makWithDraw(100); //每次取100
            }
    
        }
        
        /*
         * 取款的方法
         */
        public void makWithDraw(int num){
            //判断余额是不是大于要取款的数
            if(account.getBlance()>=num){
                System.out.println(Thread.currentThread().getName()+":准备取款!");
                try {
                    Thread.sleep(1000); //等待1秒
                    account.withDraw(num);
                    System.out.println(Thread.currentThread().getName()+":取款完成!,当前余额为:"+account.getBlance());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                
                
            }else{
                System.out.println(Thread.currentThread().getName()+"  余额不足以支付当前取款, 余额为:  "+account.getBlance());
            }
        }
    
    }
    package com.day10.thread.demo2;
    
    public class Demo {
    
        public static void main(String[] args) {
            
            AccountThread at=new AccountThread();
            Thread t1=new Thread(at);
            t1.setName("张三");
            Thread t2=new Thread(at);
            t2.setName("张三老婆");
            t1.start();
            t2.start();
        }
    
    }

    结果:

    
    张三:准备取款!
    张三老婆:准备取款!
    张三:取款完成!,当前余额为:400.0
    张三:准备取款!
    张三老婆:取款完成!,当前余额为:300.0
    张三老婆:准备取款!
    张三:取款完成!,当前余额为:200.0
    张三:准备取款!
    张三老婆:取款完成!,当前余额为:100.0
    张三老婆:准备取款!
    张三:取款完成!,当前余额为:0.0
    张三  余额不足以支付当前取款, 余额为:  0.0
    张三  余额不足以支付当前取款, 余额为:  0.0
    张三老婆:取款完成!,当前余额为:-100.0
    透支了!!
    张三老婆  余额不足以支付当前取款, 余额为:  -100.0
    透支了!!
    张三老婆  余额不足以支付当前取款, 余额为:  -100.0

    加上同步关键字

    /*
         * 取款的方法
         */
        public  synchronized void makWithDraw(int num){
            //判断余额是不是大于要取款的数
            if(account.getBlance()>=num){
                System.out.println(Thread.currentThread().getName()+":准备取款!");
                try {
                    Thread.sleep(1000); //等待1秒
                    account.withDraw(num);
                    System.out.println(Thread.currentThread().getName()+":取款完成!,当前余额为:"+account.getBlance());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                
                
            }else{
                System.out.println(Thread.currentThread().getName()+"  余额不足以支付当前取款, 余额为:  "+account.getBlance());
            }
        }

    结果:

    张三:准备取款!
    张三:取款完成!,当前余额为:400.0
    张三老婆:准备取款!
    张三老婆:取款完成!,当前余额为:300.0
    张三:准备取款!
    张三:取款完成!,当前余额为:200.0
    张三:准备取款!
    张三:取款完成!,当前余额为:100.0
    张三:准备取款!
    张三:取款完成!,当前余额为:0.0
    张三  余额不足以支付当前取款, 余额为:  0.0
    张三老婆  余额不足以支付当前取款, 余额为:  0.0
    张三老婆  余额不足以支付当前取款, 余额为:  0.0
    张三老婆  余额不足以支付当前取款, 余额为:  0.0
    张三老婆  余额不足以支付当前取款, 余额为:  0.0

    没有发生透支现象

    同步代码块:

    /*
         * 取款的方法
         */
        public   void makWithDraw(int num){
            synchronized(account){
            //判断余额是不是大于要取款的数
            if(account.getBlance()>=num){
                System.out.println(Thread.currentThread().getName()+":准备取款!");
                try {
                    Thread.sleep(1000); //等待1秒
                    account.withDraw(num);
                    System.out.println(Thread.currentThread().getName()+":取款完成!,当前余额为:"+account.getBlance());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                
                
            }else{
                System.out.println(Thread.currentThread().getName()+"  余额不足以支付当前取款, 余额为:  "+account.getBlance());
            }
            }
        }

    结果同上,没有发生透支现象

    同步函数和同步代码块的区别:
        1. 同步函数的锁是固定的this。
        2. 同步代码块的锁是任意的对象。
        建议使用同步代码块。
        由于同步函数的锁是固定的this,同步代码块的锁是任意的对象,那么如果同步函数和同步代码块都使用this作为锁,就可以实现同步。

    九、同步锁

    9.1、同锁

     

    同步方法同步对象是this,同步代码块也可以使用this来同步

    十、静态同步

    10.1、静态同步

    不是this,因为静态方法中也不可以定义this

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

    静态的同步函数使用的锁是该函数所属字节码文件对象,可以用getClass方法获取,也可以用当前类名.class表示。

    synchronizid(类名.class){  或者对象.getClass

    }

    十一、单例模式加上同步懒汉式

    11.1、懒汉式加锁

    package com.day10.thread.demo2;
    /**
     * 
     * @author denny
     *单例模式
     *饿汉式,不存在复生成对象的问题
     *懒汉工,加synchronize
     */
    public class SingleDemo {
        private static SingleDemo singleDemo;
        
        private SingleDemo(){
            
        }
        public static SingleDemo getNewIntance(){
            if(singleDemo==null){//判断是不是空,不是空就直接返回,是空就加锁
                synchronized(SingleDemo.class){  //加锁 //this.getClass() 直接写对象singleDemo.getClass()也行
                    
                if(singleDemo==null){
                    singleDemo=new SingleDemo();
                }
                }
            }
            return singleDemo;
            
        }
    
    }

     

    十二、死锁

    12.1、死锁常见情景之一:同步的嵌套。

     class MyLock{
         static  Object lockA=new Object();
         static   Object lockB=new Object();
    }
    
    public class Demo implements Runnable {
    
        private boolean flag;
    
        public Demo(boolean flag) {
            this.flag = flag;
        }
    
        @Override
        public void run() {
            
                if (flag) {
                    while (true) {
                    synchronized (MyLock.lockA) {
                        System.out.println("if.........lockA");
                        synchronized (MyLock.lockB) {
                            System.out.println("if.........lockB");
                        }
                    }
                }
                }
                 else {
                     while(true){
                    synchronized (MyLock.lockB) {
                        System.out.println("else.........lockB");
                        synchronized (MyLock.lockA) {
                            System.out.println("else.........lockA");
                        }
                    }
                    }
    
                }
            }
    
    
    
        public static void main(String[] args) {
            Thread t1 = new Thread(new Demo(true));
            Thread t2 = new Thread(new Demo(false));
            t1.start();
            t2.start();
        }
    
    }

     建议将要锁的对象,声明为static

  • 相关阅读:
    蓝桥杯真题(1)
    蓝桥杯------历届真题
    第三届蓝桥杯 c/c++真题
    数据库三大范式个人理解,书上讲的太抽象
    c#线程池ThreadPool实例详解
    c#多线程thread实例详解
    C#中异步使用及回调
    c# 多线程的几种方式
    C#中委托的同步和异步有什么区别
    C#设计模式——单例模式的实现
  • 原文地址:https://www.cnblogs.com/liunanjava/p/4805800.html
Copyright © 2011-2022 走看看