java学习之Thread

    xiaoxiao2021-03-25  76

    一、前言

          Thread是线程,要学习线程,必须先知道进程。

          百度进程:进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。

          我理解的进程:进程是电脑或者手机上运行的一个应用。

          百度线程:线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。

          我理解的线程:一个应用程序中的执行路径,或者说是应用中的某一项任务。

    二、主要内容

         1、Thread类的定义有两种方式

               继承Thread类

    public class MyThread extends Thread { @Override public void run() { super.run(); } }

                              实现Runnable接口

    public class MyRunnable implements Runnable { @Override public void run() { } }      2、自定义线程不共享数据与共享数据。

            不共享数据

            给线程定义一个成员变量并初始化,每次创建线程对象,该成员变量都会被初始化,在每个线程中,都是各自的成员变量。

    public class MyThread extends Thread { private int count = 5; public MyThread(String name){ super(); this.setName(name); } @Override public void run() { super.run(); while(count>0){ count--; System.out.println("由"+this.getName()+"该线程计算的count的结果是:"+count); } } }        测试代码 public static void main(String[] args) { MyThread t1 = new MyThread("A"); MyThread t2 = new MyThread("B"); MyThread t3 = new MyThread("C"); MyThread t4 = new MyThread("D"); MyThread t5 = new MyThread("E"); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); }        测试结果 由A该线程计算的count的结果是:4 由B该线程计算的count的结果是:4 由B该线程计算的count的结果是:3 由B该线程计算的count的结果是:2 由B该线程计算的count的结果是:1 由A该线程计算的count的结果是:3 由B该线程计算的count的结果是:0 由A该线程计算的count的结果是:2 由A该线程计算的count的结果是:1 由A该线程计算的count的结果是:0 由C该线程计算的count的结果是:4 由C该线程计算的count的结果是:3 由C该线程计算的count的结果是:2 由E该线程计算的count的结果是:4 由C该线程计算的count的结果是:1 由D该线程计算的count的结果是:4 由D该线程计算的count的结果是:3 由D该线程计算的count的结果是:2 由E该线程计算的count的结果是:3 由D该线程计算的count的结果是:1 由C该线程计算的count的结果是:0 由D该线程计算的count的结果是:0 由E该线程计算的count的结果是:2 由E该线程计算的count的结果是:1 由E该线程计算的count的结果是:0

           共享数据

           给该成员变量加上static的修饰符,则创建的线程都会共用该成员变量,从而达到共享数据的效果 ,但在多线程时,会出现并发的问题(不同的线程在不同的时间访问到的数据是相同)。

    public class MyThreadTwo extends Thread {     private static int count = 5;          public MyThreadTwo(String name){         super();         this.setName(name);     }          @Override     public void run() {         super.run();         count--;         System.out.println("由"+Thread.currentThread().getName()+"线程计算count的结果是:"+count);     } }       测试结果 由B线程计算count的结果是:3 由C线程计算count的结果是:2 由A线程计算count的结果是:3 由E线程计算count的结果是:1 由D线程计算count的结果是:0

         

           还有一种共享数据的方法,在创建线程对象时,使用下面的构造方法

    MyThreadOne mThread = new MyThreadOne(); Thread t1 = new Thread(mThread,"A"); Thread t2 = new Thread(mThread,"B"); Thread t3 = new Thread(mThread,"C"); Thread t4 = new Thread(mThread,"D"); Thread t5 = new Thread(mThread,"E");        MyThreadOne的代码 public class MyThreadOne extends Thread{ private int count = 5; @Override public void run() { super.run(); count--; System.out.println("由"+Thread.currentThread().getName()+"线程计算count的结果是:"+count); } }        测试结果 由A线程计算count的结果是:4 由B线程计算count的结果是:2 由C线程计算count的结果是:2 由D线程计算count的结果是:0 由E线程计算count的结果是:1        同样会出现并发问题

           解决多线程并发问题

           使用synchronized关键字加锁

           使用第一种方式共享数据,解决并发问题,需要使用同步代码块,这里要锁的对象必须是唯一的。

    synchronized (MyThreadTwo.class) { count--; System.out.println("由" + Thread.currentThread().getName() + "线程计算count的结果是:" + count); }         测试结果 由A线程计算count的结果是:4 由E线程计算count的结果是:3 由D线程计算count的结果是:2 由B线程计算count的结果是:1 由C线程计算count的结果是:0         使用第二种方式共享数,解决并发问题,可以在run方法前面加上synchronized关键字进行加锁操作。 @Override synchronized public void run() { super.run(); count--; System.out.println("由"+Thread.currentThread().getName()+"线程计算count的结果是:"+count); }        3、线程的几个常用的方法

              3.1、静态方法currentThread()。

                      获取当前程序所在的线程对象。

              3.2、isAlive()

                      判断线程是否是活跃状态。

                      活动状态:已经启动且尚未结束。

              3.3、静态方法sleep(long time).

                      让当前运行的线程休眠time毫秒

              3.4、getId()

                      获取线程的唯一标识。

            4、停止线程

                 4.1、停止线程的几种方式    

                      1、线程正常停止,即run()方法中的程序执行完毕。 

                      2、使用stop()方法,强制线程停止,但不安全,不推荐使用。

                          使用stop()停止线程的弊端:

                          (1)、可能使一些释放资源的 工作得不到完成 。

                          (2)、对锁的对象解锁,导致数据得不到同步的处理。

                      3、使用interrupt()方法中断线程。

                         当使用该方法停止线程时,如果进入了停止状态,可以抛一个InterruptedException异常,达到停止线程的目的。

                         当线程在休眠状态时,调用该方法,会进入catch语句,并会清除停止状态。

                         当线程在运行状态时,调用该方法,是不会进入catch语句。

                4.2、判断线程是否是停止状态

                    interrupted()线程的静态方法,测试当前线程是否已经中断,线程的中断状态由该方法清除。

                    isInterrupted(),测试线程是否已经中断,不清除中断状态。

      5、线程的暂停与恢复(已过时,不推荐使用)               5.1、暂停线程的方法 suspend();               5.2、使用它的弊端有,当线程暂停时,是不释放锁的,导致别的线程不能访问公共同步对象;还有容易造成线程不同步的现象。          6、线程礼让              6.1、礼让线程的方法 yield();主动放弃CPU资源,让其它线程得以运行。          7、线程的优先级              7.1、设置线程优先级的方法 setPriority(int priority);              7.2、获取线程优先级的方法 getPriority();              7.3、线程优先级的取值范围:1~10;              7.4、线程优先级常量:                      最低优先级:Thread.MIN_PRIORITY 1                      常规优先级:Thread.NORM_PRIORITY 5                      最高优先级:Thread.MAX_PRIORITY 10              7.5、在A线程里启动B线程,则B线程的优先级与A线程的优先级相同;              7.6、线程的默认优先级是Thread.NORM_PRIORITY;              7.7、优先级具体规则性:CPU尽量的把资源分配给优先级高的线程。              7.8、优先级具有随机性:并不一定是优先级高的线程先执行完。

           8、守护线程

                8.1、调用线程的serDecmon(boolean b)方法,可以设置该线程是不是守护线程。

                8.2、当进行中不存在非守护线程时,守护线程也将销毁。

           9、给方法加关键字synchronized同步

              9.1、方法中的私有变量是不存在线程安全问题

              9.2、多个线程访问同一对象的成员变量才会出现线程安全问题

              9.3、在方法加锁,锁的是对象,所以不同线程访问不同对象的成员变量,仍然是异步。

              9.4、A线程已经持有object对象的锁了,B线程可以调用object对象中的非同步方法。

              9.5、当线程已经持有object对象的锁了,可以继续调用该对象中的其他同步方法。

              9.6、当一个线程持有object对象的锁时,另外一线程想访问该对象的另外一个同步方法时,必须等待第一个线程释放锁之后,才能访问。

              9.7、当线程已经持有object对象的锁时,再访问其他该对象的同步方法时,是不需要等待该对象释放锁的。

              9.8、当一个线程执行代码发生异常时,它所持有的锁也会释放掉。

              9.9、同步不具有继承性的,如果子类需要同步,必须自己加上synchronized关键字。

            10、使用关键字synchronized同步代码块

              10.1、同步方法的缺点:当方法有耗时操作时,其它线程必须等待当前线程执行完毕之后释放锁之后才能执行,比较浪费时间。

              10.2、在synchronized(){}内的代码同步,其它的就不同步。

              10.3、使用同一对象的锁,它们之间是同步的。

              10.4、使用synchronized(this)时,锁对象是当前对象。

              10.5、锁对象不同,代码块之间是异步的。

              10.6、同步代码块在非同步方法中调用,并不能保证调用非同步方法的线程同步。

              10.7、当关键字synchronized加在静态方法上的时候,锁对象是该类所对应的Class类。

              10.8、使用synchronized(MyObject.class){}同步代码块,与使用关键字synchronized同步静态方法是一样的。

              10.9、尽量避免使用String的对象加锁,因为String有个常量池功能,String str1="A",String str2 = "A"。str1与str2是同一对象。

              10.10、互相等待对方释放锁,就有可能出现死锁情况。

             11、关键字volatile

               11.1、关键字volatile主要作用是使变量在多线程之间可见。没用关键字volatile修饰之前,变量是在私有堆栈与公有堆栈中,但是它们之间是不同步的;使用了关键字volatile修饰,程序会强制从公有堆栈中取值。

               11.2、使用关键字volatile的缺点是:不支持原子性。

               11.3、线程安全包含原子性和可见性。

               11.4、使用原子类进行原则操作,AtomicInteger、AtomicLong...

              12、线程间通信。

                12.1、等待通知机制,wait()方法与notify()方法只能在同步方法或同步代码块中调用,wait()方法调用之后会立即释放锁,notify()方法调用之后不会立即释放锁 。

                12.2、线程的生命周期,就绪状态、可运行状态、运行状态、暂停状态、销毁状态;就绪状态:线程创建之后进入就绪状态;可运行状态:线程调用了start()方法,但未抢到CPU资源进入可运行状态;运行状态:线程在可运行状态下,抢到了CPU资源进入运行状态;暂停状态:线程在运行状态下,调用了suspend()/sleep()/wait()方法进入暂停状态;销毁状态:当线程运行完run()方法中的代码后进入销毁状态。

                12.3、锁对象有两个队列,就绪队列与阻塞队列;线程调用了wait()方法,进入阻塞队列,线程被唤醒后,进入就绪队列;就绪队列中存放了将要获得锁的线程,阻塞队列存放了被阻塞的线程。

                12.4、唤醒所有线程notifyAll();等待N毫秒之后自动唤醒方法wait(long N),当然也可以在这之前调用notify()或notifyAll()方法唤醒。

                12.5、wait()条件发生变化时,应该将if判断改为while判断,再判断一次,看条件是否发生变化。

                12.6、多个生产者或者多个消费者,出现“假死”状态(不停的唤醒同类),将notity()方法改为notifyAll()。

                12.7、可以通过管道实现线程间通信;

                         字节流:

                         PipedInputStream pis = new PipedInputStream();

                         PipedOutputStream pos = new PipedOutputStream();

                         通过pis.connect(pos)或者pos.connect(pis)连接通道;

                         字符流:

                         PipedReader pr = new PipedReader();

                         PipedWriter pw = new PipedWriter();

                         连接pr.connect(pw)或者pw.connect(pr);

                         将这两个流分别传入两个线程中,一个用来读,一个用来写,从而实现线程间通信。

              13、线程加入join()

                13.1、使线程正常执行run里面的代码,无限阻塞当前线程知道run方法执行完毕。 

                13.2、有个重载的join(long time)方法,阻塞时间有限制,如果到了阻塞时间且线程还未运行完,会执行后边的代码。

                13.3、方法join(long time)与sleep(long time)的区别,join(long time)内部使用的是wait(long time),所以join(long time)方法释放锁,而sleep(long time)不释放锁。

              14、类ThreadLocal

                14.1、与线程绑定,存储绑定线程的私有值。

                14.2、具有线程变量的隔离性。

                14.3、第一次使用该类对象的get方法获取值返回都为null。

                14.4、解决第一次get总为null问题,自定义一个类继承ThreadLocal,重写initialValue(),返回一个任意对象,则它就为默认获取到的对象。

              15、类InheritableThreadLocal

                15.1、可以在子线程中继承父线程中的值。

                15.2、继承父线程中的值且修改。继承同14.4,修改重写childValue()方法,返回parentValue+要添加的值。

              16、类Lock

                16.1、使用方式:

                          创建对象   Lock  lock = new ReentrantLock();

                          上锁          lock.lock();

                          同步代码块;

                          释放锁      lock.unlock();

                16.2、类Condition,等待与唤醒

                      使用Condition让线程等待

                          创建Lock对象   Lock  lock = new ReentrantLock();

                          创建Condition对象 Condition condition = new Condition();

                          上锁          lock.lock();

                          同步代码块;

                          等待          condition.await();

                          同步代码块;

                          释放锁      lock.unlock();

                      使用Condition唤醒正在等待的线程。

                          将上边的代码中的

                          等待          condition.await();  替换为

                          唤醒          condition.signal();

                      与Object的方法进行对比

                          Object类的wait()方法相当于Condition类的await()方法;

                          Object类的wait(long t)方法相当于Condition类的await(long t)方法;

                          Object类的notify()方法相当于Condition类的signal()方法;

                         Object类的notifyAll()方法相当于Condition类的signalAll()方法;

                   16.3、公平锁与非公平锁

                        构造方法 ReentrantLock(boolean fair),参数就是表示是否是公平锁。

                        公平锁:谁先运行谁先获得锁

                        非公平锁:随机获得锁

                   16.4、ReentrantLock几个常用的方法

                        getHoldCount():查询当前线程持有该锁的个数;

                        getQueueLength():获取等待该锁的个数;

                        getWaitQueueLenght():获取等待唤醒线程的个数;

                        hasQueueThread():查询指定线程是否等待获取该锁;

                        hasQueueThreads():查询是否有线程等待获取该锁;

                        hasWaiters():查询是否有线程等待唤醒;

                        isFair():判断是不是公平锁;

                        isHeldByThread():查询当前线程是否持有此锁;

                        isLocked():查询此锁是否被任意线程锁持有;

                        lockInterruptibly():如果当前线程未被中断,则获取该锁,如果中断则抛出异常。

                        tryLock():在没有线程持有该锁的情况下,获得该锁。

                        tryLock(long timeout,TimeUnit tu):如果在给定时间内没有被其它线程持有该锁且没有被中断,则获取该锁;参1:时长、参2:单位(枚举值)。

                 16.5、Condition常用的几个方法

                        awaitUninterruptibly():在不管什么情况下,让线程在接收到唤醒之前一直处于等待状态。

                        awaitUntial(Date date):在时间到来之时,自动唤醒,也可以在时间到来之前,主动唤醒。

                 16.6、类ReenrantReadWriteLock(读写锁)

                      读写锁有两个,一个是读锁也可叫共享锁,一个是写锁也叫排他锁。

                      读锁的使用:

                      创建对象 ReenrantReadWriteLock lock = ReenrantReadWriteLock();

                      获取锁     lock.readLock.lock();

                      要锁的代码块

                      释放读锁  lock.readLock.unlock();

                      读锁与读锁之间是不互斥的。

                      写锁的使用:

                      创建对象 ReenrantReadWriteLock lock = ReenrantReadWriteLock();

                      获取锁     lock.writeLock.lock();

                      要锁的代码块

                      释放读锁  lock.writeLock.unlock();

                      写锁与写锁之间是互斥的。

                      读锁与写锁之间是互斥的。

    转载请注明原文地址: https://ju.6miu.com/read-34659.html

    最新回复(0)