在操作系统中,安装了多个程序,并行指的是在一段时间内宏观上有多个程序同时运行,这在单 CPU 系统中,每一时刻只能有一道程序执行,即微观上这些程序是分时的交替运行,只不过是给人的感觉是同时运行,那是因为分时交替运行的时间是非常短的。
而在多个 CPU 系统中,则这些可以并发执行的程序便可以分配到多个处理器上(CPU),实现多任务并行执行,即利用每个处理器来处理一个可以并发执行的程序,这样多个程序便可以同时执行。目前电脑市场上说的多核 CPU,便是多核处理器,核 越多,并行处理的程序越多,能大大的提高电脑运行的效率。
注意:单核处理器的计算机肯定是不能并行的处理多个任务的,只能是多个任务在单个CPU上并发运行。同理,线程也是一样的,从宏观角度上理解线程是并行运行的,但是从微观角度上分析却是串行运行的,即一个线程一个线程的去运行,当系统只有一个CPU时,线程会以某种顺序执行多个线程,我们把这种情况称之为线程调度。
我们可以再电脑底部任务栏,右键----->打开任务管理器,可以查看当前任务的进程:
进程
线程
线程调度:
大部分操作系统都支持多进程并行运行,现在的操作系统几乎都支持同时运行多个程序。比如:现在我们上课一边使用编辑器,一边使用录屏软件,同时还开着画图板,dos窗口等软件。此时,这些程序是在同时运行,”感觉这些软件好像在同一时刻运行着“。
实际上,CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。
其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。
示例:
public class MyThread extends Thread {
//定义指定线程名称的构造方法
public MyThread(String name) {
//调用父类的String参数的构造方法,指定线程的名称
super(name);
}
/**
* 重写run方法,完成该线程执行的逻辑
*/
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println(getName()+":"+i);
}
}
}
测试:
public class Demo1 {
public static void main(String[] args) {
//创建自定义线程对象
MyThread mt = new MyThread("新建的线程");
//开启新线程
mt.start();
//在主方法中执行for循环
for (int i = 0; i < 10; i++) {
System.out.println("主线程:"+i);
}
}
}
示例:
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
测试:
public class Demo2 {
public static void main(String[] args) { //创建自定义类对象 线程任务对象
MyRunnable mr = new MyRunnable();
//创建线程对象
Thread t = new Thread(mr, "新建的线程");
t.start();
for (int i = 0; i < 20; i++) {
System.out.println("主线程" + i);
}
}
}
继承Thread 和实现Runnable的区别
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。总结:实现Runnable接口比继承Thread类所具有的优势:
方法名 | 说明 |
---|---|
public static void sleep(long millis) | 当前线程主动休眠 millis 毫秒。 |
public static void yield() | 当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片。 |
public final void join() | 允许其他线程加入到当前线程中。 |
public void setPriority(int) | 线程优先级为1-10,默认为5,优先级越高,表示获取CPU机会越多。 |
public void setDaemon(boolean) | 设置为守护线程线程有两类:用户线程(前台线程)、守护线程(后台线程) |
setPriority()
来设置线程的优先级别static int MAX_PRIORITY
线程可以具有的最高优先级,取值为10。
static int MIN_PRIORITY
线程可以具有的最低优先级,取值为1。
static int NORM_PRIORITY
分配给线程的默认优先级,取值为5。
示例:
/**
* 优先级
*
*/
public class PriorityThread extends Thread{
@Override
public void run() {
for(int i=0;i<50;i++) {
System.out.println(Thread.currentThread().getName()+"============"+i);
}
}
}
测试:
public class TestPriority {
public static void main(String[] args) {
PriorityThread p1=new PriorityThread();
p1.setName("p1");
PriorityThread p2=new PriorityThread();
p2.setName("p2");
PriorityThread p3=new PriorityThread();
p3.setName("p3");
p1.setPriority(1);
p3.setPriority(10);
//启动
p1.start();
p2.start();
p3.start();
}
}
使用线程的 sleep()
可以使线程休眠指定的毫秒数,在休眠结束的时候继续执行线程
示例:
class SleepThread extends Thread
{
@Override
public void run()
{
String[] names = new String[]{"zs","ls","ww","z6"};
int index = (int)(Math.random()*4);
for (int i = 3;i > 0;i--)
{
System.out.println(i);
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println("倒计时:"+i);
}
System.out.println("抽中学员为:"+names[index]);
}
}
测试:
public class TestSleep {
public static void main(String[] args)
{
new SleepThread().start();
}
}
Thread.yield()
方法作用是:暂停当前正在执行的线程对象(及放弃当前拥有的cup资源),并执行其他线程。yield()
做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用 yield()
的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证 **yield()**
达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。示例:
class Task1 implements Runnable{
@Override
public void run() {
for (int i = 0;i < 200;i++){
System.out.println("A:"+i);
}
}
}
class Task2 implements Runnable{
@Override
public void run() {
for (int i = 0;i < 10;i++){
System.out.println("B:"+i);
Thread.yield();
}
}
}
public class Demo {
public static void main(String[] args) {
new Thread(new Task2()).start();
new Thread(new Task1()).start();
}
}
sleep()和yield()的区别
sleep()使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;
yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出 CPU 占有权,但让出的时间是不可设定的。实际上,yield()方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把 CPU 的占有权交给此线程,否则,继续运行原来的线程。所以yield()方法称为“退让”,它把运行机会让给了同等优先级的其他线程
另外,sleep 方法允许较低优先级的线程获得运行机会,但 yield() 方法执行时,当前线程仍处在可运行状态,所以,不可能让出较低优先级的线程些时获得 CPU 占有权。在一个运行系统中,如果较高优先级的线程没有调用 sleep 方法,又没有受到 I\O 阻塞,那么,较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行。
Thread
中,join()
方法的作用是调用线程等待该线程完成后,才能继续往下运行。示例:
class JoinThread extends Thread{
public JoinThread(String name){
super(name);
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始运行");
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"-->子线程:"+i);
}
System.out.println(Thread.currentThread().getName()+"线程结束运行");
}
}
public class JoinDemo {
public static void main(String[] args) {
System.out.println("主线程开始运行。。。");
JoinThread t1 = new JoinThread("新加入的线程");
t1.start();
// try {
// t1.join();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println("主线程开始结束。。。");
}
}
守护线程.setDaemon(true):设置守护线程
线程有两类:用户线程(前台线程)、守护线程(后台线程)
如果程序中所有前台线程都执行完毕了,后台线程会自动结束
垃圾回收器线程属于守护线程
public class DeamonThread extends Thread {
@Override
public void run() {
for(int i=0;i<50;i++) {
System.out.println(Thread.currentThread().getName()+"----------"+i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
测试:
public class TestDeamon {
public static void main(String[] args) {
//创建线程(默认前台线程)
DeamonThread d1=new DeamonThread();
//设置线程为守护线程
d1.setDaemon(true);//主线程结束便结束了
d1.start();
for(int i=0;i<10;i++) {
System.out.println("主线程:----------"+i);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。
新建状态(New)
当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
就绪状态(Runnable)
当调用线程对象的start()方法(t.start();
),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()
此线程立即就会执行;
运行状态(Running)
当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就 绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
阻塞状态(Blocked)
处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:
1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
2.同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
3.其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
死亡状态(Dead)
线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
就绪状态转换为运行状态:当此线程得到处理器资源;
运行状态转换为就绪状态:当此线程主动调用yield()方法或在运行过程中失去处理器资源。
运行状态转换为死亡状态:当此线程线程执行体执行完毕或发生了异常。
此处需要特别注意的是:当调用线程的yield()方法时,线程从运行状态转换为就绪状态,但接下来CPU调度就绪状态中的哪个线程具有一定的随机性,因此,可能会出现A线程调用了yield()方法后,接下来CPU仍然调度了A线程的情况。
为什么会出现线程安全问题?
线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
示例:
class TicketRunnable implements Runnable{
private int ticket=100;
//每个窗口卖票的操作
//窗口 永远开启
@Override
public void run() {
while(true){//有票可以卖
//出票操作
if(ticket>0){
//使用sleep模拟一下出票时间 //模拟一下出票的时间
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖票:"+ticket--);
}
}
}
}
public class ThreadSafe {
public static void main(String[] args) throws Exception{
TicketRunnable t = new TicketRunnable();
Thread t1 = new Thread(t,"窗口1");
Thread t2 = new Thread(t,"窗口2");
Thread t3 = new Thread(t,"窗口3");
//3个窗口同时卖票
t1.start();
t2.start();
t3.start();
}
}
为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制。那么怎么去使用呢?有三种方式完成同步操作:
语法:
synchronized(临界资源对象){ //对临界资源对象加锁
//代码(原子操作)
}
同步锁:
对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.
注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。
示例:
package com.qf.sync;
class Ticket2 implements Runnable{
private int ticket=100;
Object lock = new Object();
//每个窗口卖票的操作
//窗口 永远开启
@Override
public void run() {
while(true){//有票可以卖
synchronized(lock){//synchronized (this) {//this ---当前对象
if(ticket>0){
//出票操作
//使用sleep模拟一下出票时间
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖票:"+ticket--);
}
}
}
}
}
public class TicketDemo2 {
public static void main(String[] args) {
Ticket2 ticket2 = new Ticket2();
Thread t1 = new Thread(ticket2,"窗口1");
Thread t2 = new Thread(ticket2,"窗口2");
Thread t3 = new Thread(ticket2,"窗口3");
//3个窗口同时卖票
t1.start();
t2.start();
t3.start();
}
}
同步方法 :使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。
语法:
synchronized 返回值类型 方法名称(形参列表){ //对当前对象(this)加锁
// 代码(原子操作)
}
示例:
class Ticket3 implements Runnable{
private int ticket=100;
//Object lock = new Object();
//每个窗口卖票的操作
//窗口 永远开启
@Override
public void run() {
while(true){//有票可以卖
sellTicket();
if(ticket<=0){
break;
}
}
}
/**
* 锁对象,谁调用这个方法,就是谁
* 隐含锁对象,就是this
*
* 静态方法,隐含锁对象就是Ticket3.class
*/
public synchronized void sellTicket(){
if(ticket>0){
//出票操作
//使用sleep模拟一下出票时间
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖票:"+ticket--);
}
}
}
public class TicketDemo3 {
public static void main(String[] args) {
Ticket3 ticket3 = new Ticket3();
Thread t1 = new Thread(ticket3,"窗口1");
Thread t2 = new Thread(ticket3,"窗口2");
Thread t3 = new Thread(ticket3,"窗口3");
//3个窗口同时卖票
t1.start();
t2.start();
t3.start();
}
}
synchronized注意点
同步锁是谁?
对于非static方法,同步锁就是this。
对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。
常用方法:
方法名 | 描述 |
---|---|
void lock() | 获取锁,如锁被占用,则等待。 |
boolean tryLock() | 尝试获取锁(成功返回true。失败返回false,不阻塞)。 |
void unlock() | 释放锁。 |
ReentrantLock:
示例:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyList {
//创建锁
private Lock lock = new ReentrantLock();
private String[] str = {"A","B","","",""};
private int count = 2;
public void add(String value){
//当没有锁的时候,会出现覆盖的情况
str[count] = value;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
System.out.println(Thread.currentThread().getName()+"添加了"+value);
// lock.lock();
// try {
// str[count] = value;
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// count++;
// System.out.println(Thread.currentThread().getName()+"添加了"+value);
// }finally {
// lock.unlock();
// }
}
public String[] getStr(){
return str;
}
}
测试:
public class TestMyList {
public static void main(String[] args) throws InterruptedException {
MyList myList = new MyList();
//
Thread t1 =new Thread(new Runnable() {
@Override
public void run() {
myList.add("hello");
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
myList.add("world");
}
});
t2.start();
t1.join();
t2.join();
String[] str = myList.getStr();
for (String s : str) {
System.out.println("s:"+s);
}
}
}
多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。
为什么要处理线程间通信
多个线程并发执行时, 在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行, 那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据。
如何保证线程间通信有效利用资源
多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作。 就是多个线程在操作同一份数据时, 避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制。
什么是等待唤醒机制
这是多个线程间的一种协作机制。谈到线程我们经常想到的是线程间的竞争(race),比如去争夺锁,但这并不是故事的全部,线程间也会有协作机制。就好比在公司里你和你的同事们,你们可能存在在晋升时的竞争,但更多时候你们更多是一起合作以完成某些任务。
就是在一个线程进行了规定操作后,就进入等待状态(wait()), 等待其他线程执行完他们的指定代码过后 再将其唤醒(notify());在有多个线程进行等待时, 如果需要,可以使用 notifyAll()来唤醒所有的等待线程。
wait/notify 就是线程间的一种协作机制。
线程通信方法
方法 | 说明 |
---|---|
public final void wait() | 释放锁,进入等待队列 |
public final void wait(long timeout) | 在超过指定的时间前,释放锁,进入等待队列 |
public final void notify() | 随机唤醒、通知一个线程 |
public final void notifyAll() | 唤醒、通知所有线程 |
注意:所有的等待、通知方法必须在对加锁的同步代码块中。
等待唤醒机制就是用于解决线程间通信的问题的,使用到的3个方法的含义如下:
示例:
/*
等待唤醒案例:
1,创建一个顾客线程(消费者):告知老板要的包子种类和数量,调用wait方法,放弃cpu的执行,进入等待状态
2,创建一个老板线程(生产者):花了5秒做包子,做好包子之后,调用notify方法,通知顾客吃包子,
注意:
顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行
同步使用的锁对象必须是唯一的,
只有锁对象才能调用wait方法和notify方法
*/
public class Demo1 {
public static void main(String[] args) {
//创建锁对象,保证唯一
Object obj =new Object();
//创建顾客线程
new Thread(){
@Override
public void run() {
while(true){
//保证等待和唤醒只能有一个在执行
synchronized (obj){
System.out.println("告知老板要的包子种类和数量");
//进入等待
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
//唤醒之后执行的代码
System.out.println("拿到包子,开始吃。。。");
System.out.println("---------------------");
}
}
}
}.start();
//创建老板线程
new Thread(){
@Override
public void run() {
while(true){
//花5秒钟做包子,
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//保证等待和唤醒只能有一个在执行
synchronized (obj){
System.out.println("包子做好了。。。。");
//做好包子之后,调用notify方法,通知顾客吃包子,
obj.notify();
}
}
}
}.start();
}
}
注意:
哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,所以她需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调用 wait 方法之后的地方恢复执行。
总结如下:
调用wait和notify方法需要注意的细节
多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
如下图所示,线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。
示例:
package com.qf.safe;
public class DeadLockDemo {
private static Object lock1 = new Object();//锁1,资源1
private static Object lock2 = new Object();//锁2,资源2
public static void main(String[] args) {
//启动一个线程
new Thread(new Runnable() {
@Override
public void run() {
synchronized(lock1){
System.out.println(Thread.currentThread().getName()+"拿到了锁1,资源1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"等待锁2,资源2");
synchronized (lock2){
System.out.println(Thread.currentThread().getName()+"拿到了锁2,资源2");
}
}
}
},"线程1").start();
//产生死锁的线程
// new Thread(new Runnable() {
// @Override
// public void run() {
// synchronized(lock2){
// System.out.println(Thread.currentThread().getName()+"拿到了锁2,资源2");
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread().getName()+"等待锁1,资源1");
// synchronized (lock1){
// System.out.println(Thread.currentThread().getName()+"拿到了锁1,资源1");
// }
// }
// }
// },"线程2").start();
}
}
线程 A 通过 synchronized (resource1) 获得 resource1 的监视器锁,然后通过Thread.sleep(1000);
让线程 A 休眠 1s 为的是让线程 B 得到执行然后获取到 resource2 的监视器锁。线程 A 和线程 B 休眠结束了都开始企图请求获取对方的资源,然后这两个线程就会陷入互相等待的状态,这也就产生了死锁。
破坏死锁
//破坏死锁
new Thread(new Runnable() {
@Override
public void run() {
synchronized(lock1){
System.out.println(Thread.currentThread().getName()+"拿到了锁1,资源1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"等待锁2,资源2");
synchronized (lock2){
System.out.println(Thread.currentThread().getName()+"拿到了锁2,资源2");
}
}
}
},"线程2").start();
线程 1 首先获得到 resource1 的监视器锁,这时候线程 2 就获取不到了。然后线程 1 再去获取 resource2 的监视器锁,可以获取到。然后线程 1 释放了对 resource1、resource2 的监视器锁的占用,线程 2 获取到就可以执行了。这样就破坏了破坏循环等待条件,因此避免了死锁。
我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?
在Java中可以通过线程池来达到这样的效果。
**线程池:**其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
合理利用线程池能够带来三个好处:
Java里面线程池的顶级接口是java.util.concurrent.Executor
,但是严格意义上讲Executor
并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是java.util.concurrent.ExecutorService
。
要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在java.util.concurrent.Executors
线程工厂类里面提供了一些静态工厂,生成一些常用的线程池。官方建议使用Executors工程类来创建线程池对象。
Java类库提供了许多静态方法来创建一个线程池:
Executors类中创建线程池的方法如下:
a、newFixedThreadPool
创建一个固定长度的线程池,当到达线程最大数量时,线程池的规模将不再变化。
b、newCachedThreadPool
创建一个可缓存的线程池,如果当前线程池的规模超出了处理需求,将回收空的线程;当需求增加时,会增加线程数量;线程池规模无限制。
c、newSingleThreadPoolExecutor
创建一个单线程的Executor,确保任务对了,串行执行
d、newScheduledThreadPool
创建一个固定长度的线程池,而且以延迟或者定时的方式来执行,类似Timer;
使用线程池中线程对象的步骤:
获取到了一个线程池ExecutorService 对象,定义了一个使用线程池对象的方法如下:
public Future> submit(Runnable task)
:获取线程池中的某一个线程对象,并执行
Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用。
示例:
package com.qf.threadpool;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
class MyThread implements Runnable{
@Override
public void run() {
System.out.println("我要一个教练");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("教练来了:"+Thread.currentThread().getName());
System.out.println("教完后,教练回到了游泳池");
}
}
public class ThreadPoolDemo {
public static void main(String[] args) {
// //创建一个包含固定数量的线程池对象
// ExecutorService executorService = Executors.newFixedThreadPool(2);
// //创建一个包含单条线程的线程池
// ExecutorService executorService = Executors.newSingleThreadExecutor();
// //创建一个带缓冲区的线程池,会根据需求创建线程
// ExecutorService executorService = Executors.newCachedThreadPool();
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
//创建Runnable实例对象
MyThread r = new MyThread();
//自己创建线程的方式
// Thread t = new Thread(r);
// t.start();
// //从线程池中获取线程对象,然后调用MyThread的run方法
// executorService.submit(r);
// //再获取一个线程对象,
// executorService.submit(r);
// executorService.submit(r);
// //注意:submit方法调用后,程序并不终止,因为线程次控制了线程的关闭
// //使用完,又归还到了线程池中,
//
// //关闭线程池
// executorService.shutdown();
for (int i = 0; i < 10; i++) {
scheduledExecutorService.schedule(r,10, TimeUnit.SECONDS);//延迟10秒执行
}
scheduledExecutorService.shutdown();;//执行到此处并不会马上关闭连接池
// while(!scheduledExecutorService.isTerminated()){
//
// }
System.out.println("Main Thread finished at"+new Date());
}
}
一般情况下,使用Runnable接口、Thread实现的线程我们都是无法返回结果的。但是如果对一些场合需要线程返回的结果。就要使用用Callable、Future这几个类。Callable只能在ExecutorService的线程池中跑,但有返回结果,也可以通过返回的Future对象查询执行状态。Future 本身也是一种设计模式,它是用来取得异步任务的结果
看看其源码:
public interface Callable<V> {
V call() throws Exception;
}
它只有一个call方法,并且有一个返回V,是泛型。可以认为这里返回V就是线程返回的结果。
ExecutorService接口:线程池执行调度框架
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
示例:
import java.util.Random;
import java.util.concurrent.*;
class HandleCallable implements Callable<Integer> {
private String name;
public HandleCallable(String name) {
this.name = name;
}
@Override
public Integer call() throws Exception {
System.out.println("task"+ name + "开始进行计算");
Thread.sleep(3000);
int sum = new Random().nextInt(300);
int result = 0;
for (int i = 0; i < sum; i++)
result += i;
return result;
}
}
public class FutureTest{
public static void main(String[] args) {
System.out.println("main Thread begin at:"+ System.nanoTime());
//创建线程池对象
ExecutorService executor = Executors.newCachedThreadPool();
HandleCallable task1 = new HandleCallable("1");
HandleCallable task2 = new HandleCallable("2");
HandleCallable task3 = new HandleCallable("3");
//执行
Future<Integer> result1 = executor.submit(task1);
Future<Integer> result2 = executor.submit(task2);
Future<Integer> result3 = executor.submit(task3);
executor.shutdown();
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
//获取到返回的直接
try {
System.out.println("task1运行结果:"+result1.get());
System.out.println("task2运行结果:"+result2.get());
System.out.println("task3运行结果:"+result3.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("main Thread finish at:"+ System.nanoTime());
}
}
示例:
public class TestCopyOnWriteArrayList {
public static void main(String[] args) {
//1创建集合
CopyOnWriteArrayList<String> list=new CopyOnWriteArrayList<>();
//2使用多线程操作
ExecutorService es=Executors.newFixedThreadPool(5);
//3提交任务
for(int i=0;i<5;i++) {
es.submit(new Runnable() {
@Override
public void run() {
for(int j=0;j<10;j++) {
list.add(Thread.currentThread().getName()+"...."+new Random().nextInt(1000));
}
}
});
}
//4关闭线程池
es.shutdown();
while(!es.isTerminated()) {}
//5打印结果
System.out.println("元素个数:"+list.size());
for (String string : list) {
System.out.println(string);
}
}
}
CopyOnWriteArrayList如何做到线程安全的
CopyOnWriteArrayList使用了一种叫写时复制的方法,当有新元素添加到CopyOnWriteArrayList时,先从原有的数组中拷贝一份出来,然后在新的数组做写操作,写完之后,再将原来的数组引用指向到新数组。
当有新元素加入的时候,如下图,创建新数组,并往新数组中加入一个新元素,这个时候,array这个引用仍然是指向原数组的。
当元素在新数组添加成功后,将array这个引用指向新数组。
CopyOnWriteArrayList
的整个add操作都是在锁的保护下进行的。 这样做是为了避免在多线程并发add的时候,复制出多个副本出来,把数据搞乱了,导致最终的数组数据不是我们期望的。
CopyOnWriteArrayList
的add
操作的源代码如下:
public boolean add(E e) {
//1、先加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
//2、拷贝数组
Object[] newElements = Arrays.copyOf(elements, len + 1);
//3、将元素加入到新数组中
newElements[len] = e;
//4、将array引用指向到新数组
setArray(newElements);
return true;
} finally {
//5、解锁
lock.unlock();
}
}
由于所有的写操作都是在新数组进行的,这个时候如果有线程并发的写,则通过锁来控制,如果有线程并发的读,则分几种情况:
1、如果写操作未完成,那么直接读取原数组的数据;
2、如果写操作完成,但是引用还未指向新数组,那么也是读取原数组数据;
3、如果写操作完成,并且引用已经指向了新的数组,那么直接从新数组中读取数据。
可见,CopyOnWriteArrayList
的读操作是可以不用加锁的。
示例:
public class TestCopyOnWriteArraySet {
public static void main(String[] args) {
//1创建集合
CopyOnWriteArraySet<String> set=new CopyOnWriteArraySet<>();
//2添加元素
set.add("pingguo");
set.add("huawei");
set.add("xiaomi");
set.add("lianxiang");
set.add("pingguo");
//3打印
System.out.println("元素个数:"+set.size());
System.out.println(set.toString());
}
}
示例:
public class TestConcurrentHashMap {
public static void main(String[] args) {
//1创建集合
ConcurrentHashMap<String, String> hashMap=new ConcurrentHashMap<String, String>();
//2使用多线程添加数据
for(int i=0;i<5;i++) {
new Thread(new Runnable() {
@Override
public void run() {
for(int k=0;k<10;k++) {
hashMap.put(Thread.currentThread().getName()+"--"+k, k+"");
System.out.println(hashMap);
}
}
}).start();
}
}
}