Java线程基础学习总结

声明:原文出处已在文末标出,本人出于学习,对其做了整理,收集干货,不作商业用途!

目录

一、线程的基本概念

二、线程的创建和启动以及停止

2.1 范例1:使用实现Runnable接口创建和启动新线程

2.2 范例2:继承Thread类,并重写其run()方法创建和启动新的线程

2.3 Thread和Runnable的区别

三、线程状态转换

3.1 线程控制的基本方法

3.2 sleep/join/yield/wait/notify方法介绍以及案例 

3.3 sleep()和yield()的区别

3.4 wait和sleep区别

四、线程的优先级别

五、线程同步

5.1 synchronized关键字的使用范例

5.2 线程死锁的问题

5.3 生产者消费者问题

六、线程池


一、线程的基本概念

谈起线程,咱们得先了解这几个名词:线程,进程,程序,多线程,多进程,并发,并行。 

  • 程    序:是操作系统中实现多个功能的代码块,也叫软件,如微信、QQ。
  • 进   程: 计算机上正在运行的程序,如正在运行的微信,就是一个进程。
  • 线   程: 进程内的一个执行单元,也是进程内的可调度实体,如main()方法,是主线程。
  • 多进程:在计算机中并发运行的运行的多个进程,如微信和QQ同时工作。
  • 多线程:在单个程序中同时运行多个线程完成不同的工作,如多个CPU同时处理多个程序。
  • 并   发: 多个进程同时执行,我们就称之为“并发”,一般发生在一个处理器中。
  • 并   行: 在多个处理器中,每个处理器之间执行任务是互不影响的,在微观上来看它们是同时执行的。

  对“并发”的解释补充:CPU难道真的很神通广大,能够同时执行那么多程序吗?不是的,CPU的执行是这样的:CPU的速度很快,一秒钟可以算好几亿次,因此CPU把自己的时间分成一个个小时间片,每个时间片段中只能执行一个线程,我这个时间片执行你一会,下一个时间片执行他一会,再下一个时间片又执行其他人一会,虽然有几十个线程,但一样可以在很短的时间内把他们通通都执行一遍,但对我们人来说,CPU的执行速度太快了,因此看起来就像是在同时执行一样,像这样的多个进程同时执行,我们就称之为“并发”,但实际上在一个时间点上,CPU只有一个线程在运行。 什么才是真正的多线程?如果你的机器是双CPU,或者是双核,这确确实实是多线程

        补充:Windows操作系统是支持多线程的,它可以同时执行很多个线程,也支持多进程,因此Windows操作系统是支持多线程多进程的操作系统。Linux和Uinux也是支持多线程和多进程的操作系统。DOS就不是支持多线程和多进程了,它只支持单进程,在同一个时间点只能有一个进程在执行,这就叫单线程

  

线程理解:线程是一个程序里面不同的执行路径

  每一个分支都叫做一个线程,main()叫做主分支,也叫主线程。

  程只是一个静态的概念,机器上的一个.class文件,机器上的一个.exe文件,这个叫做一个进程。程序的执行过程都是这样的:首先把程序的代码放到内存的代码区里面,代码放到代码区后并没有马上开始执行,但这时候说明了一个进程准备开始,进程已经产生了,但还没有开始执行,这就是进程,所以进程其实是一个静态的概念,它本身就不能动。平常所说的进程的执行指的是进程里面主线程开始执行了,也就是main()方法开始执行了。进程是一个静态的概念,在我们机器里面实际上运行的都是线程。 

二、线程的创建和启动以及停止

创建方法:

  

       在JAVA里面,JAVA的线程是通过java.lang.Thread类来实现的,每一个Thread对象代表一个新的线程。创建一个新线程出来有两种方法:第一个是从Thread类继承,另一个是实现接口runnable。VM启动时会有一个由主方法(public static void main())所定义的线程,这个线程叫主线程。可以通过创建Thread的实例来创建新的线程。你只要new一个Thread对象,一个新的线程也就出现了。每个线程都是通过某个特定的Thread对象所对应的方法run()来完成其操作的,方法run()称为线程体。

启动方法:

        启动一个线程必须调用Thread的start()方法,如果线程类继承Thread,则可以直接调用对象的start()方法启动,如果线程类实现的是Runnable接口,则需要将对象封装成Thread,再调用封装后对象的start()。

