java学习之Thread

一、前言

      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();

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

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

猜你喜欢

转载自blog.csdn.net/yangshuangyue/article/details/61619152