多线程
进程和线程
概念
程序是机器上安装的软件,是一个静止的内容。当程序被启动,就会产生(至少)一个进程。一般情况下,一个程序产生一个进程,但是有些特殊用途的程序运行时可能会产生多个进程。
在一个进程中,可以创建多个任务来同时进行,这些任务称为线程,是一种轻量级的进程。当这些线程同时执行时(交替执行),称为多线程。
理解
线程是利用CPU的空闲时间交替执行,由于交替执行的时间较短,看起来像是同时执行。
现在的电脑并非单核,单核CPU执行的多线程都是交替执行执行的,但是多核意味着有多个CPU,也就是说可以做到同时执行。
区别
- 一个程序运行后至少包含一个进程。
- 一个进程至少包含一个线程,可以包含多个线程。
- 进程是系统分配资源的基本单位,而线程是CPU调度的单位。
- 进程之间一般不能共享数据,但是线程之间可以共享数据。
线程的创建
线程的组成
- CPU的时间片。每一个线程在执行时都需要CPU分配时间。
运行数据。
- 堆空间数据,共享数据。
- 栈空间数据,一般是临时变量,线程中有独立空间来保存。
逻辑代码。
线程创建
线程创建有两种方式:
- 继承Thread类
- 实现Runnable接口
继承Thread类
继承Thread类,并重写run方法。
public class TestMain {
public static void main(String[] args) {
// 创建两个线程
MyThread1 t1 = new MyThread1();
MyThread2 t2 = new MyThread2();
// 启动线程
t1.start();
t2.start();
}
}
// 定义线程
class MyThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("=====" + i);
}
}
}
class MyThread2 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("-----" + i);
}
}
}
实现Runnable接口
实现Runnable接口,重写run方法。在使用时还是要创建Thread类的对象。
- 继承Thread类后使用简单,而实现接口后还是要创建Thread类的对象,使用相对复杂。
- 继承类后不能再继承其他类,而实现接口还可以继承其他类,或者实现其他接口,使用更灵活。
- 继承类后,中间逻辑代码不能复用,而实现接口后,逻辑代码还可以复用。
public class TestMain1 {
public static void main(String[] args) {
// 创建两个线程
MyRunnable1 r1 = new MyRunnable1();
Thread t1 = new Thread(r1);
MyRunnable2 r2 = new MyRunnable2();
Thread t2 = new Thread(r2);
// 启动线程
t1.start();
t2.start();
}
}
// 定义线程
class MyRunnable1 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("=====" + i);
}
}
}
class MyRunnable2 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("-----" + i);
}
}
}
start()与run()的区别:
- 直接调用run方法是直接将线程类中的业务逻辑代码执行。等同于定义一个类,创建该类的对象,调用其方法。根本没有使用线程相关的内容,没有创建多的线程。
- 当调用start方法时,会先创建线程,进入就绪状态,等待抢占CPU的执行时间,进行执行run方法。
当程序启动时, 会自动创建一个进程,该进程中会有一个默认线程,此线程名称为main(主线程)。
线程的状态
基本状态
新建:创建Thread对象,与普通对象没有区别。
就绪:调用start后,进入就绪状态,此时会等待操作系统分配时间片。
运行:当被操作系统选中后,开始调用run方法执行相应的业务,如果业务没有执行完毕,但是时间片到期,会进入就绪状态,等待下一次的时间片。
终止:业务执行完毕或者main结束。
线程饿死:是指一个线程一直没有被分配到时间片,无法执行。
常用方法
sleep休眠
sleep:指让线程进入休眠状态,直到休眠的时间结束,进入就绪状态。
一旦线程进入休眠状态,其他的线程就会优先抢占时间片。
public class TestMain2 {
public static void main(String[] args) {
// 创建两个线程
MyThread3 t1 = new MyThread3();
MyThread4 t2 = new MyThread4();
// 启动线程
t1.start();
t2.start();
}
}
// 定义线程
class MyThread3 extends Thread{
@Override
public void run() {
// 得到当前正在运行的线程名称
String name = Thread.currentThread().getName();
for (int i = 0; i < 100; i++) {
System.out.println(name + "=====" + i);
if(i == 20) {
// 单位是毫秒
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class MyThread4 extends Thread{
@Override
public void run() {
String name = Thread.currentThread().getName();
for (int i = 0; i < 100; i++) {
System.out.println(name + "-----" + i);
}
}
}
yield让渡
yield:让线程放弃当前的时间片,进入就绪状态。
注意:当一个线程让渡时,并不一定另一个线程就能抢到时间片。也可能让渡完又抢到时间片。
public class TestMain2 {
public static void main(String[] args) {
// 创建两个线程
MyThread3 t1 = new MyThread3();
MyThread4 t2 = new MyThread4();
// 启动线程
t1.start();
t2.start();
// String name = Thread.currentThread().getName();
// System.out.println(name);
}
}
// 定义线程
class MyThread3 extends Thread{
@Override
public void run() {
// 得到当前正在运行的线程名称
String name = Thread.currentThread().getName();
for (int i = 0; i < 100; i++) {
System.out.println(name + "=====" + i);
if(i == 20) {
// 让渡
Thread.yield();
}
}
}
}
class MyThread4 extends Thread{
@Override
public void run() {
String name = Thread.currentThread().getName();
for (int i = 0; i < 100; i++) {
System.out.println(name + "-----" + i);
}
}
}
join合并
有一个线程A正在运行,此时如果join了线程B,那么线程A会等待线程B执行完毕后才继续执行。(插队)
public class TestMain4 {
// 合并
public static void main(String[] args) {
Runnable r1 = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("=======" + i);
}
}
};
Thread t1 = new Thread(r1);
Runnable r2 = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("-----" + i);
if(i == 20) {
try {
// 会让t1执行完毕后,再执行t2剩下内容
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
};
Thread t2 = new Thread(r2);
t1.start();
t2.start();
}
}
线程等待
等待:线程进入了等待状态,等待结束后进入就绪状态。
- 当线程中使用了sleep后,进入了限时等待,时间结束进入就绪状态。
- 当线程中使用join后,进入不限时等待,直到join进来的线程执行完毕才进入就绪状态。
- 当线程中使用了wait后,进入了等待,直到被唤醒或者等待超时,才进入就绪状态。
线程安全
当多线程同时访问共享资源时,如果破坏了原子操作,可能会出现线程不安全问题。
解决方式:线程同步(加锁)。
语法:使用synchronized关键字。
- synchronized同步代码块
- synchronized同步方法
public class TestMain5 {
public static void main(String[] args) {
Seller s1 = new Seller("张三");
Seller s2 = new Seller("李四");
Seller s3 = new Seller("王五");
s1.start();
s2.start();
s3.start();
}
}
// 售票员卖票问题
class Seller extends Thread{
public static Integer ticket = 10;
private String sellName;
public Seller(String sellName) {
super();
this.sellName = sellName;
}
@Override
public void run() {
while(ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 加锁在此处必须是唯一的锁,而且需要引用数据类型
synchronized (A.obj) { // 同步代码块
// 双重检测锁
if(ticket > 0) {
ticket--;
System.out.println(sellName + "卖出一张票,还剩下" + ticket + "张");
}
}
}
}
}
class A{
public static final Object obj = new Object();
}
线程阻塞
阻塞:当线程运行过程中,遇到了加锁的代码,需要去获取锁,在没有获取锁时,进入阻塞状态,需要等待持有锁的线程将加锁的代码执行完毕后,才能继续执行。
死锁
当一个线程持有锁A,等待锁B,另一个线程持有锁B,等待锁A,两个线程都不会释放锁,此时也无法继续获得另一把锁,产生死锁。
public class TestMain6 {
public static void main(String[] args) {
Boy boy = new Boy();
Girl girl = new Girl();
boy.start();
girl.start();
}
}
class Boy extends Thread{
@Override
public void run() {
synchronized (A1.a) {
System.out.println("男孩抢到了筷子A");
synchronized (A1.b) {
System.out.println("男孩抢到了筷子B");
System.out.println("男孩吃东西");
}
}
}
}
class Girl extends Thread{
@Override
public void run() {
synchronized (A1.b) {
System.out.println("女孩抢到了筷子B");
synchronized (A1.a) {
System.out.println("女孩抢到了筷子A");
System.out.println("女孩吃东西");
}
}
}
}
class A1{
public static final Object a = new Object(); // 筷子A
public static final Object b = new Object(); // 筷子B
}
线程通信
使用wait方法让线程进入等待状态。
使用notify或者notifyAll唤醒线程,进入就绪状态。
注意:使用wait需要在synchronized中使用。
使用wait等待是无限期等待,需要唤醒或者超时。
- 唤醒,指使用notify或者notifyAll
- 超时,是指在指定时间内,如果没有唤醒,那么就放弃等待。
public class TestMain7 {
public static void main(String[] args) {
MyThread5 t1 = new MyThread5();
MyThread6 t2 = new MyThread6();
t1.start();
t2.start();
}
}
class MyThread5 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("===========" + i);
if(i == 20) {
synchronized (MyObj.obj) {
try {
MyObj.obj.wait(10000);// 指定超时时间,如果不唤醒,时间过了会自动醒来
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
class MyThread6 extends Thread{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("----------" + i);
}
}
}
class MyObj{
public static final Object obj = new Object();
}
注意:唤醒时也需要在synchronized中使用。唤醒后线程进行就绪状态。
public class TestMain7 {
public static void main(String[] args) {
MyThread5 t1 = new MyThread5();
MyThread6 t2 = new MyThread6();
t1.start();
t2.start();
}
}
class MyThread5 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("===========" + i);
if(i == 20) {
synchronized (MyObj.obj) {
try {
MyObj.obj.wait(10000);// 指定超时时间,如果不唤醒,时间过了会自动醒来
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
class MyThread6 extends Thread{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("----------" + i);
if(i == 800) {
// 唤醒
synchronized (MyObj.obj) {
MyObj.obj.notify();
}
}
}
}
}
class MyObj{
public static final Object obj = new Object();
}
注意:如果有多个线程同时使用某个锁对象进行wait状态,那么一次notify方法调用只会随机唤醒一个,需要多次调用notify方法,此时,可以使用notifyAll一次唤醒所有的进入wait状态的线程。
sleep和wait的区别:
1、sleep需要指定时间,时间到了会自动醒来。而wait如果没有指定超时时间,会无限等待,直到被唤醒为止。
2、sleep是一个静态方法,而且不需要在同步时调用,而wait是一个对象方法,需要在同步时使用。
3、sleep在休眠时不会释放锁。而wait会释放锁。
public class TestMain8 {
public static void main(String[] args) {
MyThread7 t1 = new MyThread7();
MyThread8 t2 = new MyThread8();
t1.start();
t2.start();
}
}
class MyThread7 extends Thread{
@Override
public void run() {
String name = Thread.currentThread().getName();
for (int i = 0; i < 100; i++) {
System.out.println(name + "===========" + i);
if(i == 20) {
synchronized (MyObj.obj) {
System.out.println("MyThread7获得锁");
try {
// sleep不释放锁,下面的线程需要等待锁
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
class MyThread8 extends Thread{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("----------" + i);
if(i == 200) {
// 加锁
synchronized (MyObj.obj) {
System.out.println("MyThread8获得锁");
}
}
}
}
}
public class TestMain8 {
public static void main(String[] args) {
MyThread7 t1 = new MyThread7();
MyThread8 t2 = new MyThread8();
t1.start();
t2.start();
}
}
class MyThread7 extends Thread{
@Override
public void run() {
String name = Thread.currentThread().getName();
for (int i = 0; i < 100; i++) {
System.out.println(name + "===========" + i);
if(i == 20) {
synchronized (MyObj.obj) {
System.out.println("MyThread7获得锁");
try {
// wait会释放锁
MyObj.obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
class MyThread8 extends Thread{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("----------" + i);
if(i == 200) {
// 加锁
synchronized (MyObj.obj) {
System.out.println("MyThread8获得锁");
}
}
}
}
}
生产消费模式
设计模式
设计模式是指前人经验的总结,并且经过长期的实践验证行之有效的方案。将在项目中可能遇到的问题进行分类总结,并找到其对应的解决方案。
二十三种设计模式:常见的二十三种问题对应的解决方案。
分为三大类:
- 创建型模式:创建对象的不同方式。(5种)单例模式、工厂模式、原型模式等
- 结构型模式:两个对象形成一种新的结构。(7种)桥接模式、适配器模式、装饰模式
- 行为型模式:多个对象之间相互作用。(11种)命令模式、监听者模式、调停者模式、迭代模式
注意:软件发展到现在,遇到的问题远不止23种,所以现在有很多新的设计模式出现,不在23种设计模式中。例如:MVC模式等。
生产消费模式
并发和并行:
并行是指多个程序同时执行。
并发是指多线程同时执行多个任务。如果在执行过程中涉及到临界资源的修改,会引发线程安全问题。
生产消费模式:多个生产者生产产品,多个消费者消费产品,要使得消费者和生产者并发执行。使用一个缓冲区来进行缓冲,将生产者生产的产品放入到缓冲区中,但是一旦缓冲满,生产者应该等待,等待消费者消费后唤醒生产者。消费者在缓冲区中取得产品进行消费,但是一旦缓冲区空,消费者应该等待,等待生产者生产后唤醒消费者。
public class Car {
private String id;
private String name;
public Car(String id, String name) {
super();
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Car [id=" + id + ", name=" + name + "]";
}
}
public class Storage {
private Car [] cars = new Car[6];
private int size = 0;
// 1、同步方法表示当前方法的所有代码全部放入同步块
// 2、同步方法加锁的对象是调用该方法的对象,所以静态方法锁对象是类,即类名.class,非静态方法锁对象是this
public synchronized void add(Car car) {
while(size >= 6) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
cars[size++] = car;
System.out.println(car + "放入仓库");
this.notifyAll();
}
public synchronized Car out() {
while(size <= 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Car car = cars[size - 1];
cars[size - 1] = null;
size--;
System.out.println(car + "取出仓库");
this.notifyAll();
return car;
}
}
public class Producer extends Thread{
private Storage storage;
public Producer(Storage storage) {
super();
this.storage = storage;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
for (int i = 0; i < 15; i++) {
Car car = new Car("汽车" + i, name);
storage.add(car);
System.out.println("生产了一辆汽车:" + car);
}
}
}
public class Customer extends Thread{
private Storage storage;
public Customer(Storage storage) {
super();
this.storage = storage;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
for (int i = 0; i < 10; i++) {
Car car = storage.out();
System.out.println(name + "消费了一辆汽车:" + car);
}
}
}
public class TestMain {
public static void main(String[] args) {
Storage storage = new Storage();
Producer p1 = new Producer(storage);
Producer p2 = new Producer(storage);
Customer c1 = new Customer(storage);
Customer c2 = new Customer(storage);
Customer c3 = new Customer(storage);
p1.start();
p2.start();
c1.start();
c2.start();
c3.start();
}
}
线程的终止
让正在执行的线程停止。
一般有3种方法:
使用stop
1、使用stop,此方法已被废弃,不允许使用。stop是强行终止线程,相当于电脑在运行时断电,会导致一些隐患。
public class TestMain1 {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
String name = Thread.currentThread().getName();
for (int i = 0; ; i++) {
System.out.println(name + "======" + i);
}
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
String name = Thread.currentThread().getName();
for (int i = 0; i < 100; i++) {
System.out.println(name + "-----" + i);
if(i == 80) {
// 不能使用,会出现未知问题
t1.stop();
}
}
}
});
t2.start();
}
}
自定义标识
在线程运行过程种,定义一个线程运行时需要满足的标识,需要线程停止时,修改该标识,线程就会执行完毕。
public class TestMain1 {
public static void main(String[] args) {
T1 t1 = new T1();
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
String name = Thread.currentThread().getName();
for (int i = 0; i < 100; i++) {
System.out.println(name + "-----" + i);
if(i == 80) {
t1.b = true;
}
}
}
});
t2.start();
}
}
class T1 extends Thread{
public volatile boolean b;
@Override
public void run() {
String name = Thread.currentThread().getName();
for (int i = 0; ; i++) {
System.out.println(name + "======" + i);
if(b) {
break;
}
}
}
}
使用interrupt
系统对线程种定义的标识,通过改变该标识的值,来停止线程。
注意:如果线程正在休眠,通过interrupt想去终止线程,会出现异常。
public class TestMain1 {
public static void main(String[] args) {
T1 t1 = new T1();
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
String name = Thread.currentThread().getName();
for (int i = 0; i < 100; i++) {
System.out.println(name + "-----" + i);
if(i == 80) {
t1.interrupt();
}
}
}
});
t2.start();
}
}
class T1 extends Thread{
@Override
public void run() {
String name = Thread.currentThread().getName();
for (int i = 0; ; i++) {
System.out.println(name + "======" + i);
if(i == 10) {
try {
Thread.sleep(10000);
// 休眠时不能被interrupt,会出现异常
} catch (InterruptedException e) {
e.printStackTrace();
// 出现异常也停止循环
break;
}
}
// 如果被interrupt,则停止循环
if(Thread.interrupted()) {
break;
}
}
}
}
线程的优先级
优先级越高,被CPU选中分配时间片的概率越高。
优先级最高为10,最低为1,默认为5。可以通过常量设置。
public class TestMain2 {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
String name = Thread.currentThread().getName();
for (int i = 0; i < 100; i++) {
System.out.println(name + "-----" + i);
}
}
});
t1.setPriority(Thread.MAX_PRIORITY); // 设置为最高的优先级
// 如果不设置优先级,默认为普通优先级
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
String name = Thread.currentThread().getName();
for (int i = 0; i < 100; i++) {
System.out.println(name + "-----" + i);
}
}
});
t2.setPriority(Thread.MIN_PRIORITY); // 设置为最低的优先级
t2.start();
}
}
守护线程
守护线程是一个特殊的线程,如果没有其他的线程在运行,守护线程会自动停止。JVM就是一个守护线程,当程序执行结束时,JVM也自动停止。
public class TestMain3 {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
String name = Thread.currentThread().getName();
for (int i = 0;; i++) {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + "======" + i);
}
}
});
t1.setDaemon(true); // 设置为守护线程
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
String name = Thread.currentThread().getName();
for (int i = 0; i < 500; i++) {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + "----------" + i);
}
}
});
t2.start();
}
}
volatile关键字的作用
synchronized:
- 可见性:执行到同步代码块中时,如果要访问临界资源,会去获取最新的值。
- 互斥性:执行到同步代码块时,会持有锁,其他线程如果也要执行同步代码块时,会等待前一个线程执行完毕同步代码块后释放锁才能去持有锁执行。
volatile修饰属性时,表示该属性具备有可见性。注意:该关键字并不能解决线程安全。
package com.qf.day19;
public class TestMain4 {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
String name = Thread.currentThread().getName();
for (int i = 0; i < 500; i++) {
// try {
// Thread.sleep(2);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println(name + "----------" + i);
if(i == 400) {
t1.n = "b";
}
}
}
});
t2.start();
}
}
class MyThread extends Thread{
public volatile String n = "a";
@Override
public void run() {
System.out.println("程序开始执行====");
while(true) {
// try {
// Thread.sleep(2);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println("===");
if(n.equals("b")) {
break;
}
}
System.out.println("程序执行结束====");
}
}
当没有使用volatile关键字,也没有在循环中打印或者休眠时,发现即使在别的线程中修改了变量n的值,该线程也不会停止。原因是循环中的判断并没有去获取n的最新的值,一直使用a在做判断,所以无法停止。
但是加了volatile关键字后,每次使用变量n都会去获取最新的值,所以线程能够正常停止。
但是即使没有使用volatile关键字,如果在循环中休眠或者打印后,一样可以实现线程停止的效果。注意:原因是循环时CPU相对较忙,没有空闲时间进行变量值的更新,但是系统会尽量完成值的更新,所以一旦休眠或者打印,对于CPU来说,会比较闲,也就有足够的时间来进行变量n的值更新。但是不推荐这样写。应该使用volatile关键字。
线程池
池
池就是一个集合,在集合中会放入一定量的对象,对这些对象进行管理和重用,避免多次创建和销毁对象的时间浪费。
线程池
线程池是一个集合,里面保存了一定量的线程对象,对这些对象进行管理和重用,避免多次创建和销毁线程带来的时间开销。
public class TestMain5 {
public static void main(String[] args) {
// 线程池帮助类
// 创建线程数量固定的线程池
ExecutorService service = Executors.newFixedThreadPool(2);
// 创建线程数量无上限的线程池
// ExecutorService service = Executors.newCachedThreadPool();
// 创建单线程的线程池
// ExecutorService service = Executors.newSingleThreadExecutor();
// 创建任务
Runnable r1 = new Runnable() {
@Override
public void run() {
String name = Thread.currentThread().getName();
for (int i = 0; i < 200; i++) {
System.out.println(name + "====" + i);
}
}
};
// 让线程池中线程执行任务
// 当把任务交给线程池去执行时,只要有空闲的线程,会自动启动
service.submit(r1);
service.submit(r1);
service.submit(r1);
service.submit(r1);
// 关闭线程池
// 等待线程池中所有任务执行完毕后才关闭
service.shutdown();
}
}
Callable和Future
Callable在JDK1.5时候加入,与Runnable都是定义多线程任务的,但是Runnable没有返回值,而Callable有返回值。
Callable有返回值,通过Future接口去获取返回值,
// 使用两个线程分别求1~50、51~100的和,并求最终的和
public class TestMain6 {
public static void main(String[] args) {
// 线程池帮助类
// 创建线程数量固定的线程池
ExecutorService service = Executors.newFixedThreadPool(2);
// 泛型是返回值类型
// 第一个任务,计算1~50的和
Callable<Integer> c1 = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 50; i++) {
sum += i;
}
return sum;
}
};
// 第二个任务,计算51~100的和
Callable<Integer> c2 = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 51; i <= 100; i++) {
Thread.sleep(100);
sum += i;
}
return sum;
}
};
// 获得结果
Future<Integer> f1 = service.submit(c1);
Future<Integer> f2 = service.submit(c2);
// 求和
try {
int sum1 = f1.get();
System.out.println("第一个和为:" + sum1);
int sum2 = f2.get();
System.out.println("第二个和为:" + sum2);
int result = sum1 + sum2;
System.out.println("最终结果为:" + result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
// 关闭线程池
// 等待线程池中所有任务执行完毕后才关闭
service.shutdown();
}
}
同步和异步:
同步是指在一个线程中顺序执行,需要等待第一个执行完成后继续执行第二个。
异步是指多个线程同时执行。
Lock锁
Lock接口
与synchronized作用一致。但是比同步方法粒度更细,比同步代码块使用更灵活。功能更强大。
lock():加锁开始,相当于同步代码块的
{
unlock():加锁结束,相当于同步代码块的
}
boolean tryLock():尝试获取锁
重入锁(公平锁)
ReentrantLock:Lock接口的常用实现类。
在创建锁对象时,使用无参构造方法,或者传入参数false,表示普通的重入锁。
在创建锁对象时,如果传入参数true,表示创建一个公平锁,先等待的先获得锁,按照等待抢锁的顺序获得锁,需要额外的资源来维持顺序,性能会下降。
public class TestMain {
public static void main(String[] args) {
Seller s1 = new Seller("张三");
Seller s2 = new Seller("李四");
Seller s3 = new Seller("王五");
s1.start();
s2.start();
s3.start();
}
}
// 售票员卖票问题
class Seller extends Thread{
public static Integer ticket = 10;
private String sellName;
private static final ReentrantLock lock = new ReentrantLock();
public Seller(String sellName) {
super();
this.sellName = sellName;
}
@Override
public void run() {
while(ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 加锁在此处必须是唯一的锁,而且需要引用数据类型
lock.lock();
try {
// 双重检测锁
if(ticket > 0) {
ticket--;
System.out.println(sellName + "卖出一张票,还剩下" + ticket + "张");
}
} finally {
lock.unlock();
}
}
}
}
class A{
public static final Object obj = new Object();
}
读写锁
支持读和写的锁,如果多个线程都是读,没有写,则共享,如果有一个线程在写,那么其他线程需要等待。
下面是一个简单的读写锁案例:
public class MyClass {
private int value;
private ReentrantLock lock = new ReentrantLock();
public int getValue() throws InterruptedException {
lock.lock();
try {
Thread.sleep(1000);
return value;
} finally {
lock.unlock();
}
}
public void setValue(int value) throws InterruptedException {
lock.lock();
try {
Thread.sleep(1000);
this.value = value;
} finally {
lock.unlock();
}
}
}
public class TestMain1 {
public static void main(String[] args) {
final MyClass c = new MyClass();
// 写任务
Runnable r1 = new Runnable() {
@Override
public void run() {
try {
c.setValue(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// 读任务
Runnable r2 = new Runnable() {
@Override
public void run() {
try {
c.getValue();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// 创建一个20任务的线程池
ExecutorService service = Executors.newFixedThreadPool(20);
// 当前时间
long startTime = System.currentTimeMillis();
// 添加2个写任务
for (int i = 0; i < 2; i++) {
service.submit(r1);
}
// 添加18个读任务
for (int i = 0; i < 18; i++) {
service.submit(r2);
}
// 关闭线程池
service.shutdown();
// 如果线程池中的线程没有执行完毕,就卡住,不继续向下执行代码
while(!service.isTerminated()) {}
long endTime = System.currentTimeMillis();
System.out.println("执行时间为:" + (endTime - startTime));
}
}
以上代码没有使用读写锁,大概需要时间为20秒。(20131毫秒)
修改为读写锁后:
public class MyClass {
private int value;
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private ReadLock readLock = lock.readLock(); // 得到读锁
private WriteLock writeLock = lock.writeLock(); // 得到写锁
public int getValue() throws InterruptedException {
readLock.lock();
try {
Thread.sleep(1000);
return value;
} finally {
readLock.unlock();
}
}
public void setValue(int value) throws InterruptedException {
writeLock.lock();
try {
Thread.sleep(1000);
this.value = value;
} finally {
writeLock.unlock();
}
}
}
以上代码修改成读写锁后,两次写需要2秒,所有的读共享一次锁,需要1秒,共计3秒。(3027毫秒)
线程安全的集合
Collections类中提供的线程安全的集合获得方法
Collections类中提供了一些线程安全的集合获得方法(这些方法以synchronized开头),但是这些方法都是jdk1.2时提供,线程安全,但是性能不高,不推荐使用。
CopyOnWriteArrayList
写有锁,读无锁。
写时会复制一份,写入后替换掉原来的地址。
优先:线程安全,缺点:耗费内存。
与ArrayList用法一样。
CopyOnWriteArraySet
底层使用CopyOnWriteArrayList实现,
区别在于添加时应该使用addIfAbsent()方法,该方法在添加时会遍历集合,如果发现有相同元素,则放弃添加。
ConcurrentHashMap
使用方式与HashMap相同。
JDK1.7:
- 使用分段锁segment。
- 初始时采用16段,对应16把锁,只有当添加到同一个段时,才需要互斥等待,如果不是同一个段,不需要等待。
- 最理想状态是分别添加到16个段,理论可以16个线程同时添加。
JDK1.8:
- 采用CAS(compare and swap比较交换算法)机制
- 有三个核心变量,V、E、N,V是要更新的变量,E是预期值,当V==E时,认为没有其他线程修改过,则进行修改V=N,否则就认为有别的线程修改过,则取消操作。
悲观锁和乐观锁:
悲观锁就是一种互斥锁,要求只能有一个线程在操作,其他的线程需等待。
乐观锁一般需要一个状态(版本),每次操作时先下载版本号,再进行修改,修改完后提交时先比较版本号,如果版本号一致,则没有人在你修改时进行了修改,则可以正常提交修改,并将版本号一同修改。
Queue接口
队列,遵循FIFO(First In First Out先进先出)原则。
ConcurrentLinkedQueue
线程安全,高并发时性能最好的队列。
采用CAS原则。
BlockingQueue
线程阻塞的队列,是Queue的子接口,增加两个无限期等待的方法。
put(E e); 向队列中添加元素,如果队列中没有空间,则等待。
E take(); 在队列中获取元素,如果队列中没有元素,则等待。
生产消费模式。
ArrayBlockingQueue
使用数组实现的有界队列,需要固定大小。
LinkedBlockingQueue
使用链表实现的无界队列,默认大小Integer.MAX_VALUE。
public class TestMain2 {
public static void main(String[] args) {
// 创建一个6个大小的有界队列
ArrayBlockingQueue<Car> queue = new ArrayBlockingQueue<Car>(6);
// 创建一个生产任务
Runnable r1 = new Runnable() {
public void run() {
String name = Thread.currentThread().getName();
for (int i = 0; i < 15; i++) {
try {
Car car = new Car("汽车" + i, name);
queue.put(car);
System.out.println("生产一辆汽车,准备放入仓库" + car);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
// 创建一个消费任务
Runnable r2 = new Runnable() {
public void run() {
String name = Thread.currentThread().getName();
for (int i = 0; i < 10; i++) {
try {
Car car = queue.take();
System.out.println(name + "消费一辆汽车," + car);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
// 创建一个线程池
ExecutorService service = Executors.newFixedThreadPool(5);
// 提交任务
service.submit(r1);
service.submit(r1);
service.submit(r2);
service.submit(r2);
service.submit(r2);
// 关闭线程池
service.shutdown();
}
}