停止方法:      

      1.让线程的run()方法执行完,线程自然结束。(这种方法最好);

      2.使用interrupt(),而程序会丢出InterruptedException例外,因而使得执行绪离开run()方法。

      3.使用stop()方法终止线程,在上一个版本的JDK中,sun公司已经说明:Thread.stop, Thread.suspend and                                 Thread.resume都已经不被推荐使用。因为会导致程序出现不可想象的状况。

      4.通过轮询和共享标志位的方法来结束线程,例如while(flag){},flag的初始值设为真,当需要结束时,将flag的值设为false。           (这种方法也不很好,因为如果while(flag){}方法阻塞了,则flag会失效)

2.1 范例1:使用实现Runnable接口创建和启动新线程

开辟一个新的线程来调用run方法

package cn.galc.test;

public class TestThread1{
    public static void main(String args[]){
        Runner1 r1 = new Runner1();//这里new了一个线程类的对象出来
        //r1.run();//这个称为方法调用,方法调用的执行是等run()方法执行完之后才会继续执行main()方法
        Thread t = new Thread(r1);//要启动一个新的线程就必须new一个Thread对象出来
        //这里使用的是Thread(Runnable target) 这构造方法
        t.start();//启动新开辟的线程,新线程执行的是run()方法,新线程与主线程会一起并行执行
        for(int i=0;i<10;i++){
            System.out.println("maintheod:"+i);
        }
    }
}
/*定义一个类用来实现Runnable接口,实现Runnable接口就表示这个类是一个线程类*/
class Runner1 implements Runnable{
    public void run(){
        for(int i=0;i<10;i++){
            System.out.println("Runner1:"+i);
        }
    }
}

多线程程序执行的过程如下所示:

 

不开辟新线程直接调用run方法

 

运行结果如下:

2.2 范例2:继承Thread类,并重写其run()方法创建和启动新的线程

package cn.galc.test;

/*线程创建与启动的第二种方法:定义Thread的子类并实现run()方法*/
public class TestThread2{
    public static void main(String args[]){
        Runner2 r2 = new Runner2();
        r2.start();//调用start()方法启动新开辟的线程
        for(int i=0;i<=10;i++){
            System.out.println("mainMethod:"+i);
        }
    }
}
/*Runner2类从Thread类继承
通过实例化Runner2类的一个对象就可以开辟一个新的线程
调用从Thread类继承来的start()方法就可以启动新开辟的线程*/
class Runner2 extends Thread{
    public void run(){//重写run()方法的实现
        for(int i=0;i<=10;i++){
            System.out.println("Runner2:"+i);
        }
    }
}

  使用实现Runnable接口和继承Thread类这两种开辟新线程的方法的选择应该优先选择实现Runnable接口这种方式去开辟一个新的线程。因为接口的实现可以实现多个,而类的继承只能是单继承。因此在开辟新线程时能够使用Runnable接口就尽量不要使用从Thread类继承的方式来开辟新的线程。

2.3 Thread和Runnable的区别

如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。

实现Runnable接口比继承Thread类所具有的优势:

1):适合多个相同的程序代码的线程去处理同一个资源

2):可以避免java中的单继承的限制,当一个类继承另一个类,又想成为一个线程时,就不能继承Thread类了,只能通过实现             Runnable接口来实现

3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立

4):线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类

三、线程状态转换

下面的这个图非常重要!你如果看懂了这个图,那么对于多线程的理解将会更加深刻!

  

1、新建状态(New):新创建了一个线程对象。

2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。

3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。

4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:

(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)。

