多线程初体验
多线程创建:
- 方式一:继承 Thread 类
- 创建一个类继承 Thread 类
- 重写 run() 方法 -->> 将此线程需要的操作写在 run() 方法中
- 创建实例,调用 start() 方法
- 启动当前线程
- 调用 run() 方法
注意: 一个线程对象只能 start() 一次
方式一代码
package work.jkfx.thread.create;
/**
* 多线程创建:
* -方式一:继承 Thread 类
* 1. 创建一个类继承 Thread 类
* 2. 重写 run() 方法 -->> 将此线程需要的操作写在 run() 方法中
* 3. 创建实例,调用 start() 方法
* - 启动当前线程
* - 调用 run() 方法
* 注意: 一个线程对象只能 start() 一次
*
* @author geekfx
* @create 2020/4/1 11:48
*/
class MyFirstThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName());
}
}
}
}
public class ThreadTest extends Thread {
public static void main(String[] args) {
MyFirstThread thread = new MyFirstThread();
thread.start();
MyFirstThread thread1 = new MyFirstThread();
thread1.start();
// 以创建匿名子类的方式创建一个线程
new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName());
}
}
}.start();
for (int i = 0; i < 100; i++) {
if (i % 2 != 0)
System.out.println(Thread.currentThread().getName());
}
}
}
- 方式二
- 定义一个类实现 Runnable 接口
- 实现 run() 方法
- 创建对象实例 target
- 将此对象实例放入到 Thread(Runnable target) 构造器中 得到线程对象
- 调用创建线程的 start() 方法
- 启动当前线程
- 调用 target 的 run() 方法
方式二代码
package work.jkfx.thread.create;
/**
* 创建线程方式二
* 1. 定义一个类实现 Runnable 接口
* 2. 实现 run() 方法
* 3. 创建对象实例 target
* 4. 将此对象实例放入到 Thread(Runnable target) 构造器中 得到线程对象
* 5. 调用创建线程的 start() 方法
* - 启动当前线程
* - 调用 target 的 run() 方法
*
* @author geekfx
* @create 2020/4/1 14:14
*/
class CreateThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
public class CreateThreadTest {
public static void main(String[] args) {
CreateThread createThread = new CreateThread();
Thread thread = new Thread(createThread);
Thread thread1 = new Thread(createThread);
thread.setName("线程1");
thread1.setName("线程2");
thread.start();
thread1.start();
}
}
线程常用方法
- start() 启动当前线程,调用当前线程的 run()
- run() 通常需要重写 Thread 类中的此方法
- currentThread() 静态方法 返回执行当前代码的线程
- getName() 获得当前线程的名字
- setName() 设置当前线程的名字
- yield() 释放当前线程所占cpu资源(有可能分配下一个线程又分配回来)
- join() 在线程中 a 中调用线程 b 的 join() 方法,此时线程 a 进入阻塞状态,直到线程 b 完全执行结束后,线程 a 结束阻塞(但不立即执行[就绪状态])
- stop() 强制结束当前线程(已过时,不推荐使用)
- sleep(long millis) 静态方法 使当前线程睡眠 millis 毫秒(阻塞)
- isAlive() 判断当前线程是否存活
线程优先级
- 线程优先级有3个常量表示
- MAX_PRIORITY
- NORM_PRIORITY -->> 默认
- MIN_PRIORITY
- 获取/设置当前线程优先级
- getPriority()
- setPriority(int newPriority)
注意:并不是 100% 执行高优先级的线程,而是大概率执行。
线程安全问题
解决线程安全问题
- 方式一:同步代码块
synchronized(同步监视器) {
// 需要被同步的代码 -->> 访问共享数据的代码
}
说明:同步监视器 就是俗称的 锁 任何一个对象都可以充当锁
要求:所有线程共用一把锁
- 方式二:同步方法
将需要访问共享数据的代码放到一个方法中,将此方法的修饰符加一个 synchronized 即可
模拟三个窗口售100张票 使用继承 Thread 方式
package work.jkfx.thread.safe;
/**
* 模拟三个窗口售100张票 使用继承 Thread 方式
* 存在线程安全问题
*
* 解决线程安全问题
* - 方式一:同步代码块
* synchronized(同步监视器) {
* // 需要被同步的代码
* }
* - 方式二:同步方法
* * 使用 synchronized 修饰方法
*
* @author geekfx
* @create 2020/4/1 13:57
*/
class Window extends Thread {
private static int ticket = 100;
// private static Object lock = new Object(); 可以不需要 直接使用 类.class
@Override
public void run() {
while(true) {
// Window.class 只会加载一次
// 而且可以被多个对象实例共享
// synchronized(Window.class) {
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// if (ticket > 0) {
// System.out.println(getName() + ": " + ticket);
// ticket--;
// } else {
// break;
// }
// }
show();
}
}
private static synchronized void show() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ": " + ticket);
ticket--;
}
}
}
public class WindowTest {
public static void main(String[] args) {
Window w1 = new Window();
Window w2 = new Window();
Window w3 = new Window();
w1.setName("窗口1");
w2.setName("窗口2");
w3.setName("窗口3");
w1.start();
w2.start();
w3.start();
}
}
模拟三个窗口售票100张 使用实现 Runnable 接口方式
package work.jkfx.thread.safe;
/**
* 模拟三个窗口售票100张 使用实现 Runnable 接口方式
* 存在线程安全问题
*
* 解决线程安全问题
* 方式一:同步代码块
* synchronized(同步监视器) {
* // 需要被同步的代码 -->> 访问共享数据的代码
* }
* 说明:同步监视器 就是俗称的 锁 任何一个对象都可以充当锁
* 要求:所有线程共用一把锁
* 方式二:同步方法
* 将需要访问共享数据的代码放到一个方法中,将此方法的修饰符加一个 synchronized 即可
*
* @author geekfx
* @create 2020/4/1 14:27
*/
class WindowImpl implements Runnable {
private int ticket = 100;
// Object lock = new Object(); // 同步监视器 可以不需要
@Override
// synchronized 可以直接加在 run() 方法中
public void run() {
while(true) {
// 使用同步代码块解决线程安全问题
// 同步监视器可以不用建对象
// 可以直接用创建线程的对象来当锁
// synchronized(this) {
// if (ticket > 0) {
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread().getName() + ": " + ticket);
// ticket--;
// } else {
// break;
// }
// }
show();
}
}
private synchronized void show() {
// 使用同步方法解决线程安全问题
// 同步方法的同步监视器是 this
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": " + ticket);
ticket--;
}
}
}
public class WindowImplTest {
public static void main(String[] args) {
// 使用下面对象充当锁
WindowImpl window = new WindowImpl();
Thread t1 = new Thread(window);
Thread t2 = new Thread(window);
Thread t3 = new Thread(window);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
解决单例懒汉式模式的线程安全问题
package work.jkfx.thread.safe;
/**
* 解决单例懒汉式模式的线程安全问题
* @author geekfx
* @create 2020-04-02 18:36
*/
public class BankTest {
}
// 单例模式——懒汉式
class Bank {
private Bank() {}
private static Bank instance = null;
public static Bank getInstance() {
// 方式一:效率稍差
// synchronized (Bank.class) {
// if(instance == null) {
// instance = new Bank();
// }
// return instance;
// }
// 方式二
if(instance == null) {
synchronized(Bank.class) {
if(instance == null) {
instance = new Bank();
}
}
}
return instance;
}
}
死锁
演示死锁问题
解决死锁的方式:
- 专门的算法、原则
- 尽量减少同步资源的定义
- 尽量避免嵌套同步
package work.jkfx.thread.deadlock;
/**
* 演示死锁问题
*
* 解决死锁的方式:
* - 专门的算法、原则
* - 尽量减少同步资源的定义
* - 尽量避免嵌套同步
*
* @author geekfx
* @create 2020-04-02 19:19
*/
public class ThreadTest {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer("");
StringBuffer s2 = new StringBuffer("");
new Thread() {
// 使用匿名类的继承方式创建线程
@Override
public void run() {
synchronized(s1) {
s1.append("a");
s2.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(s2) {
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable() {
// 使用匿名类的实现方式创建进程
@Override
public void run() {
synchronized (s2) {
s1.append("c");
s2.append("3");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1) {
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
解决线程安全问题
- 方式三:使用 Lock 接口(ReentrantLock 实现类)
package work.jkfx.thread.deadlock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 解决线程安全问题
* - 方式三:使用 Lock 接口(ReentrantLock 实现类)
* @author geekfx
* @create 2020-04-02 19:40
*/
class Window implements Runnable {
private int ticket = 100;
/*
ReentrantLock 有两个构造器:
- ReentrantLock(boolean fair);
* 当 fair 为 true 时:创建公平锁
- 按照先进先出的方式处理等待的线程
- ReentrantLock();
* 无参构造器默认 fair 为 false
*/
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while(true) {
try {
lock.lock();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(ticket > 0) {
System.out.println(Thread.currentThread().getName() + ": " + ticket);
ticket--;
} else {
break;
}
} finally {
lock.unlock();
}
}
}
}
public class LockTest {
public static void main(String[] args) {
Window window = new Window();
Thread t1 = new Thread(window);
Thread t2 = new Thread(window);
Thread t3 = new Thread(window);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
死锁的演示代码
package work.jkfx.thread.deadlock;
//死锁的演示
class A {
public synchronized void foo(B b) { //同步监视器:A类的对象:a
System.out.println("当前线程名: " + Thread.currentThread().getName()
+ " 进入了A实例的foo方法"); // ①
try {
Thread.sleep(200);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("当前线程名: " + Thread.currentThread().getName()
+ " 企图调用B实例的last方法"); // ③
b.last();
}
public synchronized void last() {//同步监视器:A类的对象:a
System.out.println("进入了A类的last方法内部");
}
}
class B {
public synchronized void bar(A a) {//同步监视器:b
System.out.println("当前线程名: " + Thread.currentThread().getName()
+ " 进入了B实例的bar方法"); // ②
try {
Thread.sleep(200);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("当前线程名: " + Thread.currentThread().getName()
+ " 企图调用A实例的last方法"); // ④
a.last();
}
public synchronized void last() {//同步监视器:b
System.out.println("进入了B类的last方法内部");
}
}
public class DeadLock implements Runnable {
A a = new A();
B b = new B();
public void init() {
Thread.currentThread().setName("主线程");
// 调用a对象的foo方法
a.foo(b);
System.out.println("进入了主线程之后");
}
public void run() {
Thread.currentThread().setName("副线程");
// 调用b对象的bar方法
b.bar(a);
System.out.println("进入了副线程之后");
}
public static void main(String[] args) {
DeadLock dl = new DeadLock();
new Thread(dl).start();
dl.init();
}
}
线程通信
线程通信设计的三个方法:
- wait() 一旦执行此方法,当前进程进入阻塞状态,并且释放同步监视器(锁)。
- notify() 执行此方法,唤醒一个被 wait() 的线程,如果阻塞的线程有多个,唤醒一个优先级最高的。
- notifyAll() 执行此方法,唤醒所有被 wait() 的线程
说明:
- 三个方法必须放在同步代码块或同步方法中
- 三个方法的调用者必须是同步代码块/同步方法的同步监视器
- 三个方法都是定义在 java.lang.Object 类下
package work.jkfx.thread.communication;
/**
* 使两个线程交替打印输出 1-100
* 线程通信设计的三个方法:
* - wait() 一旦执行此方法,当前进程进入阻塞状态,并且释放同步监视器(锁)。
* - notify() 执行此方法,唤醒一个被 wait() 的线程,如果阻塞的线程有多个,唤醒一个优先级最高的。
* - notifyAll() 执行此方法,唤醒所有被 wait() 的线程
* 说明:
* - 三个方法必须放在同步代码块或同步方法中
* - 三个方法的调用者必须是同步代码块/同步方法的同步监视器
* - 三个方法都是定义在 java.lang.Object 类下
*
* @author geekfx
* @create 2020-04-02 21:06
*/
class Communication implements Runnable {
private int number = 1;
@Override
public void run() {
while (true) {
synchronized (this) {
notify();
if(number <= 100) {
System.out.println(Thread.currentThread().getName() + ": " + number);
number++;
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
}
}
}
public class CommunicationTest {
public static void main(String[] args) {
Communication communication = new Communication();
Thread t1 = new Thread(communication);
Thread t2 = new Thread(communication);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
JDK5.0 新增两种线程创建方式
- 方式一:实现 Callable 接口
- 创建一个类实现 Callable 接口 此接口比 Runnable 强大 支持泛型
- 实现 cal() 方法,此方法类似 run() 方法但有返回值而且可以抛出异常
- 创建实现类的对象
- 将实现类对象作为参数传递到 FutureTask 类的构造器中,得到 FutureTask 对象
- 将 FutureTask 对象作为参数传递到 Thread 构造器中
- 调用 start() 方法启动线程
- 完成任务后,可以调用 FutureTask 对象的 get() 方法来获得实现类实现的 cal() 方法的返回值
说明:
- FutureTask 类实现了 Runnable、Future 接口,并且是 Future 接口的唯一实现类
- 它既可以作为 Runnable 被线程执行,也可以作为 Future 得到 Callable 的返回值
方式一代码
package work.jkfx.thread.create;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* JDK5.0 新增两种创建线程的方式:
* - 方式一:实现 Callable 接口
* (1) 创建一个类实现 Callable 接口 此接口比 Runnable 强大 支持泛型
* (2) 实现 cal() 方法,此方法类似 run() 方法但有返回值而且可以抛出异常
* (3) 创建实现类的对象
* (4) 将实现类对象作为参数传递到 FutureTask 类的构造器中,得到 FutureTask 对象
* (5) 将 FutureTask 对象作为参数传递到 Thread 构造器中
* (6) 调用 start() 方法启动线程
* (7) 完成任务后,可以调用 FutureTask 对象的 get() 方法来获得实现类实现的 cal() 方法的返回值
* * 说明:
* (1) FutureTask 类实现了 Runnable、Future 接口,并且是 Future 接口的唯一实现类
* (2) 它既可以作为 Runnable 被线程执行,也可以作为 Future 得到 Callable 的返回值
*
* @author geekfx
* @create 2020-04-02 22:35
*/
//1. 创建类,实现 Callable 接口并实现 cal() 方法
class NewCreate implements Callable {
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 0; i < 101; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ": " + i);
sum += i;
}
}
return sum;
}
}
public class NewCreateTest {
public static void main(String[] args) {
//2. 创建实现类的对象
NewCreate newCreate = new NewCreate();
//3. 将实现类对象作为参数传递到 FutureTask 构造器中,得到 FutureTask 对象
FutureTask futureTask = new FutureTask(newCreate);
//4. 将 FutureTask 对象作为参数传递到 Thread 构造器中,创建线程对象
Thread thread = new Thread(futureTask);
thread.setName("新线程");
//5. 执行 start() 方法启动线程
thread.start();
//6. 可以调用 FutureTask 对象的 get() 方法来获得实现类的 cal() 方法的返回值
try {
Object o = futureTask.get();
System.out.println("cal() 方法的返回值: " + o);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
- 方式二:使用线程池
提前创建好多个线程,放入线程池;使用时直接获取,使用后放回,避免了频繁的创建销毁,实现了重复利用;便于线程管理。
- 调用工具类 Executors.newFixedThreadPool(int nThreads) 返回一个容量为 nThreads 个线程的线程池
- 返回一个 (Executor[interface] -> ExecutorService[interface] -> AbstractExecutorService[class] -> ThreadPoolExcutor) 对象
- 调用 execute()/submit() 方法传入一个实现了 Runnable/Callable 接口的方法来启动线程,后者可以有返回值,返回值为实现 cal() 方法的返回值
- 调用 shutdown() 关闭线程池
方式二代码
package work.jkfx.thread.create;
import java.util.concurrent.*;
/**
* JDK5.0 新增两种线程创建方式
* - 方式二:使用线程池
* 提前创建好多个线程,放入线程池;使用时直接获取,使用后放回,避免了频繁的创建销毁,实现了重复利用;便于线程管理。
* (1) 调用工具类 Executors.newFixedThreadPool(int nThreads) 返回一个容量为 nThreads 个线程的线程池
* (2) 返回一个 (Executor[interface] -> ExecutorService[interface] -> AbstractExecutorService[class] -> ThreadPoolExcutor) 对象
* (3) 调用 execute()/submit() 方法传入一个实现了 Runnable/Callable 接口的方法来启动线程,后者可以有返回值,返回值为实现 cal() 方法的返回值
* (4) 调用 shutdown() 关闭线程池
*
* @author geekfx
* @create 2020-04-02 23:30
*/
public class ThreadPool {
public static void main(String[] args) {
//1. 使用工具类 Executors 创建进程池
ExecutorService executorService = Executors.newFixedThreadPool(15);
// 实际上返回的对象是 ThreadPoolExecutor 对象
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executorService;
// 由于 ExecutorService 方法不多不便于线程池管理 返回的对象实际上是 ThreadPoolExecutor 有很多管理线程池的方法
// 调用 execute() 方法传入一个实现了 Runnable 接口的方法
// 此方法没有返回值、不能抛出异常、不支持泛型
executorService.execute(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
});
// 调用 submit() 传入一个实现了 Callable 接口的类
// 使用此方法可以有返回值、可以抛出异常、可以使用泛型
// 此方法返回一个 FutureTask 对象
Future<Integer> future = threadPoolExecutor.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + ": " + i);
sum += i;
}
}
return sum;
}
});
try {
Integer sum = future.get();
System.out.println("使用 submit() 方法启动线程得到的返回值: " + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
// 关闭线程池
executorService.shutdown();
}
}
经典线程同步问题 生产者-消费者问题
package work.jkfx.thread.exer;
/**
* 经典线程同步问题 生产者-消费者问题
* 假设产品最多存放 20 个
*
* @author geekfx
* @create 2020-04-02 21:41
*/
// 店员 最多存放 20 个产品
class Clerk {
private int productNum = 0;
// 生产产品
public synchronized void produceProduct() {
if(productNum < 20) {
productNum++;
System.out.println(Thread.currentThread().getName() + ": " + productNum);
notify();
} else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 消费产品
public synchronized void consumeProduct() {
if(productNum > 0) {
System.out.println(Thread.currentThread().getName() + ": " + productNum);
productNum--;
notify();
} else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 生产者
class Producer extends Thread {
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(getName() + " 开始生产产品");
while (true) {
try {
Thread.sleep(80);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.produceProduct();
}
}
}
// 消费者
class Consumer extends Thread {
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(getName() + " 开始消费产品");
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.consumeProduct();
}
}
}
public class ProductTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer p1 = new Producer(clerk);
p1.setName("生产者1");
Consumer c1 = new Consumer(clerk);
c1.setName("消费者1");
// Consumer c2 = new Consumer(clerk);
// c2.setName("消费者2");
p1.start();
c1.start();
// c2.start();
}
}
线程安全问题练习:银行账户
package work.jkfx.thread.exer;
import java.util.concurrent.locks.ReentrantLock;
/**
* 线程安全问题测试:银行账户
* 一个银行账户有 3000 元,两个账户往里存钱,每个账户存 3 次,每次存 1000,存完打印余额
*
* @author geekfx
* @create 2020-04-02 19:57
*/
class Account {
private double balance;
public Account(double balance) {
this.balance = balance;
}
public void save(double money) {
balance += money;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "存钱 余额: " + balance);
}
}
class Customer extends Thread {
private Account account;
// 如果使用 ReentrantLock 在继承子类的线程类的方式解决线程安全问题
// 需要将锁设置为静态
private static ReentrantLock lock = new ReentrantLock();
public Customer(Account account) {
this.account = account;
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
lock.lock();
account.save(1000);
lock.unlock();
}
}
}
public class BankAccountTest {
public static void main(String[] args) {
Account account = new Account(3000);
Customer c1 = new Customer(account);
Customer c2 = new Customer(account);
c1.setName("甲");
c2.setName("乙");
c1.start();
c2.start();
}
}