Java多线程 -个人笔记

一、程序、进程、线程

        1、程序:为完成特定任务,用某种语言编写的一组指令的集合,是一段静态的代码

        2、进程:是程序的一次执行,正在运行的程序,cpu分配一个进程。进程是资源分配的单位

              在内存中会为每个进程分配不同的内存区域。

               进程具有生命周期,自身的产生,存在和消除的过程

        3、线程:是一个程序内部的一条执行路径

                若一个进程可以同一时间执行多个线程,就是支持多线程的

线程本质上是进程中一段并发运行的代码,所以线程需要操作系统投入CPU资源来运行和调度。

二、并行和并发

        1、并行:多个cpu同时执行多个任务

        2、并发:一个cpu"同时”执行多个任务(采取时间片切换的方式)

三、创建线程的方式(四种)

        方式1、实现java.Thread类,并重写run()方法,在方法内写实现;

使用:在main()方法内创建线程的对象,然后开启线程,thread.start(),然后会自动调用run方法

                直接调用thread.run方法,只会当做是普通的方法调用

        方式2、实现 Runable接口,并重写run()方法,在方法内写实现;

        使用:A、创建接口实现类的对象,

                B、将实现类的对象与Thread类绑定:(这些线程共享同一个实现类类的数据)

                               Thread thread1=new Thread(runableThread)

                                Thread thread2=new Thread(runableThread)

                                     Thread thread3=new Thread(runableThread)

                                   //Thread thread=new Thread(runableThread,"线程名")

            开启多线程        thread1.start() 

                                  thread2.start() 

                                  thread3.start() 

        方式1与2对比,使用方式2较多。        

        原因:java是单继承,继续Thread类之后就不能再继承别的类。

                      方式2的共享能力强一些,不需要一定得static

        方式1、2的缺点:不能返回值,run为void;不能抛出异常

        方式3:使用callable和Future接口创建线程,重写call()方法

                好处:有返回值和能抛出异常

                接口有泛型callable<T>,如果不写,返回类型为Object;写了,就是对应的类型

                步骤:A、写实现类和call的逻辑实现

                           B、开启线程

                                创建实现类对象

                                实现类对象绑定到FutureTask类的对象

                Java5提供了Future接口来代表Callable接口里call()方法的返回值,并且为Future接口提供了一个实现类FutureTask,这个实现类既实现了Future接口,还实现了Runnable接口

                                 将FutuerTaske类的对象绑定到Thead类

                                  然后Thread类的对象开启线程     

                             C、获取线程的方法返回值

                                     FutureTask类的对象.get();

方式四、使用线程池(ThreadPoolExcutor、excetorService)

        前三种的线程如果创建关闭频繁会消耗系统资源影响性能,而使用线程池可以不用线程的时候放回线程池,用的时候再从线程池取,项目开发中主要使用线程池。

4、线程的生命周期

        新生->就绪->运行->消亡

                                阻塞  

四、设置和获取线程的名字

       1、start():开启线程,线程获取cpu资源后调用run方法

        2、run():线程类都要实现这个方法,run方法的内容就是线程要实现的内容

        3、currentThread():是Thread类的一个静态方法。

                Thread.currentThread()  获取当前线程

       4、设置和获取线程的名字

              Thread.setName()

              Thread.getName()

                //通过带参构造器调用父类的构造器,设置线程名

                  public 类型(String Name){super(name)}

        5、setPriority()  设置线程的优先级 ,优先级:1-10,默认是5

                优先级相同,先到先服务;优先级高,被调用的概率就高

                getPriority  获取线程的优先级

        6、join()  

        当一个线程调用了join方法,这个线程就会先别执行,它执行结束以后才可以执行其余的线程

        注意:必须先start,在join

        7、sleep() 让线程阻塞

        8、setDemo() 将进程设置为伴随进程(守护进程),主进程停止的时候,子进程也会停止执行。(必须设置在start方法之前)

             例子:在对应的线程内开启伴随进程

                比如在main线程内开启

                main{

                tt.setDemo(true) //  注意:先将进程设置为伴随进程,再启动

                tt.start()}

        9、stop()  让进程死亡,不推荐使用