(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。

(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)。

5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

3.1 线程控制的基本方法

  

3.2 sleep/join/yield/wait/notify方法介绍以及案例 

3.2.1 方法介绍:

线程睡眠:Thread.sleep(long millis)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结                               束后,就转为就绪(Runnable)状态。sleep()平台移植性好。

线程等待:Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方                               法。这个两个唤醒方法也是Object类中的方法,行为等价于调用 wait(0) 一样。

线程让步:Thread.yield() 方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。

线程加入:join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到                                另一个进程运行结束,当前线程再由阻塞转为就绪状态。

线程唤醒:Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选                             择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对                             象的监视器上等待。 直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常                             规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方                               面没有可靠的特权或劣势。类似的方法还有一个notifyAll(),唤醒在此对象监视器上等待的所有线程。

3..2.2 案例(sleep/join/yield的案例)

sleep方法的应用范例:

package cn.galc.test;

import java.util.*;

public class TestThread3 {
    public static void main(String args[]){
        MyThread thread = new MyThread();
        thread.start();//调用start()方法启动新开辟的线程
        try {
            /*Thread.sleep(10000);
            sleep()方法是在Thread类里面声明的一个静态方法,因此可以使用Thread.sleep()的格式进行调用
            */
            /*MyThread.sleep(10000);
            MyThread类继承了Thread类,自然也继承了sleep()方法,所以也可以使用MyThread.sleep()的格式进行调用
            */
            /*静态方法的调用可以直接使用“类名.静态方法名”
              或者“对象的引用.静态方法名”的方式来调用*/
            MyThread.sleep(10000);
            System.out.println("主线程睡眠了10秒种后再次启动了");
            //在main()方法里面调用另外一个类的静态方法时,需要使用“静态方法所在的类.静态方法名”这种方式来调用
            /*
            所以这里是让主线程睡眠10秒种
            在哪个线程里面调用了sleep()方法就让哪个线程睡眠,所以现在是主线程睡眠了。
            */
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //thread.interrupt();//使用interrupt()方法去结束掉一个线程的执行并不是一个很好的做法
        thread.flag=false;//改变循环条件,结束死循环
        /**
         * 当发生InterruptedException时,直接把循环的条件设置为false即可退出死循环,
         * 继而结束掉子线程的执行,这是一种比较好的结束子线程的做法
         */
        /**
         * 调用interrupt()方法把正在运行的线程打断
        相当于是主线程一盆凉水泼上去把正在执行分线程打断了
        分线程被打断之后就会抛InterruptedException异常,这样就会执行return语句返回,结束掉线程的执行
        所以这里的分线程在执行完10秒钟之后就结束掉了线程的执行
         */
    }
}

class MyThread extends Thread {
    boolean flag = true;// 定义一个标记,用来控制循环的条件

    public void run() {
        /*
         * 注意:这里不能在run()方法的后面直接写throw Exception来抛异常, 
         * 因为现在是要重写从Thread类继承而来的run()方法,重写方法不能抛出比被重写的方法的不同的异常。
         *  所以这里只能写try……catch()来捕获异常
         */
        while (flag) {
            System.out.println("==========" + new Date().toLocaleString() + "===========");
            try {
                /*
                 * 静态方法的调用格式一般为“类名.方法名”的格式去调用 在本类中声明的静态方法时调用时直接写静态方法名即可。 当然使用“类名.方法名”的格式去调用也是没有错的
                 */
                // MyThread.sleep(1000);//使用“类名.方法名”的格式去调用属于本类的静态方法
                sleep(1000);//睡眠的时如果被打断就会抛出InterruptedException异常
                // 这里是让这个新开辟的线程每隔一秒睡眠一次,然后睡眠一秒钟后再次启动该线程
                // 这里在一个死循环里面每隔一秒启动一次线程,每个一秒打印出当前的系统时间
            } catch (InterruptedException e) {
                /*
                 * 睡眠的时一盘冷水泼过来就有可能会打断睡眠 
                 * 因此让正在运行线程被一些意外的原因中断的时候有可能会抛被打扰中断(InterruptedException)的异常
                 */
                return;
                // 线程被中断后就返回,相当于是结束线程
            }
        }
    }
}

运行结果:

 join方法的使用范例:

package cn.galc.test;

public class TestThread4 {
    public static void main(String args[]) {
        MyThread2 thread2 = new MyThread2("mythread");
        // 在创建一个新的线程对象的同时给这个线程对象命名为mythread
        thread2.start();// 启动线程
        try {
            thread2.join();// 调用join()方法合并线程,将子线程mythread合并到主线程里面
            // 合并线程后,程序的执行的过程就相当于是方法的调用的执行过程
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i <= 5; i++) {
            System.out.println("I am main Thread");
        }
    }
}

class MyThread2 extends Thread {
    MyThread2(String s) {
        super(s);
        /*
         * 使用super关键字调用父类的构造方法 
         * 父类Thread的其中一个构造方法:“public Thread(String name)” 
         * 通过这样的构造方法可以给新开辟的线程命名,便于管理线程
         */
    }

    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println("I am a\t" + getName());
            // 使用父类Thread里面定义的
            //public final String getName(),Returns this thread's name.
            try {
                sleep(1000);// 让子线程每执行一次就睡眠1秒钟
            } catch (InterruptedException e) {
                return;
            }
        }
    }
}

