java之多线程

131 篇文章 1 订阅

一、进程和线程的概念

Thread和Runnable的区别:

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

main函数,实例化线程对象也有所不同,

extends Thread :t.start();

implements Runnable : new Thread(t).start();

总结:

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

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

2):可以避免java中的单继承的限制(不能访问父类的私有成员?)

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

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

二、线程的创建

  1.继承Thread类

/**
 * 线程的创建-继承Thread类
 */
public class Demo01 {
    public static void main(String[] args) {
        //创建线程对象
        Thread t1 = new MyThread01();
        //启动线程
        //启动线程应该使用start方法,run方法虽然可以执行具体逻辑,但是不会创建新的线程对象
        //而start方法在执行run方法的同时也会一个开辟一个新的线程,而这正是多线程所需要的
        t1.start();
    }
}

/**
 * 定义一个类,用来表示线程,该类需要实现Thread类
 */
class MyThread01 extends Thread {
    @Override
    public void run() {
        System.out.println("这是一个线程");
    }

  2.实现Runnable接口

/**
 * 线程对象的创建--实现Runnable接口
 */
public class Demo02 {
    public static void main(String[] args) {
        Thread t1 = new Thread(new MyThread02());
        //也通过start去启动线程
        t1.start();

        //也可以通过匿名内部类实现Runnable接口
        Thread t2=new Thread(new Runnable(){
            @Override
            public void run() {
                System.out.println("通过匿名内部类创建的线程...");
            }
        });
        t2.start();
    }
}

class MyThread02 implements Runnable {
    @Override
    public void run() {
        System.out.println("这是第二个线程...");
    }
}

3.返回线程的标识符

/**
 * long getId()
 * 返回该线程的标识符
 */
public class Demo05 {
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {

            }
        });
        t1.start();
        System.out.println(t1.getId());
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {

            }
        });
        t2.start();
        //由于虚拟机在启动时会创建很多其它的线程
        //所以在之前的id都被这些线程给占据了
        //自己创建的线程的编号就不是从1开始的了
        System.out.println(t2.getId());
    }
}

4.获取当前代码块片段的线程

/**
 * Thread.currentThread()
 * 获取运行当前代码片段的线程
 */
public class Demo04 {
    public static void main(String[] args) {
        //Thread[main,5,main]
        //main-表示线程名,main表示首要线程或主要线程
        //5-表示线程的优先级
        //main-表示的是当前线程所属的线程组
        System.out.println(Thread.currentThread());
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                //通过start方法启动线程会开启新的线程对象,此时执行该代码块的线程即为新建的线程
                System.out.println("t1 run方法中的线程:" + Thread.currentThread());
            }
        });
        t1.start();
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("t2 run方法中的线程:" + Thread.currentThread());
            }
        });
        t2.start();
        //由于method01方法是直接在主方法中调用的,并没有开辟新的线程对象
        //所以执行该方法的线程依然是主线程
        method01();
        //由于method02方法是在main线程中直接调用的,而method02中通过run方法调用
        //并没有开启新的线程
        method02();
    }

    public static void method01() {
        System.out.println("方法1中的线程:" + Thread.currentThread());
    }

    public static void method02() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("方法2中的线程:" + Thread.currentThread());
            }
        }).run();

    }

}

5.获取/设置线程的名字

/**
 * String getName()/void setName(String threadName)
 * 获取/设置线程名
 */
public class Demo06 {
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程名:" + Thread.currentThread().getName());
            }
        });
        t1.setName("线程1");
        t1.start();

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程名:" + Thread.currentThread().getName());
            }
        },"线程2");
        t2.start();
    }
}

6.测试线程是否处于活跃状态

/**
 * boolean isAlive()
 * 测试线程是否处于活动状态
 */