五、线程安全

        1、原因:多个线程在争抢资源的过程中,导致共享资源出现问题。一个线程还没执行完,另一个线程就参与进来,开始争抢。(此时,就要进行加锁,并且这个锁必须是共享的,必须唯一)

        2、解决

        A、同步代码块:synchronized() 

           a、 把具有安全隐患的代码锁住,如果锁多了就会效率低

                公共资源放在synchronized(){} 里

           b、认识Synchronized (同步监视器

                1)同步监视器的参数必须是引用数据类型,不能使用简单类型

                2)同步监视器没有什么业务含义,只是一个锁的名字,一般使用共享资源即可

                3)尽量不适用String 和包装类做同步监视器

                4)尽量使用final修饰同步监视器

            c、同步代码块的执行流程

                        线程一找到同步代码块,发现同步代码块的标志位open,则进入执行,然后把标志改为close。如果线程一还没执行完同步代码块就被cpu切换出去,此时进来线程二,线程二发现同步代码块是close,线程就会进入阻塞状态。当线程一再次被cpu调用并执行完同步代码块时,就会将close变为open。当线程二发现同步代码块标志为open,则会进入就绪态,等待被cpu调用执行。

        d、多个代码块被同一个同步监视器锁住时,当其中一个代码块被调用执行,这个代码块就会被锁住,其它被这个锁绑定的代码块也被锁住。

        B、同步方法

              public synchronized add(){}   锁住的是调用这个方法的对象

             static public synchronized add(){} 

        锁住的是调用这个方法的对象所在类的全部对象  即类名.class 字节码信息对象

        C、同步代码块和同步方法的对比

         非静态同步方法的锁是this,锁住一个方法,类内的其它同步方法就会被锁住

        同步代码块只会锁住同步监视器的代码块,其它不会被锁住 

        D、Lock锁

        a、JDK1.5以后,新增的线程同步方式

        synchronized是Java中的关键字,这个关键字的失败是靠JVM来识别完成的,是虚拟机级别

        Lock锁是API级别,提供了相应的接口和对应的实现类(有多个实现类),这个方法更灵活,表现的性能优于synchronized

        实现类 ReentrantLock  (可重入锁)、ReadWriteLock(读写锁)

        读写操作:实现读与读不互斥,读与写和写与写互斥

        synchronized同步代码块或同步方法,被对象调用后 相同同步监视器就会锁上,实现了全部排斥,不能实现读与读不互斥

        collable接口的ReadWriteLock就可以实现这个操作

        condition

       b、代码示例

        Lock lock=new ReetrantLock(); //实现类多,根据需要选择        

       lock.lock()                //使用前打开锁

        //捕捉异常,防止抛出异常而没关闭锁

        try{        

        }catch(){

        }finally{

        lock.unlock(); //关闭锁}

        c、lock和synchronized的区别

        1)lock是显示锁,需要手动开启和关闭,没关闭可能造成死锁现象

             synchronized是隐式锁,同步代码块或同步方法执行完毕后,系统释放对锁的占有

        2)lock是代码锁,synchronized是方法锁和代码锁

        3)使用lock锁,jvm将花费较少的时间来调度线程,性能更好,并且有更好的扩展性(提供更多的子类)

        4) synchronized它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。synchronized是内置的语言实现;synchronized在发生异常时,会自动释放线程占有的锁,,因此不会导致死锁现象发生。Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。

        d、使用优先顺序

        lock>synchronized代码块>synchronized同步方法   

        e、线程同步的缺点

              a、线程安全,效率低

              b、可能造成死锁。出现死锁后,不会出现报错,不会出现异常,只是所有线程处于阻塞状态,无法继续。

六、线程状态切换

        1、线程睡眠    线程类的对象.sleep(1000)  /休眠一秒

        2、线程唤醒   

                noyify()    唤醒当前对象阻塞队列的任意线程

                notifyAll()      唤醒当前对象阻塞队列的所有线程

        3、interrupte()   将调用该线程的中断状态设为true                

              isInterrupted()   判断该线程的中断状态

        4、线程阻塞   wait()

        5、线程合并是优先执行调用该方法的线程,执行完执行其他线程

             join()

         6、线程让步  yield() 可以让当前正在执行的线程暂停,将线程转入就绪队列

                (只有优先级与当前执行线程相同且处于就绪状态才会获得执行机会)

       

七、Condition

        Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set (wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用
        在Condition中,用await()替换wait(),用signal()替换notify(),用signalAll()替换notifyAll(),传统线程的通信方式,Condition都可以实现,这里注意,Condition是被绑定到Lock上的,要创建一个Lock的Condition必须用newCondition()方法。

class BoundedBuffer {
   final Lock lock = new ReentrantLock();//锁对象
   final Condition notFull  = lock.newCondition();//写线程条件 
   final Condition notEmpty = lock.newCondition();//读线程条件 
 
   final Object[] items = new Object[100];//缓存队列
   int putptr/*写索引*/, takeptr/*读索引*/, count/*队列中存在的数据个数*/;
 
   public void put(Object x) throws InterruptedException {
     lock.lock();
     try {
       while (count == items.length)//如果队列满了 
         notFull.await();//阻塞写线程
       items[putptr] = x;//赋值 
       if (++putptr == items.length) putptr = 0;//如果写索引写到队列的最后一个位置了,那么置为0
       ++count;//个数++
       notEmpty.signal();//唤醒读线程
     } finally {
       lock.unlock();
     }
   }
 
   public Object take() throws InterruptedException {
     lock.lock();
     try {
       while (count == 0)//如果队列为空
         notEmpty.await();//阻塞读线程
       Object x = items[takeptr];//取值 
       if (++takeptr == items.length) takeptr = 0;//如果读索引读到队列的最后一个位置了,那么置为0
       --count;//个数--
       notFull.signal();//唤醒写线程
       return x;
     } finally {
       lock.unlock();
     }
   } 
 }

线程同步:就是多个线程同时访问同一资源,必须等一个线程访问结束,才能访问其它资源,比较浪费时间,效率低
线程异步:访问资源时在空闲等待时可以同时访问其他资源

sleep()和wait()的区别

sleep是线程类(Thread)的方法,导致此线程暂停执行指定时间,把执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁。
wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值