运行结果:

yield方法的使用范例:

package cn.galc.test;

public class TestThread5 {
    public static void main(String args[]) {
        MyThread3 t1 = new MyThread3("t1");
        /* 同时开辟了两条子线程t1和t2,t1和t2执行的都是run()方法 */
        /* 这个程序的执行过程中总共有3个线程在并行执行,分别为子线程t1和t2以及主线程 */
        MyThread3 t2 = new MyThread3("t2");
        t1.start();// 启动子线程t1
        t2.start();// 启动子线程t2
        for (int i = 0; i <= 5; i++) {
            System.out.println("I am main Thread");
        }
    }
}

class MyThread3 extends Thread {
    MyThread3(String s) {
        super(s);
    }

    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println(getName() + ":" + i);
            if (i % 2 == 0) {
                yield();// 当执行到i能被2整除时当前执行的线程就让出来让另一个在执行run()方法的线程来优先执行
                /*
                 * 在程序的运行的过程中可以看到,
                 * 线程t1执行到(i%2==0)次时就会让出线程让t2线程来优先执行 
                 * 而线程t2执行到(i%2==0)次时也会让出线程给t1线程优先执行
                 */
            }
        }
    }
}

运行结果如下:

3.3 sleep()和yield()的区别


        sleep()和yield()的区别):sleep()使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
        sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出 CPU 占有权,但让出的时间是不可设定的。实际上,yield()方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把 CPU  的占有权交给此线程,否则,继续运行原来的线程。所以yield()方法称为“退让”,它把运行机会让给了同等优先级的其他线程。
       另外,sleep 方法允许较低优先级的线程获得运行机会,但 yield()  方法执行时,当前线程仍处在可运行状态,所以,不可能让出较低优先级的线程些时获得 CPU 占有权。在一个运行系统中,如果较高优先级的线程没有调用 sleep 方法,又没有受到 I\O 阻塞,那么,较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行。

3.4 wait和sleep区别


共同点: 
1. 他们都是在多线程的环境下,都可以在程序的调用处阻塞指定的毫秒数,并返回。 
2. wait()和sleep()都可以通过interrupt()方法 打断线程的暂停状态 ,从而使线程立刻抛出InterruptedException。 
   如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在wait/sleep /join,则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程。 
   需要注意的是,InterruptedException是线程自己从内部抛出的,并不是interrupt()方法抛出的。对某一线程调用 interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。但是,一旦该线程进入到 wait()/sleep()/join()后,就会立刻抛出InterruptedException 。 
不同点: 
1. Thread类的方法:sleep(),yield()等 
   Object的方法:wait()和notify()等 
2. 每个对象都有一个锁来控制同步访问。Synchronized关键字可以和对象的锁交互,来实现线程的同步。 
   sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。 
3. wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用 
4. sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
所以sleep()和wait()方法的最大区别是:
    sleep()睡眠时,保持对象锁,仍然占有该锁;
    而wait()睡眠时,释放对象锁。
  但是wait()和sleep()都可以通过interrupt()方法打断线程的暂停状态,从而使线程立刻抛出InterruptedException(但不建议使用该方法)。
sleep()方法
sleep()使当前线程进入停滞状态(阻塞当前线程),让出CUP的使用、目的是不让当前线程独自霸占该进程所获的CPU资源,以留一定时间给其他线程执行的机会;
   sleep()是Thread类的Static(静态)的方法;因此他不能改变对象的机锁,所以当在一个Synchronized块中调用Sleep()方法是,线程虽然休眠了,但是对象的机锁并木有被释放,其他线程无法访问这个对象(即使睡着也持有对象锁)。
  在sleep()休眠时间期满后,该线程不一定会立即执行,这是因为其它线程可能正在运行而且没有被调度为放弃执行,除非此线程具有更高的优先级。 
wait()方法
wait()方法是Object类里的方法;当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去(释放)了对象的机锁(暂时失去机锁,wait(long timeout)超时时间到后还需要返还对象锁);其他线程可以访问;
  wait()使用notify或者notifyAlll或者指定睡眠时间来唤醒当前等待池中的线程。
  wiat()必须放在synchronized block中,否则会在program runtime时扔出”java.lang.IllegalMonitorStateException“异常。