public class Demo07 {
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("这是一个线程...");
            }
        });
        //线程刚刚实例化以后,处于新建状态
        System.out.println("1:" + t1.isAlive());
        //新建状态的线程不具备竞争时间片的能力
        //通过start方法使得线程成为就绪状态
        t1.start();
        //就绪状态的线程是一种活动状态,它可能因为竞争到时间片而执行
        System.out.println("2:" + t1.isAlive());
    }
}

7.当前线程休眠指定毫秒数

/**
 * void sleep(long millis)
 * 当前线程休眠指定毫秒数
 */
public class Demo08 {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i <= 5; i++) {
                    System.out.println(Thread.currentThread().getName() + "->" + i);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "线程1").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i <= 5; i++) {
                    System.out.println(Thread.currentThread().getName() + "->" + i);
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "线程2").start();

    }
}

8.等待当前代码的结束

/**
 * void join()
 * 该方法用于等待当前线程的结束
 */
public class Demo09 {
    public static void main(String[] args) {
        Thread downLoadThread = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i <= 10; i++) {
                    System.out.println("已经下载了:" + (i * 10) + "%");
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        downLoadThread.start();
        Thread showThread = new Thread(new Runnable() {
            @Override
            public void run() {
                //保证在downLoadThread执行完毕以后再执行当前线程
                try {
                    downLoadThread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("显示图片");
            }
        });
        showThread.start();
    }
}

9.守护线程

/**
 * void setDaemon(boolean isDaemon)
 * 当参数为true时,该线程为守护线程(在调用start方法之前,否则,会有异常)
 * 守护线程
 * 当进程中只剩下守护线程时,所有守护线程强制终止。
 * GC与守护线程
 * Java垃圾回收线程就是一个典型的守护线程,当我们的程序中不再有任何运行中的Thread,程序就不会再产生垃圾,
 * 垃圾回收器也就无事可做,所以当垃圾回收线程是Java虚拟机上仅剩的线程时,Java虚拟机会自动离开。
 */
public class Demo10 {
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("守护线程");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        //2秒后主线程结束,而此时只剩下t1这个守护线程,守护线程会在没有其它线程的时候自动退出
        t1.setDaemon(true);
        t1.start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程结束了");
    }
}

10.获取线程的状态

/**
 * State getState():获取线程的状态
 * NEW
 * 线程实例化后还从未执行start()方法时的状态
 * RUNNABLE:
 * runnable状态是线程进入运行的状态
 * BLOCKED:
 * 某一个线程在等待锁的时候的状态
 * WAITING
 * 是线程执行了Object.wait()方法后所处的状态
 * TIMED_WAITING
 * 代表线程执行了Thread.sleep()方法,呈等待状态,等待时间到达,继续向下运行。
 * TERMINATED
 * 终结
 */
public class Demo11 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程执行...");

                //如果加了这一段,则main线程执行完毕,t1依然处于休眠状态中
                //所以此时t1状态为TIMED_WAITING
//                try {
//                    Thread.sleep(5000);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
            }
        });
        System.out.println(t1.getState());
        t1.start();
        System.out.println(t1.getState());
        //对main线程做休眠操作
        Thread.sleep(2000);
        //main线程,t1线程,两者是并发执行的
        //t1在执行的过程中,main也在执行,当main线程等到时,t1已经执行完毕
        //所以main休眠以后,t1是死亡状态
        System.out.println(t1.getState());


    }
}

11.

三、线程中相关的API

线程的两种调度方式

(1)分时调度模型。所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片。

(2)抢占式调度模型。优先让优先级高的线程使用CPU,若相同,则随机选择,优先级高的线程获取CPU的时间片相对多一些。

Java使用的是抢占式调度模型。

 

 优先级

线程的优先级划分为1-10级,其中1最低,10最高,线程提供了3个常量来表示最低、最高以及默认优先级。线程的调度是由线程调度控制的,可以通过提高线程的优先级来最大程度改善线程获取时间片的几率。但不一定就能保证优先级高的就先获得时间片。

 

