线程:进程可进一步细化为线程,是一个程序内部的一条执行路径。
①若一个进程同一时间并行执行多个线程,就是支持多线程的。
②线程作为调度和执行的单位,每个线程拥有独立运行栈和程序计数器(pc),线程切换的开销小。
③一个进程(有一方法区、和堆)中的多个线程共享相同的内存单元/内存地址空间->它们从同意堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患。
进程:是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程——生命周期 比如运行中的QQ
程序:是为了完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码。
单核CPU:其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程任务。因为CPU时间单元特别短,因此感觉不出来。
如果是多核的话,才能更好的发挥多线程的效率。
一个Java应用程序java.exe 其实至少有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。
并发:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
并行:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。
为什么使用多线程(优点)?
1.提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
2.提高计算机系统CPU的利用率
3.改善程序结构。将即长又复杂的进程分为多个线程,独立运行,利于理解和修改。
什么时候使用多线程?
1.程序需要同时执行两个或多个任务。
2.程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
3.需要一些后台运行的程序时。
参考API文档
创建新执行线程有两种方法。
//声明为Thread子类
class MyThread extends Thread {
@Override
//重写Thread类中的run()
public void run() {
//遍历100以内的偶数
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//创建Thread子类的对象
MyThread m1 = new MyThread();
//该对象调用start()
m1.start();
//m1.start(); //错误
}
}
思考第一个问题:
如果我想要创建两个线程可不可以直接通过此对象调用两次start()呢?
当你调用两次start()就会报 -> IllegalThreadStateException
为什么会报这样一个错误呢?
证据一: 当启动线程时,如果threadStatus!=0就会抛出异常。换言之,线程的状态码等于0,正常调用start()方法。
private volatile int threadStatus = 0;
public synchronized void start() {
//NEW状态 指的是创建一个新的线程
/**
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
}
证据二: 此方法的注释也有说明:如果当前线程已经启动,多次启动线程是不合法的
MyThread m1 = new MyThread();
MyThread m2 = new MyThread();
m1.start();
m2.start();
思考第二个问题:
此对象调用run()方法可以启动线程吗?
答曰:不可以
查看API文档可知start()方法的作用:
1.使该线程开始执行;
2.Java 虚拟机调用该线程的 run 方法。
验证:
通过获取当前线程名,就可以知道是否开启了新的线程
//稍微改造一下上面的代码
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
可以看到此对象调用start()方法,它的线程名是Thread-0,这就说明启动了线程
而线程名是main,这就说明了是主线程(主方法)调用了run()方法。
总结:
练习:创建两个线程 其中一个线程遍历100以内的偶数,另一个线程遍历100以内的奇数
public class PracticeThreadTest {
public static void main(String[] args) {
PraThread p1 = new PraThread();
PraThread2 p2 = new PraThread2();
p1.start();
p2.start();
}
}
class PraThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
class PraThread2 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
例子1:
两种方式修改当前线程的名字:
class ThreadMethod extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
//方式一:
public ThreadMethod(String name) {
super(name);
}
}
public class ThreadMethodTest {
public static void main(String[] args) {
ThreadMethod t1 = new ThreadMethod("子线程");
//方式二:
//t1.setName("子线程");
t1.start();
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
例子2 测试yield():
测试结果: 正常情况来说两个线程是会交互的,多测试几次会明显的看到。
class ThreadMethod extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
if (i % 20 == 0) {
//当前线程释放CPU执行权
yield();
}
}
}
public ThreadMethod() {
super("子线程");
}
}
public class ThreadMethodTest {
public static void main(String[] args) {
ThreadMethod t1 = new ThreadMethod();
t1.start();
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
例子3 测试join():
class ThreadMethod extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
public ThreadMethod(String name) {
super(name);
}
}
public class ThreadMethodTest {
public static void main(String[] args) {
ThreadMethod t1 = new ThreadMethod("子线程");
t1.start();
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
if (i == 20) {
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
例子4 测试sleep():
class ThreadMethod extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
public ThreadMethod(String name) {
super(name);
}
}
public class ThreadMethodTest {
public static void main(String[] args) {
ThreadMethod t1 = new ThreadMethod("子线程");
t1.start();
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
例子5 测试isAlive()
class ThreadMethod extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
public ThreadMethod(String name) {
super(name);
}
}
public class ThreadMethodTest {
public static void main(String[] args) {
ThreadMethod t1 = new ThreadMethod("子线程");
t1.start();
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
if (i == 20) {
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
boolean alive = t1.isAlive();
System.out.println(alive);
}
}
测试结果:
说明当前线程执行完声明的逻辑后,就死亡了。
false
例子 测试:
Thread类中分别定义了最小、正常、最大优先级。
其中默认的优先级是NORM_PRIORITY = 5。
/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(getName()+ ":" + getPriority() +":" + i);
}
}
}
public MyThread(String name) {
super(name);
}
}
public class ThreadPriorityTest {
public static void main(String[] args) {
MyThread m1 = new MyThread("子线程");
//设置子线程的优先级
m1.setPriority(Thread.MAX_PRIORITY);
//启动线程 调用run()
m1.start();
//设置主线程的名字
Thread.currentThread().setName("主线程");
//设置主线程的优先级
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + i);
}
}
}
}
说明:高优先级的线程不一定要执行完,才能执行低优先级的线程,只是高优先级的线程抢占CPU执行权的概率高于低优先级的线程
练习:模拟窗口买票,创建三个窗口买票 总票数100张 使用继承Thread类
代码如下:
class Window extends Thread{
//三个窗口(线程)共享100张票
private static int ticket = 100;
@Override
public void run() {
while (true) {
if (ticket > 0) {
//这里调用sleep()目的是提高重票和错票的概率
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + ": 卖票,票号为" + ticket);
ticket--;
}else {
break;
}
}
}
}
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();
}
}
//1. 创建一个实现Runnable接口的类
class MyThread2 implements Runnable{
//2. 实现类去实现Runnable中的抽象方法 run()
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(i);
}
}
}
}
public class ThreadTest2 {
public static void main(String[] args) {
//3. 创建实现类的对象
MyThread2 m1 = new MyThread2();
//4. 该此对象作为参数传入Thread类的构造器中,创建Thread对象
Thread t1 = new Thread(m1);
//5. 通过Thread类的对象调用start() ①启动线程 ②调用当前线程run() -> 调用Runnable类型的target run()
t1.start();
}
}
为什么要将实现类的对象作为参数Thread类的构造器中,创建Thread对象?
源码:
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
可以看见Thread类中,不止通过无参构造一种方式去创建Thread类对象,还可以通过传入一个target参数去创建Thread类对象,在这里我们直接传入Runnable接口的子类去完成创建。
谁调动了run方法?
继承Thread类,调用start()方法,不仅可以启动线程,而且也调用了run()。
实现Runnable接口的子类,调用start()方法,启动线程后,是target调用了run()方法
源码:
@Override
public void run() {
//已经创建了一个实现Runnable接口的类,所以target不等于null,满足条件。
if (target != null) {
target.run();
}
}
练习:模拟窗口买票,创建三个窗口买票 总票数100张 创建实现Runnable接口的类。
代码如下:
class Window implements Runnable{
//这里区别于继承Thread类 不加static关键字 因为创建了一个对象本身就只有一份ticket属性
private int ticket = 100;
@Override
public void run() {
while (true) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": 卖票,票号为" + ticket);
ticket--;
}else {
break;
}
}
}
}
public class WindowTest {
public static void main(String[] args) {
Window w1 = new Window();
Thread t1 = new Thread(w1);
Thread t2 = new Thread(w1);
Thread t3 = new Thread(w1);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
两种创建多线程方式的比较
开发中:优先选择 实现Runnable接口的方式
原因:
联系:Thread类实现了Runnable接口 -> public class Thread implements Runnable
相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中
当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。
在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5 种状态。尤其是当线程启动以后,它不可能一直"霸占"着 CPU 独自运行,所以 CPU 需要在多条线程之间切换,于是线程状态也会多次在运行、阻塞之间切换
JDK中用Thread.State类定义了线程的几种状态。
新建状态(NEW)
当程序使用 new 关键字创建了一个线程之后,该线程就处于新建状态,此时仅由 JVM 为其分配内存,并初始化其成员变量的值
就绪状态(RUNNABLE)
当线程对象调用了 start()方法之后,该线程处于就绪状态。Java 虚拟机会为其创建方法调用栈和程序计数器,等待调度运行。
运行状态(RUNNING)
如果处于就绪状态的线程获得了 CPU,开始执行 run()方法的线程执行体,则该线程处于运行状态。
阻塞状态(BLOCKED)
阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得 cpu timeslice 转到运(running)状态。阻塞的情况分三种:
一.等待阻塞(o.wait->等待对列):
二.同步阻塞(lock->锁池)
三.其他阻塞(sleep/join)
死亡状态(DEAD)
线程会以下面三种方式结束,结束后就是死亡状态。
正常结束
异常结束
调用stop()
问题:多个线程操作同一个数据时出现重票、错票问题。
出现问题原因:当某个线程操作车票的过程中,尚未完成,另一个线程也参与进来,对车票进行操作。
解决方式:当一个线程a在操作ticket的时候,其他线程不能参与进来。 直到线程a操作完ticket时,其他线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能改变。
Java中通过同步机制,解决线程安全问题。
解决方式:
//语法
synchronized(同步监视器) {
//需要被同步的代码
}
什么是同步监视器?
答曰:同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
要求:多个线程必须要共用同一把锁。
什么是需要被同步的代码?
①操作共享数据的代码,即为需要被同步的代码
②共享数据:多个线程共同操作的变量,比如:ticket就是共享数据。
使用同步代码块解决窗口售票出现的多线程安全问题。
代码如下(继承Thread类):
class Window extends Thread {
//多个线程共享一个变量
private static int ticket = 100;
//多个线程共用同一个属性
private static Object obj = new Object();
@Override
public void run() {
while (true) {
//同步监视器的两种方式:①类.class ②多个线程通用同一个对象属性。
//synchronized (Window.class) {
synchronized (obj) {
if (ticket > 0) {
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + ": 卖票,票号为" + ticket);
ticket--;
}else {
break;
}
}
}
}
}
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();
}
}
代码如下(实现Runnable接口的类):
class Window2 implements Runnable {
private int ticket = 100;
private Object obj = new Object();
@Override
public void run() {
while (true) {
//this指的是当前对象的引用 当前对象只有一个,引用也就有一个,符合同步监视器的条件
//synchronized (this) {
synchronized (obj) {
if (ticket > 0) {
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": 卖票,票号为" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class WindowTest2 {
public static void main(String[] args) {
Window2 w1 = new Window2();
Thread t1 = new Thread(w1);
Thread t2 = new Thread(w1);
Thread t3 = new Thread(w1);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
//方法上加synchronized关键字
public synchronized int length() {
return count;
}
同步方法: 也需要同步监视器,只是不需要我们显式声明
静态同步方法:同步监视器指的是当前类本身
非静态同步方法:同步监视器指的是 this
使用同步方法解决窗口售票出现的多线程安全问题。
代码如下(继承Thread类):
class Window3 extends Thread {
//多个线程共享一个变量
private static int ticket = 100;
@Override
public void run() {
while (true) {
play();
}
}
public static synchronized void play() {
if (ticket > 0) {
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": 卖票,票号为" + ticket);
ticket--;
}
}
}
public class WindowTest3 {
public static void main(String[] args) {
Window3 w1 = new Window3();
Window3 w2 = new Window3();
Window3 w3 = new Window3();
w1.setName("窗口1");
w2.setName("窗口2");
w3.setName("窗口3");
w1.start();
w2.start();
w3.start();
}
}
代码如下(实现Runnable接口的类):
class Window4 implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
play();
}
}
public synchronized void play() {
if (ticket > 0) {
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": 卖票,票号为" + ticket);
ticket--;
}
}
}
public class WindowTest4 {
public static void main(String[] args) {
Window4 w1 = new Window4();
Thread t1 = new Thread(w1);
Thread t2 = new Thread(w1);
Thread t3 = new Thread(w1);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
1.从JDK5.0开始,Java提供了更强大的线程同步机制——通过显示定义同步锁对象来实现同步。同步锁使用Lock对象充当。
2.java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
3.ReentrantLock 类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是 ReentrantLock ,可以显式加锁、释放锁。
4.通过有参构造创建ReentrantLock对象,其中参数fair表示公平。如果参数为true,则表示线程公平竞争,根据线程启动的顺序去执行。参考下面的代码的结果可知。
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
使用Lock解决窗口售票出现的多线程安全问题。
class LockThread implements Runnable {
private int ticket = 100;
private ReentrantLock lock = new ReentrantLock(true);
@Override
public void run() {
while (true) {
try {
//锁定方法
lock.lock();
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票, 票号为" + ticket);
ticket--;
}else {
break;
}
}finally {
//解锁方法
lock.unlock();
}
}
}
}
public class LockTest {
public static void main(String[] args) {
LockThread lockThread = new LockThread();
Thread t1 = new Thread(lockThread);
Thread t2 = new Thread(lockThread);
Thread t3 = new Thread(lockThread);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t3.start();
t2.start();
t1.start();
}
}
执行结果:
综合练习:
银行有一个账户,有两个储户分别向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额。
分析:
是否存在多线程? 是
是否存在共享数据? 是 两个用户都操作账户
是否存在安全问题? 是
代码如下:
//账户
class Account {
//账户余额
private double balance;
public Account(double balance) {
this.balance = balance;
}
//存钱的方法
public void deposit(double money) {
synchronized (this) {
if (money > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
balance += money;
System.out.println(Thread.currentThread().getName() + ": 存钱,余额为" + balance);
}
}
}
}
//储户
class Consumer implements Runnable {
//共享数据
private Account account;
public Consumer(Account account) {
this.account = account;
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
account.deposit(1000);
}
}
}
public class AccountTest {
public static void main(String[] args) {
Account acc = new Account(0);
Consumer con = new Consumer(acc);
Thread t1 = new Thread(con);
Thread t2 = new Thread(con);
t1.setName("甲");
t2.setName("乙");
t1.start();
t2.start();
}
}
执行结果:
面试题:synchronized 与 Lock的异同?
死锁:
不同线程占用了对方需要的同步资源,都在等待对方放弃同步资源,就形成了线程的死锁。
简单来说:多个线程互相抱着对方需要的资源,然后形成僵持。
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。
解决方法:
专门的算法、原则。
尽量减少同步资源的定义。
尽量避免嵌套同步
死锁的例子1
public class DeadLockTest {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread(){
@Override
public void run() {
//假设有线程a和线程b 线程一启动,如果
//线程a拿到了s1锁,这时候睡了一秒 ,阻塞的过程中,很有可能线程b拿到了s2锁,
//这时候线程b也睡了一秒,想要继续执行下去,必须拿到s1锁 发现s1锁已经被线程a拿到 无法执行下去
//这时候就出现了 双方都占用这对方需要的共同资源,互相僵持这,出现 死锁。
synchronized (s1) {
s1.append("a");
s2.append("1");
try {
sleep(1000);
} 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 {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1) {
s1.append("d");
s2.append("4");
}
System.out.println(s1);
System.out.println(s2);
}
}
}).start();
}
}
死锁的例子2:
//口红
class Lipstick {
}
//镜子
class Mirror {
}
class Makeup extends Thread {
int choice;//选择
String name; //化妆人的姓名
//只有一份镜子和口红
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
public Makeup(int choice, String name) {
this.choice = choice;
this.name = name;
}
@Override
public void run() {
makeUp();
}
public void makeUp() {
if (choice == 0) {
synchronized (lipstick) {
System.out.println(this.name + "拿到了口红");
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (mirror) {
System.out.println(this.name + "拿到了镜子");
}
}
}else {
synchronized (mirror) {
System.out.println(this.name + "拿到了镜子");
try {
sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lipstick) {
System.out.println(this.name + "拿到了口红");
}
}
}
}
}
public class DeadLockTest2 {
public static void main(String[] args) {
Makeup g1 = new Makeup(0,"灰姑娘");
Makeup g2 = new Makeup(1,"白雪公主");
g1.start();
g2.start();
}
}
解决方式:同步代码块不要嵌套。
面试题:synchronized 与 Lock的异同?
线程通信的例子:使用两个线程打印 1-100。线程1,线程2 交替打印
涉及到的三个方法:
说明:
代码如下:
class Number implements Runnable{
private int number = 1;
private Object obj = new Object();
@Override
public void run() {
while (true) {
/**
* 解释 wait() notify()的过程
* 当CPU将执行权分配给线程a时,线程a进入同步代码块(此时其他线程进不来)执行逻辑,
* 当线程a调用wait()方法,线程a进入阻塞状态,并且释放同步监视器(也就是同步锁),这时CPU将执行权分配给了线程b
* 线程b进入了同步代码块(锁好门),这时执行notify()唤醒线程a,线程a只能在同步代码块外面等着。
*
*/
//同步监视器可以是任何类的对象 此对象调用notify() 说明notify()被放在Object类中
synchronized (obj) {
obj.notify();
if (number <= 100) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
try {
//调用wait()方法的线程进入阻塞状态
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
break;
}
}
}
}
}
public class CommunicationTest {
public static void main(String[] args) {
Number number = new Number();
Thread t1 = new Thread(number);
Thread t2 = new Thread(number);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
面试题:sleep() 和 wait()的异同?
生产者和消费者例题:
生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来去走产品。
分析:
1.是否是多线程问题? 是,生产者线程,消费者线程
2.是否有共享数据?是店员(或产品)
3.如何解决线程的安全问题?同步机制,有三种方法
4.是否涉及线程的通信?是
代码如下
//店员
class Clerk {
//产品数量
private int productCount;
//生产产品的方法
public synchronized void producerProduct() {
if (productCount < 20) {
productCount++;
System.out.println(Thread.currentThread().getName() + ":开始生产第" + productCount + "个产品");
notify();
}else {
//等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
消费产品的方法
public synchronized void consumerProduct() {
if (productCount > 0) {
System.out.println(Thread.currentThread().getName() + ":开始消费第" + productCount + "个产品");
productCount--;
notify();
}else {
//等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//消费者
class Consumer extends Thread {
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println("消费者开始消费产品....");
while (true) {
try {
sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.consumerProduct();
}
}
}
//生产者
class Producer extends Thread {
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println("生产者开始生产产品....");
while (true) {
try {
sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.producerProduct();
}
}
}
public class ProducerTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer producer = new Producer(clerk);
Consumer consumer = new Consumer(clerk);
Consumer consumer2 = new Consumer(clerk);
producer.setName("生产者1");
consumer.setName("消费者1");
consumer2.setName("消费者2");
producer.start();
consumer.start();
consumer2.start();
}
}
//1. 创建实现Callable接口的实现类
class MyThread implements Callable<Integer>{
//2. 完成Callable接口的call()重写
@Override
public Integer call() throws Exception {
//完成100以内的偶数之和
int sum = 0;
for (int i = 1; i <= 100; i++) {
if (i % 2 == 0) {
System.out.println(i);
sum+=i;
}
}
return sum;
}
}
public class ThreadTest {
public static void main(String[] args) {
//3. 创建接口的实现类对象
MyThread m1 = new MyThread();
//4. 该对象作为参数传递给FutureTask类的构造器, 创建该类对象
FutureTask<Integer> futureTask = new FutureTask<Integer>(m1);
//5. 此对象作为参数传递给Thread类的构造器,创建一个线程
Thread t1 = new Thread(futureTask);
t1.start();
try {
//6. 调用futureTask的get()返回重写call()的返回值
Integer sum = futureTask.get();
System.out.println("偶数总和: " + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。比如说:去一个地方你选择骑车去,应该避免自己去造车子,到达目的地后销毁这样的事情。应该去选择共享单车。
好处:
①提高响应速度(减少了创建新线程的时间)
②降低资源消耗(重复利用线程池中线程,不需要每次都创建)
③便于线程管理
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
代码如下:
class NumberThread 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 ThreadPool {
public static void main(String[] args) {
//1. 提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//获得操作线程管理的类
ThreadPoolExecutor poolExecutor = (ThreadPoolExecutor) service;
//设置核心池的大小
poolExecutor.setCorePoolSize(20);
//2. 执行指定的线程操作。需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread());//适用于Runnable
//service.submit();//适用于Callable
//关闭连接池
service.shutdown();
}
}