四、线程的优先级别

  

线程优先级别的使用范例:

package cn.galc.test;

public class TestThread6 {
    public static void main(String args[]) {
        MyThread4 t4 = new MyThread4();
        MyThread5 t5 = new MyThread5();
        Thread t1 = new Thread(t4);
        Thread t2 = new Thread(t5);
        t1.setPriority(Thread.NORM_PRIORITY + 3);// 使用setPriority()方法设置线程的优先级别,这里把t1线程的优先级别进行设置
        /*
         * 把线程t1的优先级(priority)在正常优先级(NORM_PRIORITY)的基础上再提高3级 
         * 这样t1的执行一次的时间就会比t2的多很多     
         * 默认情况下NORM_PRIORITY的值为5
         */
        t1.start();
        t2.start();
        System.out.println("t1线程的优先级是:" + t1.getPriority());
        // 使用getPriority()方法取得线程的优先级别,打印出t1的优先级别为8
    }
}

class MyThread4 implements Runnable {
    public void run() {
        for (int i = 0; i <= 1000; i++) {
            System.out.println("T1:" + i);
        }
    }
}

class MyThread5 implements Runnable {
    public void run() {
        for (int i = 0; i <= 1000; i++) {
            System.out.println("===============T2:" + i);
        }
    }
}

  run()方法一结束,线程也就结束了。

运行结果如下(截取了一部分):

五、线程同步

  

5.1 synchronized关键字的使用范例

package cn.galc.test;

public class TestSync implements Runnable {
    Timer timer = new Timer();

    public static void main(String args[]) {
        TestSync test = new TestSync();
        Thread t1 = new Thread(test);
        Thread t2 = new Thread(test);
        t1.setName("t1");// 设置t1线程的名字
        t2.setName("t2");// 设置t2线程的名字
        t1.start();
        t2.start();
    }

    public void run() {
        timer.add(Thread.currentThread().getName());
    }
}

class Timer {
    private static int num = 0;

    public/* synchronized */void add(String name) {// 在声明方法时加入synchronized时表示在执行这个方法的过程之中当前对象被锁定
        synchronized (this) {
            /*
             * 使用synchronized(this)来锁定当前对象,这样就不会再出现两个不同的线程同时访问同一个对象资源的问题了 只有当一个线程访问结束后才会轮到下一个线程来访问
             */
            num++;
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name + ":你是第" + num + "个使用timer的线程");
        }
    }
}

5.2 线程死锁的问题

package cn.galc.test;

/*这个小程序模拟的是线程死锁的问题*/
public class TestDeadLock implements Runnable {
    public int flag = 1;
    static Object o1 = new Object(), o2 = new Object();

    public void run() {
        System.out.println(Thread.currentThread().getName() + "的flag=" + flag);
        /*
         * 运行程序后发现程序执行到这里打印出flag以后就再也不往下执行后面的if语句了 
         * 程序也就死在了这里,既不往下执行也不退出
         */

        /* 这是flag=1这个线程 */
        if (flag == 1) {
            synchronized (o1) {
                /* 使用synchronized关键字把对象01锁定了 */
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o2) {
                    /*
                     * 前面已经锁住了对象o1,只要再能锁住o2,那么就能执行打印出1的操作了 
                     * 可是这里无法锁定对象o2,因为在另外一个flag=0这个线程里面已经把对象o1给锁住了 
                     * 尽管锁住o2这个对象的线程会每隔500毫秒睡眠一次,可是在睡眠的时候仍然是锁住o2不放的
                     */
                    System.out.println("1");
                }
            }
        }
        /*
         * 这里的两个if语句都将无法执行,因为已经造成了线程死锁的问题 
         * flag=1这个线程在等待flag=0这个线程把对象o2的锁解开, 
         * 而flag=0这个线程也在等待flag=1这个线程把对象o1的锁解开 
         * 然而这两个线程都不愿意解开锁住的对象,所以就造成了线程死锁的问题
         */