join方法和yield方法的区别

void join()

该方法用于等待当前线程的结束

void yield()

该方法用于使当前线程主动让出当次CPU时间片回到就绪状态,等待时间片分配。使得多个线程的执行更和谐,但是保证不了严格交替执行。

 

线程的状态转换图

新建状态:线程对象创建完毕以后,还没调用start方法之前,此时的线程不具备任何竞争时间片资源的能力

 

就绪状态:新建状态的线程通过start方法启动后得到的状态,此时的线程可以竞争时间片,如果竞争到时间片则进入运行状态,如果失去时间片则进入就绪状态

 

死亡状态:run方法执行完毕(正常死亡),出现异常或错误(非正常死亡)

 

阻塞状态:

  1. 运行过程中遇到了IO输入操作,例如Scanner对象中的nextxxx方法,只有当方法返回结果以后,程序才能继续运行。
  2. sleep方法运行过程种,只有等到休眠时间到了以后才能继续运行。
  3. 同步锁:
  4. 等待通知:

 

线程并发安全问题

线程并发安全问题的成因

多线程并发操作访问同一个临界资源,例如:有一个静态变量,有两个线程,一个对其加1,一个对其加5,分别执行5次,执行过程中如果两个线程并发执行,则有可能同时对其进行修改,最后可能希望1先走,5再走,也有可能希望5再走,1再走。

Int a=0;

a=5

a=25

并发执行时,有可能1走一次,接下来就是5走了,……顺序不确定,和最后的结果可能不同。

临界资源:共享的变量(实例变量、静态变量)

线程并发安全问题的解决方案

默认多线程是异步执行的

异步执行改为同步执行

 

异步:同时并发执行,各干各的

同步:有顺序地执行,你干完我再干

具体解决方案

  1. synchronized同步锁

synchronized是Java语言内置的语义锁

synchronized是Java中的同步锁,提供内置锁机制来支持原子性。

可以用在代码块和方法上

每个java对象都可以用作一个同步锁,线程进入同步代码块之前自动获得锁,并且在退出同步代码块时自动释放锁,无论是正常还是通过抛异常,都一样,获得内置锁的唯一途径就是进入由这个锁保护的同步代码块或方法。

    

(1)同步代码块

synchronized(锁对象){

 

}

注意事项:

A.使用synchronied时需要对一个对象上锁以保证线程同步,这个对象应该注意:多个需要同步的线程在访问该同步块时,看到的应该是同一个对象的引用,否则达不到同步效果,通常用this作为锁对象。

B.在使用同步块时,应当尽量在允许的情况下减少同步范围,以提高开发的执行效率。

public class Demo13 {
	public static void main(String[] args) {
		// 此时的this代表的对象就是new MyThread()对象,
		// MyThread myThread = new MyThread();
		// 对于线程而言,myThread就是同一个对象,所以选择的this对象锁也就是同一个对象
		// Thread t1 = new Thread(myThread, "线程1");
		// Thread t2 = new Thread(myThread, "线程2");

		// 对于两个线程而言,执行了两次new MyThread()对象,所以this对象锁不是同一个对象,因此
		// 无法做到同步
		Thread t1 = new Thread(new MyThread(), "线程1");
		Thread t2 = new Thread(new MyThread(), "线程2");
		t1.start();
		t2.start();
	}

}

class MyThread implements Runnable {
	// 如果是不同的MyThread对象,objA也不是同一个对象,无法同步
	private Object objA = new Object();
	// 对于静态变量而言,不管是不是同一个线程,一定是同一个对象(因为静态变量在全局只有一份)
	private static Object objB = new Object();

