目录
- 1、进程和线程
- 1.1 进程的介绍
- 1.2 并行和并发
- 1.3 线程的介绍
- 2、JAVA开启线程的三种方法
- 2.1 继承Thread类:
- 2.2 实现Runnable接口
- 2.3 实现Callable接口
- 2.4 总结:
- 3、线程相关方法
- 3.1 获取和设置线程名字的方法
- 3.2 线程休眠方法:
- 3.3 线程优先级方法:
- 3.4 守护线程
- 4、线程安全和同步
- 4.1 案例
- 4.2 安全和同步问题的解决:
- 4.2.1 同步代码块:
- 4.2.2 同步方法:
- 4.2.3 Lock锁:
- 5、线程通信
- 5.1 两条线程的通信
- 5.2 三条线程的通信
- 5.3 等待唤醒机制
- 5.4 生产者消费者模式
- 5.5 总结
- 6、线程生命周期
- 7、线程池
- 7.1 介绍:
- 7.2 使用线程池:
- 7.2.1 使用JDK提供的线程池:
- 7.2.2 自定义线程池:
- 8、单例设计模式
1、进程和线程
1.1 进程的介绍
1.2 并行和并发
1.3 线程的介绍
2、JAVA开启线程的三种方法
JAVA开启线程方法一共三种,如下所示:
2.1 继承Thread类:
package com.itheima.thread;
public class ThreadDemo1 {
/**
* 开启线程的第一种方式:继承Thread类
* 1、编写一个类继承Thread
* 2、重写run方法
* 3、将线程任务代码写在run方法中
* 4、创建线程对象
* 5、调用start方法开启线程
*
* 细节:调用start方法开启线程,会自动的调用run方法执行
* 注意:只有调用了start方法,才是开启了新的线程
*/
public static void main(String[] args) {
//4、创建线程对象
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
//5、调用start方法开启线程
myThread1.start();
myThread2.start();
}
}
//1、编写一个类继承Thread
class MyThread extends Thread{
//2、重写run方法
@Override
public void run() {
//3、将线程任务代码写在run方法中
for (int i = 1; i <= 200; i++) {
System.out.println("线程执行了:" + i + "次");
}
}
}
注:Java程序默认是多线程的,程序启动后默认会存在两条线程。
- 1、主线程;
- 2、垃圾回收线程
package com.itheima.thread;
public class ThreadDemo2 {
/**
* Java程序默认是多线程的,程序启动后默认会存在两条线程
* 1、主线程
* 2、垃圾回收线程
*/
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
for (int i = 0; i < 2000; i++) {
System.out.println("主程序运行!");
}
// //制造垃圾
// for (int i = 0; i < 5000000; i++) {
// new Demo();
// }
}
}
class Demo{
/**
* 内存中不再使用的对象被称为垃圾,如果垃圾被回收了,则会调用finalize方法
*/
@Override
protected void finalize() throws Throwable {
System.out.println("垃圾被清理了");
}
}
2.2 实现Runnable接口
package com.itheima.thread;
public class ThreadDemo3 {
/**
* 开启线程的第二种方式:实现Runnable接口
* 1、编写一个类实现Runnable接口
* 2、重写run方法
* 3、将线程任务代码写在run方法中
* 4、创建线程任务资源
* 5、创建线程对象,将资源传入
* 6、使用线程对象调用start方法,开启线程
*
* 优势:如果一个类继承了其他父类,那就不能再继承Thread类了(java是单继承),但可以实现多个接口,因此开启线程可以实现Runnable接口
*/
public static void main(String[] args) {
//4、创建线程任务资源
MyRunnable myRunnable = new MyRunnable();
//5、创建线程对象,将资源传入
Thread t = new Thread(myRunnable);
//6、使用线程对象调用start方法,开启线程
t.start();
for (int i = 0; i < 10000; i++) {
System.out.println("主线程执行了!");
}
}
}
//1、编写一个类实现Runnable接口
class MyRunnable implements Runnable{
//2、重写run方法
@Override
public void run() {
//3、将线程任务代码写在run方法中
for (int i = 0; i < 200; i++) {
System.out.println("线程任务执行了" + i);
}
}
}
上述方法可以使用匿名内部类的方式实现,如下所示:
package com.itheima.thread;
public class ThreadDemo {
/**
* 使用匿名内部类的方式开启线程:
*/
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("使用匿名内部类方式开启线程" + i);
}
}
});
t1.start();
}
}
2.3 实现Callable接口
package com.itheima.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class ThreadDemo4 {
/**
* 开启线程的第三种方式:实现Callable接口
* 1、编写一个类实现Callable接口
* 2、重写call方法
* 3、将线程任务代码写在call方法中
* 4、创建线程任务资源对象
* 5、创建线程任务对象,封装线程资源
* 6、创建线程对象,传入线程任务
* 7、使用线程对象调用start开启线程
*
* 优势:可以获取返回值
*/
public static void main(String[] args) throws Exception {
//4、创建线程任务资源对象
MyCallable mc = new MyCallable();
//5、创建线程任务对象,封装线程资源
FutureTask<Integer> task1 = new FutureTask<>(mc);//开启第一个线程
FutureTask<Integer> task2 = new FutureTask<>(mc);//开启第二个线程
//6、创建线程对象,传入线程任务
Thread th1 = new Thread(task1);
Thread th2 = new Thread(task2);
//7、使用线程对象调用start开启线程
th1.start();
th2.start();
Integer result1 = task1.get();
Integer result2 = task2.get();
System.out.println("task1获取到的结果为:" + result1);
System.out.println("task2获取到的结果为:" + result2);
}
}
//1、编写一个类实现Callable接口
class MyCallable implements Callable<Integer>{
//2、重写call方法
@Override
public Integer call() throws Exception {
//3、将线程任务代码写在call方法中
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
System.out.println("sum=" + sum);
}
return sum;
}
}
这第三种方式也可以使用匿名内部类的方式实现:
package com.itheima.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class ThreadDemo7 {
/**
* Callable开启线程,也可以使用匿名内部类的方式实现:
*/
public static void main(String[] args) {
Thread t = new Thread(new FutureTask<Integer>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 200; i++) {
sum += i;
System.out.println("使用匿名内部类方式开启线程" + i);
}
return sum;
}
}));
t.start();
}
}
2.4 总结:
上述三种方法,最推荐第二种,尤其使用匿名内部类的方式很方便。如果需要返回值的话,那就只能选择第三种方法了。
3、线程相关方法
3.1 获取和设置线程名字的方法
package com.itheima.thread;
public class ThreadDemo5 {
/**
* 线程设置名字和获取名字
*
* Thread类的方法:
* 1、public String getName():获取线程名字
* 2、public void setName():设置线程名字
* --注意:也可以创建对象时直接用构造方法设置线程名字(需要写带参构造方法)
* 3、public Static Thread currentThread():获取当前线程的对象
*/
public static void main(String[] args) {
TestThread t1 = new TestThread("A:");//也可以创建对象时直接用构造方法设置线程名字
TestThread t2 = new TestThread("B:");
//2、public void setName():设置线程名字
// t1.setName("线程一:");
// t2.setName("线程二:");
t1.start();
t2.start();
}
}
class TestThread extends Thread{
@Override
public void run() {
for (int i = 1; i <= 2000; i++) {
//1、public String getName():获取线程名字
System.out.println(super.getName() + "线程执行了" + i);
//3、public Static Thread currentThread():获取当前线程的对象
System.out.println(Thread.currentThread().getName() + "是通过currentThread方法获取的" + i);
}
}
public TestThread(String name) {
super(name);
}
public TestThread() {
}
}
3.2 线程休眠方法:
package com.itheima.thread;
public class ThreadDemo6 {
/**
* 休眠线程的方法
* public static void sleep(long time):让线程休眠指定的时间,单位为毫秒
*/
public static void main(String[] args) throws InterruptedException {
for (int i = 5; i >= 0; i--) {
System.out.println("倒计时" + i + "秒");
Thread.sleep(1000);//休眠一秒
}
}
}
3.3 线程优先级方法:
为什么会有优先级的说法,先看下线程的调度方式:
简单来说:
(1)抢占式调度就是哪个线程抢到就执行哪个线程—随机;
(2)非抢占式调度则是轮流让每个线程执行—轮流使用;
Java使用的是抢占式调度,我们使用线程优先级方法,可以提高抢占的概率。下面来看具体的方法:
package com.itheima.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class ThreadDemo7 {
/**
* 线程优先级的方法:
* public setPriority(int newPriority):设置线程优先级,1~10之间,1最高、10最低
* public final int getPriority():获取线程优先级,不设置默认为5
*/
public static void main(String[] args) {
//1、使用匿名内部类的方式定义两个线程
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
}
}
}, "线程A:");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
}
}
}, "线程B:");
//2、setPriority(int newPriority):设置线程优先级
t1.setPriority(1);//优先级设置为1
t2.setPriority(10);//优先级设置为10
//3、getPriority():获取线程优先级
System.out.println(t1.getPriority());
System.out.println(t2.getPriority());
//4、开启线程,优先级越高越容易先执行
t1.start();
t2.start();
}
}
3.4 守护线程
在Java中,线程有两种类型:用户线程和守护线程。守护线程是为了支持用户线程而存在的,他们通常用于执行一些后台任务,如垃圾回收、监控等。与用户线程不同,守护线程在没有其他用户线程运行时会自动终止。
package com.itheima.thread;
public class ThreadDemo8 {
/**
* 守护线程
* public final void setDaemon(boolean on):设置为守护线程
*
* 注意:守护线程会随着所有非守护线程的结束而结束,所以如果有两条非守护线程,如果其中一条线程结束了,但守护线程不会结束。
*/
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 20; i++) {//线程A运行20次
System.out.println(Thread.currentThread().getName() + "运行了" + i);
}
}
}, "线程A:");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 200; i++) {//线程B运行200次
System.out.println(Thread.currentThread().getName() + "运行了" + i);
}
}
}, "线程B:");
//将线程B设置为守护线程
t2.setDaemon(true);
t1.start();
t2.start();
}
}
4、线程安全和同步
4.1 案例
线程安全和同步问题,我们使用案例来介绍:
先按照之前学习的知识,来编写代码,如下所示:
package com.itheima.thread.lock;
public class ThreadTest1 {
/**
* 需求:某电影院目前正在上映国产大片,共有100张票,而他有3个窗口卖票,请设计一个程序模拟该电影院卖票
* -- 多条线程共享同一份资源,就会出现线程不安全问题(如本案例中,三个线程会卖同一张票,这是有问题的)
*/
public static void main(String[] args) {
TicketsTask ticketsTask = new TicketsTask();
Thread t1 = new Thread(ticketsTask, "线程1:");
Thread t2 = new Thread(ticketsTask, "线程2:");
Thread t3 = new Thread(ticketsTask, "线程3:");
t1.start();
t2.start();
t3.start();
}
}
class TicketsTask implements Runnable{
int ticket = 100;
@Override
public void run() {
while (ticket != 0) {
System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket + "号票");
ticket--;
}
}
}
上述代码是不安全的,(1)票号有可能变成负数,(2)三个线程会卖同一张票,如下所示的结果:
(1)针对上面变成负数做,如下解释:
(2)针对上面三个线程会卖同一张票,如下解释:
上述两个问题,就是因为代码在运行过程中,cpu在来回切换运行这三个线程导致的,怎么解决呢?
答:当这一段逻辑在某一个线程运行时,先给这段代码上锁,确保运行完后,其他线程再来运行即可解决。
那如何上锁呢,接着看:
4.2 安全和同步问题的解决:
有三种方法上锁,我们一个一个看:
4.2.1 同步代码块:
package com.itheima.thread.lock;
public class ThreadTest1 {
/**
* 需求:某电影院目前正在上映国产大片,共有100张票,而他有3个窗口卖票,请设计一个程序模拟该电影院卖票
* -- 多条线程共享同一份资源,就会出现线程不安全问题(如本案例中,三个线程会卖同一张票,这是有问题的)
*/
public static void main(String[] args) {
TicketsTask ticketsTask = new TicketsTask();
Thread t1 = new Thread(ticketsTask, "线程1:");
Thread t2 = new Thread(ticketsTask, "线程2:");
Thread t3 = new Thread(ticketsTask, "线程3:");
t1.start();
t2.start();
t3.start();
}
}
class TicketsTask implements Runnable{
private final Object o = new Object();
int ticket = 100000;
@Override
public void run() {
//同步代码块:把需要上锁的代码放在里面
//虽然锁对象可以是任意对象,但是要注意:锁对象需要唯一,即静态化。既然这样,可以使用字节码文件TicketsTask.class作为锁对象,好处:唯一且方便
synchronized (TicketsTask.class){
while (ticket != 0) {
System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket + "号票");
ticket--;
}
}
}
}
4.2.2 同步方法:
package com.itheima.thread.lock;
public class ThreadTest2 {
/**
* 同步方法:在方法的返回值类型前面加入 synchronized 关键字
*
* public synchronized void method(){ }
*
* 同步方法的锁对象:
* 1、非静态的方法:this
* 2、静态的方法:类的字节码对象
*/
public static void main(String[] args) {
TicketsTask2 ticketsTask = new TicketsTask2();
Thread t1 = new Thread(ticketsTask, "线程1:");
Thread t2 = new Thread(ticketsTask, "线程2:");
Thread t3 = new Thread(ticketsTask, "线程3:");
t1.start();
t2.start();
t3.start();
}
}
class TicketsTask2 implements Runnable{
private int ticket = 1000;
@Override
public void run() {
while (true) {
if (method()) break;
}
}
/*
* 同步方法:在方法的返回值类型前面加入 synchronized 关键字
*/
private synchronized boolean method() {
if (ticket == 0){
return true;
}
System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket + "号票");
ticket--;
return false;
}
}
4.2.3 Lock锁:
package com.itheima.thread.lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadTask3 {
/**
* 互斥锁 ReentrantLock()
* @param args
*/
public static void main(String[] args) {
TicketsTask3 ticketsTas = new TicketsTask3();
Thread thread1 = new Thread(ticketsTas, "线程1");
Thread thread2 = new Thread(ticketsTas, "线程2");
Thread thread3 = new Thread(ticketsTas, "线程3");
thread1.start();
thread2.start();
thread3.start();
}
}
class TicketsTask3 implements Runnable{
private int tickets = 100;
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try {
//上锁,以下代码会被锁住
lock.lock();
if (tickets == 0){
break;
}
System.out.println(Thread.currentThread().getName() + "卖出了" + tickets + "张票");
tickets--;
} finally {
//解锁,放在finally是因为无论如何都会执行到
lock.unlock();
}
}
}
}
5、线程通信
5.1 两条线程的通信
我们先看下如果两条线程没有通信会出现什么情况,如下所示的代码,就是谁抢到,谁就执行:
package com.itheima.correspondence;
public class CorrespondenceDemo1 {
/**
* 两条线程通信
*
*/
public static void main(String[] args) {
Prints p = new Prints();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
synchronized (Prints.class){//同步代码块:在这里面的方法会执行完,才会切换CPU到其他线程
p.print1();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
synchronized (Prints.class){
p.print2();
}
}
}
}).start();
}
}
class Prints{
public void print1(){
System.out.print("传");
System.out.print("智");
System.out.print("教");
System.out.print("育");
System.out.println();
}
public void print2(){
System.out.print("黑");
System.out.print("马");
System.out.print("程");
System.out.print("序");
System.out.print("员");
System.out.println();
}
}
上述代码运行结果如下,发现这两个线程是没有顺序的,因此
如何解决上述两个线程随机运行的问题呢,也就是说这两个线程,如何按我们想要的特定顺序执行呢,我们接着看:
package com.itheima.correspondence;
public class CorrespondenceDemo1 {
/**
* 两条线程通信
*
*/
public static void main(String[] args) {
Prints p = new Prints();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
synchronized (Prints.class){//同步代码块:在这里面的方法会执行完,才会切换CPU到其他线程
try {
p.print1();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
synchronized (Prints.class){
try {
p.print2();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}).start();
}
}
class Prints{
//标记为1线程1执行,标记为2线程2执行
int flag = 1;
public void print1() throws InterruptedException {
if (flag != 1){
//线程1等待
Prints.class.wait();//锁对象调用等待方法
}
System.out.print("传");
System.out.print("智");
System.out.print("教");
System.out.print("育");
System.out.println();
flag = 2;
//唤醒线程2
Prints.class.notify();//锁对象调用唤醒方法。注意这个方法会随机唤醒一个线程,也就是下次有可能还会唤醒线程1
}
public void print2() throws InterruptedException {
if (flag != 2){
//线程2等待
Prints.class.wait();//锁对象调用等待方法
}
System.out.print("黑");
System.out.print("马");
System.out.print("程");
System.out.print("序");
System.out.print("员");
System.out.println();
flag = 1;
//唤醒线程1
Prints.class.notify();//锁对象调用唤醒方法。注意这个方法会随机唤醒一个线程,也就是下次有可能还会唤醒线程2
}
}
5.2 三条线程的通信
上述是两条线程的通信,那三条线程怎么通信呢,我们接着看:
假设我们按上述处理两条线程那样,处理三条线程的通信问题,如下代码:
package com.itheima.correspondence;
public class CorrespondenceDemo2 {
/**
* 三条线程通信
*/
public static void main(String[] args) {
Print2 p = new Print2();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
synchronized (Print2.class){
try {
p.print1();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
synchronized (Print2.class){
try {
p.print2();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
synchronized (Print2.class){
try {
p.print3();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}).start();
}
}
class Print2{
int flag = 1;
public void print1() throws InterruptedException {
if (flag != 1){
Print2.class.wait();//线程1等待
}
System.out.print("传");
System.out.print("智");
System.out.print("教");
System.out.print("育");
System.out.println();
flag = 2;//让线程2执行
//随机唤醒一条线程
Print2.class.notify();
}
public void print2() throws InterruptedException {
if (flag != 2){
Print2.class.wait();//线程2等待
}
System.out.print("黑");
System.out.print("马");
System.out.print("程");
System.out.print("序");
System.out.print("员");
System.out.println();
flag = 3;//让线程3执行
//随机唤醒一条线程
Print2.class.notify();
}
public void print3() throws InterruptedException {
if (flag != 3){
Print2.class.wait();//线程3等待
}
System.out.print("传");
System.out.print("智");
System.out.print("大");
System.out.print("学");
System.out.println();
flag = 1;//让线程1执行
//随机唤醒一条线程
Print2.class.notify();
}
}
运行结果:
发现并没有按照1,2,3的顺序执行,我们分析下原因:
(1)假设在程序开始运行时,线程2
抢到了CPU执行权,由于flag=1
,因此线程2
进入等待状态;
(2)然后CPU释放出资源,这时线程1
和线程3
会抢占执行权,假设线程3
抢到了CPU的执行权,由于flag=1
,因此线程3
进入等待状态;
(3)由于此时线程2
和线程3
都进入了等待状态,因此线程1
会抢占到CPU执行权。由于flag=1
,因此线程1
会执行完(设置flag=2
,并随机唤醒一条线程);
(4)假设此时随机唤醒了线程3
,那么线程3
就会从等待状态的地方继续向下运行,此时我们发现线程3
运行完,并设置flag=1
,如果此时线程1抢到执行权,那么就会运行线程1
;
(5)代码运行到此时,我们发现线程运行顺序如下:线程1——》线程3——》线程1…
原因就是notify()
这个方法是随机唤醒一个线程。我们换如下所示的唤醒线程的方法:
package com.itheima.correspondence;
public class CorrespondenceDemo2 {
/**
* 三条线程通信
*/
public static void main(String[] args) {
Print2 p = new Print2();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
synchronized (Print2.class){
try {
p.print1();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
synchronized (Print2.class){
try {
p.print2();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
synchronized (Print2.class){
try {
p.print3();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}).start();
}
}
class Print2{
int flag = 1;
public void print1() throws InterruptedException {
while (flag != 1){//把if换成while,是为了当线程唤醒时,再重新判断一次条件
Print2.class.wait();//线程1等待
}
System.out.print("传");
System.out.print("智");
System.out.print("教");
System.out.print("育");
System.out.println();
flag = 2;//让线程2执行
//随机唤醒一条线程
Print2.class.notifyAll();
}
public void print2() throws InterruptedException {
while (flag != 2){//把if换成while,是为了当线程唤醒时,再重新判断一次条件
Print2.class.wait();//线程2等待
}
System.out.print("黑");
System.out.print("马");
System.out.print("程");
System.out.print("序");
System.out.print("员");
System.out.println();
flag = 3;//让线程3执行
//随机唤醒一条线程
Print2.class.notifyAll();
}
public void print3() throws InterruptedException {
while (flag != 3){//把if换成while,是为了当线程唤醒时,再重新判断一次条件
Print2.class.wait();//线程3等待
}
System.out.print("传");
System.out.print("智");
System.out.print("大");
System.out.print("学");
System.out.println();
flag = 1;//让线程1执行
//随机唤醒一条线程
Print2.class.notifyAll();
}
}
运行结果:
发现是按照我们想要的顺序执行的。
注意:
(1)但是每次都是把所有的线程唤醒,运行效率很低。。。
(2)同步代码块技术不是说不会切换到其他线程么,那为啥wait方法运行后,就会切换呢,答:
5.3 等待唤醒机制
上面虽然可以解决线程通信问题,但是效率太低,本节就解决效率低的问题。
直接上代码,看注释即可:
package com.itheima.correspondence;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class CorrespondenceDemo3 {
/**
* 三条线程通信 --优化
*/
public static void main(String[] args) {
Print3 p = new Print3();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
try {
p.print1();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
try {
p.print2();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
try {
p.print3();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}).start();
}
}
class Print3{
ReentrantLock lock = new ReentrantLock();//锁对象
//获取锁状态
Condition c1 = lock.newCondition();
Condition c2 = lock.newCondition();
Condition c3 = lock.newCondition();
int flag = 1;
public void print1() throws InterruptedException {
lock.lock();//上锁
if (flag != 1){
//注意:第一次调用await()方法时,c1就会和线程1绑定
c1.await();//状态不为1,让线程1等待,并让c1绑定线程1
}
System.out.print("传");
System.out.print("智");
System.out.print("教");
System.out.print("育");
System.out.println();
flag = 2;//唤醒线程2
c2.signal();//注意:这里是指定线程2被唤醒,如果没有被绑定的话,是随机唤醒一个线程
lock.unlock();//释放锁
}
public void print2() throws InterruptedException {
lock.lock();//上锁
if (flag != 2){
c2.await();//状态不为2,让线程2等待,并让c2绑定线程2
}
System.out.print("黑");
System.out.print("马");
System.out.print("程");
System.out.print("序");
System.out.print("员");
System.out.println();
//唤醒线程3
flag = 3;
c3.signal();
lock.unlock();
}
public void print3() throws InterruptedException {
lock.lock();
if (flag != 3){
c3.await();//状态不为3,让线程3等待,并让c3绑定线程3
}
System.out.print("传");
System.out.print("智");
System.out.print("大");
System.out.print("学");
System.out.println();
//唤醒线程1
flag = 1;
c1.signal();
lock.unlock();
}
}
5.4 生产者消费者模式
下面看代码:
这个是共享数据区的代码:
package com.itheima.correspondence.producer_consumer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 共享数据区:注意,公共并静态,这样就可以使用类名直接调用了
*/
public class WareHouse {
public static boolean mark = false;
public static ReentrantLock lock = new ReentrantLock();
//
public static Condition producer = lock.newCondition();
public static Condition consumer = lock.newCondition();
}
这个是生产者代码:
package com.itheima.correspondence.producer_consumer;
public class Producer implements Runnable{
@Override
public void run() {
while (true){
WareHouse.lock.lock();//上锁
if (WareHouse.mark){
//true:说明有包子,线程进入等待状态
try {
WareHouse.producer.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else {
//false:没有包子,生产包子,生产后改变mark状态,唤醒消费者线程
System.out.println("生产者线程生产包子...");
WareHouse.mark = true;
WareHouse.consumer.signal();//生产完包子,唤醒消费者线程
}
WareHouse.lock.unlock();//释放锁
}
}
}
这个是消费者代码:
package com.itheima.correspondence.producer_consumer;
public class Consumer implements Runnable{
@Override
public void run() {
while (true){
WareHouse.lock.lock();
if (WareHouse.mark){
//true:说明有包子,开吃包子,吃完后改变mark状态,唤醒生产者线程
System.out.println("消费者线程吃包子...");
WareHouse.mark = false;
WareHouse.producer.signal();//吃完包子,唤醒生产者线程
}else {
//false:说明没有包子,线程进入等待状态
try {
WareHouse.consumer.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
WareHouse.lock.unlock();
}
}
}
测试代码:
package com.itheima.correspondence.producer_consumer;
public class Test {
public static void main(String[] args) {
new Thread(new Producer()).start();
new Thread(new Consumer()).start();
}
}
运行结果:
5.5 总结
6、线程生命周期
下面看看一个线程的生命周期流程图:
Java中的线程状态:
7、线程池
7.1 介绍:
7.2 使用线程池:
7.2.1 使用JDK提供的线程池:
package com.itheima.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolDemo1 {
/**
* JDK自带线程池
* Executors 中提供静态方法来创建线程池
* static ExecutorService newCachedThreadPool() 创建一个默认的线程池
* static newFixedThreadPool(int nThreads) 创建一个指定最多线程数量的线程池
* @param args
*/
public static void main(String[] args) {
//1、获取线程池
//ExecutorService pool = Executors.newCachedThreadPool();//此方法最多可以创建 2147483647(21亿)线程对象
ExecutorService pool = Executors.newFixedThreadPool(10);//此方法可以指定最多创建的线程对象
//2、提交线程任务
for (int i = 1; i < 100; i++) {
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "提交了线程任务");
}
});
}
//3、关闭池
pool.shutdown();
}
}
7.2.2 自定义线程池:
package com.itheima.pool;
import java.util.concurrent.*;
public class ThreadPoolDemo2 {
/**
* 自定义线程池对象
* 参数5:任务队列
* 1)有界队列 new ArrayBlockingQueue<>(10)
* 2)无界队列 new LinkedBlockingQueue<>()。注:最多21亿个任务数
*/
public static void main(String[] args) {
//1、获取线程池
ThreadPoolExecutor pool = new ThreadPoolExecutor(
2, //核心线程数量
5, //最大线程数量
60, //空闲时间
TimeUnit.SECONDS,//空闲单位
new ArrayBlockingQueue<>(10),//任务队列,即指定排队人数
Executors.defaultThreadFactory(),//线程对象任务工厂
new ThreadPoolExecutor.AbortPolicy()//拒绝策略
);
//2、提交线程池
for (int i = 0; i < 13; i++) {
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "提交了线程任务");
}
});
}
//3、关闭池
pool.shutdown();
}
}
8、单例设计模式
package com.itheima.single_design;
public class SingleDesignDemo2 {
/**
* 单例设计模式 -懒汉式(延迟加载模式)
* ------------------------------------------------
* class Single2{
* private Single2(){ //私有化构造方法,防止new对象
* }
*
* private static Single2 s;
*
* public static Single2 getInstance(){
* if (s == null) s = new Single2();//等调用对象时才new
* return s;
* }
* }
* 上述写法的弊端:在多线程并发操作的时候,有可能创建出多个对象
* -------------------------------------------------
* class Single2{
* private Single2(){ //私有化构造方法,防止new对象
* }
*
* private static Single2 s;
*
* public static Single2 getInstance(){
* synchronized (Single2.class){//给这段代码上锁,这样避免了多线程的操作
* if (s == null) s = new Single2();//等调用对象时才new
* return s;
* }
* }
* }
* 述写法的弊端:效率非常低,因为线程会多次被阻塞(线程阻塞通常是指一个线程在执行过程中暂停,以等待某个条件的触发)。
* 下述是正确的写法
*/
public static void main(String[] args) {
Single2 s1 = Single2.getInstance();
Single2 s2 = Single2.getInstance();
System.out.println(Single1.getInstance());
System.out.println(s1 == s2);//true:表明指向同一个地址
}
}
class Single2{
private Single2(){ //私有化构造方法,防止new对象
}
private static Single2 s;
public static Single2 getInstance(){
if (s == null){//这里加if,是为了减小线程被多次阻塞的次数,从而提高效率
synchronized (Single2.class){//给这段代码上锁,这样避免了多线程的操作
if (s == null) {
s = new Single2();//等调用对象时才new
}
}
}
return s;
}
}