        /* 这是flag=0这个线程 */
        if (flag == 0) {
            synchronized (o2) {
                /* 这里先使用synchronized锁住对象o2 */
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o1) {
                    /*
                     * 前面已经锁住了对象o2,只要再能锁住o1,那么就能执行打印出0的操作了 可是这里无法锁定对象o1,因为在另外一个flag=1这个线程里面已经把对象o1给锁住了 尽管锁住o1这个对象的线程会每隔500毫秒睡眠一次,可是在睡眠的时候仍然是锁住o1不放的
                     */
                    System.out.println("0");
                }
            }
        }
    }

    public static void main(String args[]) {
        TestDeadLock td1 = new TestDeadLock();
        TestDeadLock td2 = new TestDeadLock();
        td1.flag = 1;
        td2.flag = 0;
        Thread t1 = new Thread(td1);
        Thread t2 = new Thread(td2);
        t1.setName("线程td1");
        t2.setName("线程td2");
        t1.start();
        t2.start();
    }
}

  解决线程死锁的问题最好只锁定一个对象,不要同时锁定两个对象

5.3 生产者消费者问题

package cn.galc.test;

/*    范例名称:生产者--消费者问题
 *     源文件名称:ProducerConsumer.java
 *    要  点:
 *        1. 共享数据的不一致性/临界资源的保护
 *        2. Java对象锁的概念
 *        3. synchronized关键字/wait()及notify()方法
 */

public class ProducerConsumer {
    public static void main(String args[]){
            SyncStack stack = new SyncStack();
            Runnable p=new Producer(stack);
            Runnable c = new Consumer(stack);
            Thread p1 = new Thread(p);
            Thread c1 = new Thread(c);
            
            p1.start();
            c1.start();
    }
}


class SyncStack{  //支持多线程同步操作的堆栈的实现
    private int index = 0;
    private char []data = new char[6];    
    public synchronized void push(char c){
        if(index == data.length){
        try{
                this.wait();
        }catch(InterruptedException e){}
        }
        this.notify();
        data[index] = c;
        index++;
    }
    public synchronized char pop(){
        if(index ==0){
            try{
                this.wait();
            }catch(InterruptedException e){}
        }
        this.notify();
        index--;
        return data[index];
    }
}


class  Producer implements Runnable{
    SyncStack stack;    
    public Producer(SyncStack s){
        stack = s;
    }
    public void run(){
        for(int i=0; i<20; i++){
            char c =(char)(Math.random()*26+'A');
            stack.push(c);
            System.out.println("produced:"+c);
            try{                                    
                Thread.sleep((int)(Math.random()*1000)); 
            }catch(InterruptedException e){
            }
        }
    }
}


class Consumer implements Runnable{
    SyncStack stack;    
    public Consumer(SyncStack s){
        stack = s;
    }
    public void run(){
        for(int i=0;i<20;i++){
            char c = stack.pop();
            System.out.println("消费:"+c);
            try{                                       
                Thread.sleep((int)(Math.random()*1000));
            }catch(InterruptedException e){
            }
        }
    }
}

六、线程池

       为什么用线程池:

  在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源。在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁,这就是”池化资源”技术产生的原因。

  因此,顾名思义线程池就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。并且我们知道,当线程并发的数量达到一定的量之后,线程之间抢占cpu资源过于激烈,最后可能导致cpu资源不足从而导致阻塞,甚至导致“饿死”,然而使用线程池恰好能控这一点(线程池设置一个最大量),因此,线程池的使用是有必要的。

  线程池的创建:

  Java 5+中的Executor接口定义一个执行线程的工具。它的子类型即线程池接口是ExecutorService。要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,因此在工具类Executors面提供了一些静态工厂方法,生成一些常用的线程池.

  常用的线程池:

  • newFixedThreadPool(int nThreads):创建一个线程池,该线程池重用固定数量的从共享无界队列中运行的线程。
  • newScheduledThreadPool(int corePoolSize) :创建一个线程池,可以调度命令在给定的延迟之后运行,或定期执行。
  • newCachedThreadPool() :创建一个根据需要创建新线程的线程池,但在可用时将重新使用以前构造的线程。

友情链接如下:

①线程池的概念、原理、方法等:https://www.cnblogs.com/-Marksman/p/9291811.html

②额外连接(线程其他方面的一些补充):https://www.cnblogs.com/sunlightlee/p/4957489.html

③40个Java多线程问题总结:http://www.cnblogs.com/xrq730/p/5060921.html

 

参考出处:https://www.cnblogs.com/xdp-gacl/p/3633936.html

                  https://www.cnblogs.com/yjd_hycf_space/p/7526608.html

                  https://www.cnblogs.com/beiyan/p/4371820.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值