	@Override
	public void run() {
		// synchronized后面要求指定锁对象
		// synchronized将代码锁定,一个线程在执行的过程中,另外一个线程就必须等待前面一个线程执行完毕,将锁对象释放才能执行
		// 锁对象的选取:Java中的任何对象都能作为锁对象(语法层面)
		// 对于多个线程而言,锁对象必须是同一个对象
		// this:
		// 实例变量:
		// 对于前两者而言,一定要关注不同线程,this或实例变量是不是同一个对象
		// 静态变量:
		// 类名.class:获取类信息,无论哪个类,在虚拟机加载了以后,其类信息也是全局唯一的
		// 对于后两者而言,是一种全局锁,由于都是静态的,所以对于多个线程而言,一定是同一个锁对象

		synchronized (objB) {
			for (int i = 1; i <= 5; i++) {
				System.out.println(Thread.currentThread().getName() + "->" + i);
			}
		}

	}
}

(2)同步方法

可以使用synchronized关键字修饰方法

注意:

A.接口方法,构造方法都不能使用synchronized关键字修饰

B.在继承关系中,synchronized如果修饰父类方法,子类默认是不继承这个关键字的,如果子类方法需要同步,可以自行添加该关键字

C.在一个不同步的方法中,调用了一个同步方法,则原来不同步的方法此时也可以按照同步方法的逻辑来执行

**
 * 修饰方法
 *
 * @author Administrator
 */
public class Demo14 {
    public static void main(String[] args) {
        MyThread04 myThread04 = new MyThread04();

        Thread t1 = new Thread(myThread04, "线程1");
        Thread t2 = new Thread(myThread04, "线程2");
        t1.start();
        t2.start();
    }

}

class MyThread04 implements Runnable {

    @Override
    public void run() {

        method02();
    }

    /**
     * 如果修饰的是实例方法,则同步锁默认就是类似于this这种和实例相关的对象 ,所以同步方法和同步代码块相比
     * <p>
     * 其实只是少了锁的选取,并且同步的范围从代码块变成了整个方法
     */
    public synchronized void method01() {
        for (int i = 1; i <= 5; i++) {
            System.out.println(Thread.currentThread().getName() + "->" + i);
        }
    }

    /**
     * 如果修饰的是静态方法,则同步锁默认就是类似于类信息或静态变量等对象,由于全局是唯一的,所以无论何种情况,都可以做到同步
     */
    public synchronized static void method02() {
        for (int i = 1; i <= 5; i++) {
            System.out.println(Thread.currentThread().getName() + "->" + i);
        }
    }
}

 

   2.lock

Lock锁属于Java中的编程式锁,和synchronized最大的不同之处在于,Lock锁的获取、释放都可以通过代码来进行控制,而synchronized同步锁中,锁的获取和释放都是通过Jvm虚拟机自行完成的。

ReentrantLock是Lock接口一种常见的实现,该锁在同一时刻只允许一个线程来访问,

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo15 {
	public static void main(String[] args) {
		MyThread03 myThread03 = new MyThread03();

		Thread t1 = new Thread(myThread03, "线程1");
		Thread t2 = new Thread(myThread03, "线程2");
		t1.start();
		t2.start();
	}

}

class MyThread03 implements Runnable {
	/**
	 * 由于上锁和解锁对应的是同一把锁,所以不能将其定义在方法中, 因为方法中定义了以后就是局部变量了,
	 * 
	 * 这样在两个线程执行的时候,对应的其实是两个不同的锁对象,所以应该定义为全局的
	 */
	private static Lock lock = new ReentrantLock();

	@Override
	public void run() {

		method();
	}

	public void method() {
		// 获取锁
		lock.lock();
		try {
			for (int i = 1; i <= 5; i++) {
				System.out.println(Thread.currentThread().getName() + "->" + i);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			// 无论正常与否都要释放锁--synchronized中也是如此
			// 只不过synchronized是自动释放的

			// 如果锁没有正确释放,会发生死锁现象

			// 面试官:告诉我什么叫死锁,我就录用你!
			// 程序猿:你录用我,我就告诉你什么叫死锁!
			lock.unlock();
		}

	}

}

synchronized和Lock锁的区别

  1. synchronized是内置语义锁,Lock锁是JDK1.5后提供的编程锁
  2. synchronized锁的获取和释放自动完成,而Lock锁需要手动操作
  3. Lock锁可以知道锁对象有没有获取成功
  • 死锁案例

public class Demo16 {
   private static Object objA = new Object();
   private static Object objB = new Object();

   public static void main(String[] args) {

      /***
       * 正常情况下,两个线程按照顺序依次执行出来,但是,也有特殊情况:
       *
       * 两个线程是并发执行的
       *
       * 第一个线程在执行的过程中占据objA锁,第二个线程在执行过程中占据objB锁
       *
       * 对于第一个线程而言,此时如果要继续执行需要获取到objB锁,而第二个线程中只有当代码执行完毕才会释放objB
       *
       * 对于第二个线程而言,此时如果要继续执行需要获取到objA锁,
       *
       * 而此时第一个线程由于处于等到objB锁的状态,所以不会往下执行,自然也不可能释objA
       *
       * 两个线程之间都在等待彼此释放锁,此时出现死锁现象
       */
      new Thread(new Runnable() {

         @Override
         public void run() {
            synchronized (objA) {
               System.out.println(1);
               synchronized (objB) {
                  System.out.println(2);
               }
            }
         }
      }).start();

      new Thread(new Runnable() {

         @Override
         public void run() {
            synchronized (objB) {
               System.out.println(3);
               synchronized (objA) {
                  System.out.println(4);
               }
            }
         }
      }).start();
   }

}

import java.util.concurrent.Executors;

/**
 * 死锁
 *
 * @author Administrator
 */
public class Demo16 {
    private static Object objA = new Object();
    private static Object objB = new Object();

    public static void main(String[] args) {

        /***
         * 正常情况下,两个线程按照顺序依次执行出来,但是,也有特殊情况:
         *
         * 两个线程是并发执行的
         *
         * 第一个线程在执行的过程中占据objA锁,第二个线程在执行过程中占据objB锁
         *
         * 对于第一个线程而言,此时如果要继续执行需要获取到objB锁,而第二个线程中只有当代码执行完毕才会释放objB
         *
         * 对于第二个线程而言,此时如果要继续执行需要获取到objA锁,
         *
         * 而此时第一个线程由于处于等到objB锁的状态,所以不会往下执行,自然也不可能释objA
         *
         * 两个线程之间都在等待彼此释放锁,此时出现死锁现象
         */
        new Thread(new Runnable() {

            @Override
            public void run() {
                synchronized (objA) {
                    System.out.println(1);
                    synchronized (objB) {
                        System.out.println(2);
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {

            @Override
            public void run() {
                synchronized (objB) {
                    System.out.println(3);
                    synchronized (objA) {
                        System.out.println(4);
                    }
                }
            }
        }).start();
    }

}

3、线程间的通讯方式wait()/notify()/notifyAll()

前面的同步操作只能保证多线程并发执行的过程中,一个线程在执行时,其它线程处在等待状态,使得多线程在执行过程中有一个先后顺序。但是同步操作中并不能确定执行顺序中具体的顺序。

当多线程协同工作时,可以通过线程通讯API来完成线程之间通讯。

wait()

用来将当前线程置入休眠状态,直到接到通知或被中断为止。在调用wait()之前,线程必须要获得该对象的对象级别锁

notify()

用于唤醒一个处于休眠状态的线程,该方法用来通知那些可能等待该对象的对象锁的其他线程。如果有多个线程等待,则线程规划器任意挑选出其中一个wait()状态的线程来发出通知。

notifyAll()

notifyAll使所有原来在该对象上wait的线程统统退出wait的状态,即全部被唤醒,不再等待。

/**
 * 1:1-5
 * 2:6-10
 * 3:11-15
 * 1:16-20
 * ……
 * n:71-75
 */
public class Homework02 {
    private static int num = 0;
    /**
     * 当前正在使用的线程
     **/
    private static int curThread = 1;
    private static Object obj = new Object();


    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                print(1, 2);
            }
        }, "线程1").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                print(2, 3);
            }
        }, "线程2").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                print(3, 1);
            }
        }, "线程3").start();
    }

    /**
     * 用来打印数字的方法
     *
     * @param now  当前使用哪个线程来打印
     * @param next 当前线程执行完毕以后再由哪个线程来执行操作
     */
    public static void print(int now, int next) {
        while (num <= 75) {
            synchronized (obj) {
                //限定一下当前应该打印数字的线程
                while (curThread != now) {
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                for (int i = 1; i <= 5; i++) {
                    num++;
                    System.out.println(Thread.currentThread().getName() + "->" + num);

                }
                if (num == 75) {
                    //break;
                    System.exit(0);
                }
                curThread = next;
                obj.notifyAll();
            }
        }
    }


}
/**
 * wait()
 * 用来将当前线程置入休眠状态,直到接到通知或被中断为止。在调用wait()之前,线程必须要获得该对象的对象级别锁
 * notify()
 * 用于唤醒一个处于休眠状态的线程,该方法用来通知那些可能等待该对象的对象锁的其他线程。
 * 如果有多个线程等待,则线程规划器任意挑选出其中一个wait()状态的线程来发出通知。
 * notifyAll()
 * notifyAll使所有原来在该对象上wait的线程统统退出wait的状态,即全部被唤醒,不再等待。
 * <p>
 * <p>
 * <p>
 * 上述三个方法需要在同步代码块中进行调用
 */
public class Demo17 {
    public static void main(String[] args) {
        //定义一个对象锁
        Object obj = new Object();

        Thread downLoadThread = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i <= 10; i++) {
                    System.out.println("已经下载了:" + (i * 10) + "%");
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                synchronized (obj) {
                    //随机唤醒一个线程
                    // obj.notify();
                    //唤醒所有的线程
                    obj.notifyAll();
                }


            }
        });
        downLoadThread.start();
        Thread showThread = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (obj) {
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("显示图片");
            }
        });
        showThread.start();
    }
}

wait和sleep的区别?

wait是object类中的方法,任何对象都能调用,sleep只能被Thread调用

wait可以是任意对象来调用,sleep只能当前线程调用

wait可以释放对象锁,sleep保留对象锁

wait可以通过notify随时唤醒,sleep只能等待设定时间结束后自然唤醒,否则将引发异常

wait必须在同步方法或同步块中进行调用,sleep可以在任意位置调用

生产者消费者模式

生产者消费者

import java.util.LinkedList;
import java.util.Queue;

/**
 * 生产者、消费者模式
 */
public class Homework01 {
    public static void main(String[] args) {
        //缓冲区
        Queue<Integer> queue = new LinkedList<>();

        //生产者线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i <= 4; i++) {
                    synchronized (queue) {
                        //假定有一个元素就视作满的情况
                        //如果缓冲区不为空,则不需要添加元素,如果缓冲区为空,需要添加元素
                        while (queue.size() == 1) {
                            System.out.println("通知消费者进行消费...");
                            try {
                                queue.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        System.out.println("生产了:" + i);
                        queue.offer(i);
                        //当缓冲区中有元素时,唤醒消费者
                        queue.notifyAll();
                    }
                }
            }
        }, "生产者").start();

        //消费者线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                //消费者被唤醒后才开始消费,此前不知道会被唤醒多少次
                while (true) {
                    synchronized (queue) {
                        while (queue.size() == 0) {
                            System.out.println("缓冲区为空,等待生产者生产...");

                            try {
                                queue.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }

                        //消费操作
                        int num = queue.poll();
                        //通知生产者进行生产
                        queue.notifyAll();

                        System.out.println("消费了元素:" + num);
                        if (num == 4) {
                            break;
                        }
                    }
                }
            }
        }, "消费者").start();
    }
}

 

线程池

线程池的作用

对于大量的线程如果要执行,在不使用线程池的时候,需要创建大量的线程对象,这些对象在任务执行完毕以后被销毁。

存在的问题:构建了大量的线程对象、对象的频繁创建和销毁会耗费大量的资源,影响执行效率。

 

有了线程池以后,首先先创建一个集合,其中预先就创建一些线程对象放入这个集合,当有请求需要使用线程时,从线程池中获取一个空闲的线程来执行任务,当任务执行完毕以后,将这个线程对象归还到线程池中(不是销毁)  ,等到下一次再有要使用线程的请求时,将这个线程取出再次使用。

优点:控制线程数量、线程对象的重用

  • 常用线程池的实现

1、newCachedThreadPool

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。

**
 * newCachedThreadPool
 * 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,
 * 若无可回收,则新建线程。线程池为无限大,当执行第二个任务时第一个任务已经完成,
 * 会复用执行第一个任务的线程,而不用每次新建线程。
 */
public class Demo18 {
    public static void main(String[] args) {
        //创建了线程池对象
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程1:" + Thread.currentThread().getName());
                //两个线程并发执行时,有可能第一个线程还没执行完,第二个线程已经开始执行
                //此时第二个线程执行时需要创建一个新的线程对象,有可能两次分配不同的线程
                //也有可能第一个线程执行完毕以后,第二个线程在再来执行,有可能得到相同的线程


                //如果加了try以后,第二个线程执行时,第一个线程一定还没执行完毕,所以一定会分配出一个新的线程
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程2:" + Thread.currentThread().getName());
            }
        });
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程3:" + Thread.currentThread().getName());
            }
        });
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程4:" + Thread.currentThread().getName());
            }
        });
        //在合理的时机去关闭线程池
        executorService.shutdown();
    }
}

2、newFixedThreadPool

创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * newFixedThreadPool
 * 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
 */
public class Demo19 {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程1" + Thread.currentThread().getName());
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程2" + Thread.currentThread().getName());
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        //由于线程池的最大长度为2,而此时有3个线程需要执行
        //前两个线程在执行过程中,需要等到2s钟,第三个线程此时没有空闲的线程可以使用
        //就会处于等到的状态,所以通过定长线程池可以约束线程数量的上限
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程3" + Thread.currentThread().getName());
            }
        });


        executorService.shutdown();

    }
}

3、newScheduledThreadPool

创建一个定长线程池,支持定时及周期性任务执行。

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * newScheduledThreadPool
 * 创建一个定长线程池,支持定时及周期性任务执行。
 */
public class Demo21 {
    public static void main(String[] args) {
        //创建一个定时执行的线程池对象
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
        //第一个参数:要执行的任务
        //第二个参数:延迟执行的时间
        //第三个参数:延迟执行的时间单位
        //3s以后执行
//        scheduledExecutorService.schedule(new Runnable() {
//            @Override
//            public void run() {
//                System.out.println("3s后执行...");
//            }
//        }, 3, TimeUnit.SECONDS);


        //第三个参数:周期性执行的时间间隔
        scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("5s后开始执行,每3s秒执行一次");
            }
        }, 5, 3, TimeUnit.SECONDS);


        //scheduledExecutorService.shutdown();
    }
}

4、newSingleThreadExecutor

创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * newSingleThreadExecutor
 * 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
 */
public class Demo20 {
    public static void main(String[] args) {
        //只有一个线程,所以每一个线程在执行过程中会排队等待前面的线程执行完毕
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程1" + Thread.currentThread().getName());
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程2" + Thread.currentThread().getName());
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        executorService.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程3" + Thread.currentThread().getName());
            }
        });


        executorService.shutdown();
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值