文章目录
1.线程概述
1.什么是进程
是程序的一次执行过程,或是正在运行的一个程序,是一个动态过程,有他自身的产生,纯在和消亡的过程
2.什么是线程
进程可进一步细化为线程,是一个程序内部的一条执行路径
3.什么是程序
是为完成特定的任务,用某种语言编写的一组指令的集合,即使一段静态的代码,静态对象
4.并行和并发
并行:多个CPU同时执行多个任务,比如多个人同时做不同的事(一遍吃饭一遍完手机)
并发:同时执行多个任务,比如秒杀平台,多个人做一件事
2.线程入门
1.多线程创建方式1
package com.xjggb.thread;
public class Test01 {
public static void main(String[] args) {
Threadl threadl1 = new Threadl("A");
Threadl threadl2 = new Threadl("B");
threadl1.start();
threadl2.start();
}
}
/*
* 方式一
* 步骤
* 继承Thread类
* 重写run方法
* 创建对象调用start方法
* */
class Threadl extends Thread {
private String name;
public Threadl(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("运行" + i);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果
运行0
运行0
运行1
运行2
运行3
运行4
运行1
运行2
运行3
运行4
小结
开发步骤
- 继承Thread类
- 重写run方法
- 创建子类对象调用start
注意:start()方法调用后并不是立即执行多线程代码,而是使得该线程变为可运行状态(Runnable),什么时候运行由操作系统决定
创建线程下载图片
添加依赖
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>
代码如下
package com.xjggb.entity;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
//图片下载
public class WebDownLoader {
public void downLoader(String url, String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常");
}
}
}
//创建线程
class TestDemo extends Thread{
private String url; //网络图片地址
private String name; // 保存的文件名
public TestDemo(String url ,String name){
this.name=name;
this.url=url;
}
@Override
public void run() {
WebDownLoader s = new WebDownLoader();
s.downLoader(url,name);
System.out.println("已经下载的文件名称为"+name+"图片" );
}
}
class Test{
public static void main(String[] args) {
TestDemo testDemo1 = new TestDemo("https://img-blog.csdnimg.cn/20210210194321356.png","我的博客图片01.png");
TestDemo testDemo2 = new TestDemo("https://img-blog.csdnimg.cn/20210228094648502.png","我的博客图片02.png");
TestDemo testDemo3 = new TestDemo("https://img-blog.csdnimg.cn/20210303104522231.png","我的博客图片03.png");
//调用start()方法
testDemo1.start();
testDemo2.start();
testDemo3.start();
}
}
2.多线程创建方式2
开发步骤
- 创建类实现Runnable接口重写run方法
- 创建接口子类,创建线程对象子类放入线程对象的构造方法中
- 调用start方法
package com.xjggb.thread;
public class TestRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("我在秀代码" + i);
}
}
public static void main(String[] args) {
//创建线程接口实现对象
TestRunnable testRunnable = new TestRunnable();
//床架线程对象,通过线程对象开启线程
Thread thread = new Thread(testRunnable);
//掉用start方法
thread.start();
for (int i = 0; i < 100; i++) {
System.out.println("我是主线程" + i);
}
}
}
小结
- 实现Runnable接口
- 创建子线程传入到线程对象的构造方法中
- 调用start()方法
- 推荐使用,避免单继承灵活方便同一对象被多线程调用
3.多线程创建方式3(了解)
现实步骤
- 实现Callable接口,需要返回值类型
- 重写call()方法,需要抛出异常
- 创建目标对象
- 创建执行服务:ExecutorService ser = Executor.newFixedThreadPool(1);
- 提交执行:Future result1 = ser.submit(t1);
- 获取结果:boolean r1 = result1.get();
- 关闭服务:ser.shutdownNow();
package com.xjggb.thread;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;
public class Test03 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建线程
TestCallable testCallable1 = new TestCallable("https://img-blog.csdnimg.cn/20210210194321356.png", "我的博客图片01.png");
TestCallable testCallable2 = new TestCallable("https://img-blog.csdnimg.cn/20210228094648502.png", "我的博客图片02.png");
TestCallable testCallable3 = new TestCallable("https://img-blog.csdnimg.cn/20210303104522231.png", "我的博客图片03.png");
//创建执行任务
ExecutorService ser = Executors.newFixedThreadPool(3);
Future<Boolean> submit1 = ser.submit(testCallable1);
Future<Boolean> submit2 = ser.submit(testCallable2);
Future<Boolean> submit3 = ser.submit(testCallable3);
//获取结果
Boolean aBoolean1 = submit1.get();
Boolean aBoolean2 = submit2.get();
Boolean aBoolean3 = submit3.get();
//关闭服务
ser.shutdown();
}
}
class TestCallable implements Callable<Boolean> {
private String url; //网络图片地址
private String name; // 保存的文件名
public TestCallable(String url, String name) {
this.name = name;
this.url = url;
}
@Override
public Boolean call() throws Exception {
WebDownLoader webDownLoader = new WebDownLoader();
webDownLoader.downLoader(url, name);
System.out.println("下载的文件名称为" + name + "的图片");
return true;
}
}
//下载器
//图片下载
class WebDownLoader {
public void downLoader(String url, String name) {
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常");
}
}
}
3.线程状态
- 新建状态(New):新新建一个线程对象
- 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的starta()方法,该状态的线程位于可运行线程池中,变得可运行等待获取CPU的使用权
- 运行状态(Running)就绪状态的线程获取了CPU,执行程序代码
- 阻塞状态(Blocked):阻塞状态线程因为某种原因放弃CPU使用权, 暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
- 等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
- 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
- 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)
- 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
小结:
线程状态
- 创建
- 就绪
- 运行
- 阻塞
- 死亡
4.线程API
Thread.currentThread().getName() //获取当前线程的名字
1.start():1.启动当前线程2.调用线程中的run方法
2.run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
3.currentThread():静态方法,返回执行当前代码的线程
4.getName():获取当前线程的名字
5.setName():设置当前线程的名字
6.yield():主动释放当前线程的执行权
7.join():在线程中插入执行另一个线程,该线程被阻塞,直到插入执行的线程完全执行完毕以后,该线程才继续执行下去
8.stop():过时方法。当执行此方法时,强制结束当前线程。
9.sleep(long millitime):线程休眠一段时间
10.isAlive():判断当前线程是否存活
1.线程停止
- 测试Stop
- 1.建议线程正常停止—>利用次数,不建议死循环.
- 2.建议使用标志位---->设置一个标志位
- 3.不要使用stop和destroy 等过时或者jdk不建议使用的方法.
package com.xjggb.thread;
//建议线程正常停止:利用此时,不建议死循环
//建议使用标记位
public class TestStop implements Runnable {
// 1.设置一个标志位
private boolean flag = true;
@Override
public void run() {
int i = 0;
while (flag){
System.out.println("run...Thread: " + i++);
}
}
//2.设置公开的方法停止线程
public void stop(){
this.flag = false;
}
public static void main(String[] args) {
TestStop testStop = new TestStop();
new Thread(testStop).start();
for (int i = 0; i < 30; i++) {
System.out.println("main " +i);
if( i == 20){
//调用stop方法切换线程标志位,让线程停止
testStop.stop();
System.out.println("线程已停止");
}
}
}
}
2.线程睡眠
每个对象都有一个锁,sleep不会释放锁
sleep(时间)指定当前线程阻塞的毫秒数
sleep可以模拟网络延迟,倒计时等
每个对象都有一个锁,seep不会释放锁
模拟倒计时
package com.xjggb.thread;
public class Test04 {
public static void main(String[] args) {
try {
show(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 模拟倒计时
public static void show(int add) throws InterruptedException {
while (true) {
Thread.sleep(1000);
System.out.println("add = " + add--);
if (add < 0) {
break; //结束循环
}
}
}
}
打印当前系统时间
package com.xjggb.thread;
public class Test04 {
public static void main(String[] args) {
try {
show(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//打印当前系统时间
public static void show() throws InterruptedException {
Date date = new Date(System.currentTimeMillis()); //获取系统当前的时间
int i=10;
while (true){
Thread.sleep(1000);
String format = new SimpleDateFormat("HH:mm:ss").format(date);
System.out.println("format = " + format);
//获取当前最新的时间
date = new Date(System.currentTimeMillis());
--i;
if (i<0){
System.out.println(" = " +i);
break;
}
}
}
}
3.线程礼让
- 礼让线程,让当前正在执行的线程暂停,单不堵塞
- 将线程从运行状态转为就绪状态
- 让CPU重新调度,礼让不一定成功,看CPU
代码如下:
package com.xjggb.thread;
public class ThreadYield {
public static void main(String[] args) {
//创建线程子对象
TestYield testYield = new TestYield();
//创建线程对象
new Thread(testYield,"线程A").start();
new Thread(testYield,"线程B").start();
}
}
class TestYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程开启");
//线程礼让
Thread.yield();
System.out.println(Thread.currentThread().getName() + "线程开启");
/* 礼让成功 礼让不成功就多执行两次
* 线程A线程开启
线程B线程开启
线程A线程开启
线程B线程开启
* */
}
}
4.插入线程
join():在线程中插入执行另一个线程,该线程被阻塞,直到插入执行的线程完全执行完毕以后,该线程才继续执
代码如下
package com.xjggb.thread;
public class ThreadJoin implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("run+我是VIP都滚开" + i);
}
}
public static void main(String[] args) {
//启动线程
ThreadJoin threadJoin = new ThreadJoin();
Thread thread = new Thread(threadJoin);
//主线程
for (int i = 0; i < 500; i++) {
System.out.println("我是Main方法" + i);
if (i == 300) {
//启动VIP线程
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/*
* 礼让显示
* 我是Main方法297
我是Main方法298
我是Main方法299
我是Main方法300
run+我是VIP都滚开0
run+我是VIP都滚开1
run+我是VIP都滚开2
run+我是VIP都滚开3
run+我是VIP都滚开4
run+我是VIP都滚开5
run+我是VIP都滚开6
run+我是VIP都滚开7
* */
}
}
}
5.线程检测状态
线程状态。线程可以处于一下状态
- NEW
尚未启动的线程的线程状态
代码如下
package com.xjggb.thread;
public class Thst05 {
public static void main(String[] args) {
//创建线程
Thread thread = new Thread(() -> {
for (int i = 0; i < 2; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//观察状态
Thread.State state1 = thread.getState();
System.out.println("state1 = " + state1);
//启动后观察状态
thread.start();
state1 = thread.getState();
System.out.println("state2 = " + state1);
//只要线程不终止,就会一直输出状态
while (state1 != Thread.State.TERMINATED) {
try {
Thread.sleep(200);
//更新线程状态
state1 = thread.getState();
System.out.println("state1 = " + state1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/*
* state1 = NEW
state2 = RUNNABLE
state1 = RUNNABLE
state1 = RUNNABLE
state1 = TERMINATED
* */
}
}
6.线程优先级
线程优先级用数字表示,范围从1~10
- Thread.MIN_PRIORITY = 1
- _PRIORITY = 5
- Thread.MAX_PRIORITY = 10
注意先设计优先级在启动
代码如下:
package com.xjggb.thread;
public class ThreadPriority {
public static void main(String[] args) {
System.out.println(" = " + Thread.currentThread().getName());
Mypriority mypriority = new Mypriority();
Thread thread1 = new Thread(mypriority);
Thread thread2 = new Thread(mypriority);
Thread thread3 = new Thread(mypriority);
Thread thread4 = new Thread(mypriority);
Thread thread5 = new Thread(mypriority);
Thread thread6 = new Thread(mypriority);
thread1.start();
//设置优先级
thread2.setPriority(3);
thread2.start();
thread3.setPriority(5);
thread3.start();
thread4.setPriority(7);
thread4.start();
thread5.setPriority(Thread.MAX_PRIORITY);
thread5.start();
thread6.setPriority(Thread.NORM_PRIORITY);
thread6.start();
/* 运行结果
* = main
Thread-3--->7
Thread-4--->10
Thread-0--->5
Thread-2--->5
Thread-5--->5
Thread-1--->3
* */
}
}
class Mypriority implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "--->" + Thread.currentThread().getPriority());
}
}
7.守护线程
- 线程分为用户线程和守护线程
- 虚拟机必须确保线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 如后台记录操作日志,监控内存,垃圾回收等
package com.xjggb.thread;
public class Test06 {
public static void main(String[] args) {
//创建上帝
Gog gog = new Gog();
//创建你
You you = new You();
//创建线程对象
Thread thread = new Thread(gog); //使用上帝作为守护线程
//线程守护默认是false表示用户线程,正常的线程就是用户线程
thread.setDaemon(true);
thread.start();
//you启动
new Thread(you).start();
}
}
class Gog implements Runnable{
@Override
public void run() {
while (true){
System.out.println("上帝守护你");
}
}
}
class You implements Runnable{
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
System.out.println("我还开开心心的活着" + i+"岁");
}
System.out.println("任务结束了");
}
}
/*
* 当主线程退出时,JVM会随之退出运行,守护线程同时也会被回收,即使你里面是个死循环也不碍事
*
* 上帝守护你
上帝守护你
我还开开心心的活着0岁
我还开开心心的活着1岁
我还开开心心的活着2岁
我还开开心心的活着3岁
我还开开心心的活着4岁
我还开开心心的活着5岁
我还开开心心的活着6岁
我还开开心心的活着7岁
我还开开心心的活着8岁
我还开开心心的活着9岁
任务结束了
上帝守护你
上帝守护你
* */
小结:
如果 JVM 中没有一个正在运行的非守护线程,这个时候,JVM 会退出。换句话说,守护线程拥有自动结束自己生命周期的特性,而非守护线程不具备这个特点。
5.什么是线程安全
线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。
1.没有线程同步
package com.xjggb.security;
public class Test02 {
public static void main(String[] args) {
//创建子线程实现列
WindowRunnable windowRunnable = new WindowRunnable(100);
//创建线程对象
Thread thread1 = new Thread(windowRunnable);
Thread thread2 = new Thread(windowRunnable);
Thread thread3 = new Thread(windowRunnable);
thread1.start();
thread2.start();
thread3.start();
/*
* Thread-0售出票号100
Thread-2售出票号100
Thread-2售出票号99
Thread-1售出票号100
* */
}
}
class WindowRunnable implements Runnable{
//定义票数
private int ticket;
public WindowRunnable(int ticket) {
this.ticket = ticket;
}
@Override
public void run() {
while (true){
if (ticket>0){
System.out.println(Thread.currentThread().getName() +"售出票号"+ ticket);
ticket--;
}else {
break;
}
}
}
}
由于线程被CPU调度的随机性,多次运行结果不一致,但基本一致可以看出,多个窗口在迸发进行销售,而且有
错票,重要 ,漏票问题
2.有线程同步
同步代码块
synchronized(同步监视器){
需要被同步的代码
}
- 操作共享数据的代码,需要被同步的代码
- 共享数据,多个线程共同操作的变量,比如:ticket九四共享数据
- 同步监视器,俗称锁,任何一个类对象,都可以充当锁
package com.xjggb.security;
public class Test02 {
public static void main(String[] args) {
//创建子线程实现列
WindowRunnable windowRunnable = new WindowRunnable(100);
//创建线程对象
Thread thread1 = new Thread(windowRunnable);
Thread thread2 = new Thread(windowRunnable);
Thread thread3 = new Thread(windowRunnable);
thread1.start();
thread2.start();
thread3.start();
/*
* Thread-0售出票号100
Thread-2售出票号100
Thread-2售出票号99
Thread-1售出票号100
* */
}
}
class WindowRunnable implements Runnable{
//定义票数
private int ticket;
public WindowRunnable(int ticket) {
this.ticket = ticket;
}
@Override
public void run() {
synchronized (WindowRunnable.class){ //同步代码块
while (true){
if (ticket>0){
System.out.println(Thread.currentThread().getName() +"售出票号"+ ticket);
ticket--;
}else {
break;
}
}
}
}
}
同步方法
package com.xjggb.security;
public class Test04 {
public static void main(String[] args) {
//创建线程
Window2 window2 = new Window2();
//创建线程
Thread thread1 = new Thread(window2,"线程1");
Thread thread2 = new Thread(window2,"线程2");
Thread thread3 = new Thread(window2,"线程3");
//启动线程
thread1.start();
thread2.start();
thread3.start();
}
}
class Window2 implements Runnable{
//创建票数
private int ticket=100;
@Override
public void run() {
while (true){
show();
}
}
//同步方法
private synchronized void show(){
if (ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+ticket);
ticket--;
}
}
}
7.死锁
1.什么是死锁
- 不同的线程分别抢占用对方需要的同步资源不放弃,都在等待对方放弃,自己需要的同步志愿,就形成了线程死锁
- 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
代码如下:
package com.xjggb.security;
public class Test05 {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
//创建线程
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s1) {
s1.append("a");
s2.append("1");
//睡眠两秒
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//在嵌套一个线程安全
synchronized (s2) {
s1.append("b");
s2.append("2");
System.out.println("s2 = " + s2);
System.out.println("s1 = " + s1);
}
}
}
}).start();
//在创建一个线程
new Thread(() -> {
synchronized (s2) {
s1.append("o");
s2.append("7");
//睡眠两秒
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
//在嵌套一个线程安全
synchronized (s1) {
s1.append("p");
s2.append("8");
System.out.println("s2 = " + s2);
System.out.println("s1 = " + s1);
}
}
}).start();
}
}
小结:
程序不会结束,也无法执行下去
2.解决方法
- 专门的算法,原则
- 尽量减少同步资源的定义
- 尽量避免嵌套同步
8.Lock(锁)
java提供了更强大的线程同步机制–通过显示定义同步代锁对象来实现同步,同步锁使用Lock锁对象充当
Lock接口是控制多个线程对共享资源进行访问的工具,
代码如下:
package com.xjggb.security;
import java.util.concurrent.locks.ReentrantLock;
public class Test06 {
public static void main(String[] args) {
/*
* 面试题 synchronized与Lock的异同
* 相同: 二者都可以解决线程安全问题
* 不同: synchronized 执行完同步代码会自动释放锁
* lock需要手动的启动同步(lock()) 同时结束同步也需要手动的实现
* */
//创建子线程
TestLock testLock = new TestLock();
Thread thread1 = new Thread(testLock);
Thread thread2 = new Thread(testLock);
Thread thread3 = new Thread(testLock);
thread1.start();
thread2.start();
thread3.start();
}
}
class TestLock implements Runnable {
private int ticket = 100; //定义票数
private ReentrantLock lock = new ReentrantLock(); //实例化ReentrantLock
@Override
public void run() {
try {
lock.lock(); //开启锁
while (true) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":" + ticket);
ticket--;
} else {
break;
}
}
} finally {
lock.unlock(); //释放锁
}
}
}
9.同步练习
1.线程不安全
package com.xjggb.account;
public class Test01 {
public static void main(String[] args) {
Account account = new Account(0);
Customer customer1 = new Customer(account);
Customer customer2 = new Customer(account);
Customer customer3 = new Customer(account);
customer1.setName("甲");
customer2.setName("乙");
customer3.setName("丙");
//启动线程
customer1.start();
customer2.start();
customer3.start();
/*
* 代码运行结果直接出现线程安全问题
* 存钱成功,余额为2000.0
存钱成功,余额为2000.0
存钱成功,余额为2000.0
存钱成功,余额为4000.0
存钱成功,余额为5000.0
存钱成功,余额为4000.0
存钱成功,余额为8000.0
存钱成功,余额为8000.0
存钱成功,余额为8000.0
* */
}
}
//多线程用户存钱
class Customer extends Thread {
private Account account;
//创建有参构造赋值
public Customer(Account account) {
this.account = account;
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
account.setMoney(1000);
}
}
}
//创建账户类
class Account {
private double money;
public Account() {
}
public Account(double money) {
this.money = money;
}
/**
* 获取
* @return money
*/
public double getMoney() {
return money;
}
/**
* 设置
* @param
*/
public void setMoney(double mone) {
if (mone>0){
//睡3秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
money+=mone; //睡了一秒,甲还没有反应过来,乙就进来了,这样就发生了线程安全问题
System.out.println("存钱成功,余额为" + money);
}
}
}
2.线程安全的
三种方式
- synchronized方法
- synchronized代码块
- lock锁
三种方式都行
使用synchronized方法
package com.xjggb.account;
public class Test01 {
public static void main(String[] args) {
Account account = new Account(0);
Customer customer1 = new Customer(account);
Customer customer2 = new Customer(account);
Customer customer3 = new Customer(account);
customer1.setName("甲");
customer2.setName("乙");
customer3.setName("丙");
//启动线程
customer1.start();
customer2.start();
customer3.start();
/*
*保证线程安全
* 存钱成功,余额为1000.0
存钱成功,余额为2000.0
存钱成功,余额为3000.0
存钱成功,余额为4000.0
存钱成功,余额为5000.0
存钱成功,余额为6000.0
存钱成功,余额为7000.0
存钱成功,余额为8000.0
存钱成功,余额为9000.0
* */
}
}
//多线程用户存钱
class Customer extends Thread {
private Account account;
//创建有参构造赋值
public Customer(Account account) {
this.account = account;
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
account.setMoney(1000);
}
}
}
//创建账户类
class Account {
private double money;
public Account() {
}
public Account(double money) {
this.money = money;
}
/**
* 获取
* @return money
*/
public double getMoney() {
return money;
}
/**
* 设置
* @param
*/
public synchronized void setMoney(double mone) {
if (mone>0){
//睡3秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
money+=mone; //睡了一秒,添加synchronized 就像队列一样排队一个一个来
System.out.println("存钱成功,余额为" + money);
}
}
}
10.线程通信
涉及到三个方法:
wait():一旦此执行此方法,当前线程就进入阻塞状态,并释放同步监视器
notify():一旦执行
代码如下:
package com.xjggb.account;
public class Test01 {
public static void main(String[] args) {
Account account = new Account(0);
Customer customer1 = new Customer(account);
Customer customer2 = new Customer(account);
Customer customer3 = new Customer(account);
customer1.setName("甲");
customer2.setName("乙");
customer3.setName("丙");
//启动线程
customer1.start();
customer2.start();
customer3.start();
/*
*
* 存钱成功,余额为1000.0
存钱成功,余额为2000.0
存钱成功,余额为3000.0
存钱成功,余额为4000.0
存钱成功,余额为5000.0
存钱成功,余额为6000.0
存钱成功,余额为7000.0
存钱成功,余额为8000.0
存钱成功,余额为9000.0
* */
}
}
//多线程用户存钱
class Customer extends Thread {
private Account account;
//创建有参构造赋值
public Customer(Account account) {
this.account = account;
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
account.setMoney(1000);
}
}
}
//创建账户类
class Account {
private double money;
public Account() {
}
public Account(double money) {
this.money = money;
}
/**
* 获取
* @return money
*/
public double getMoney() {
return money;
}
/**
* 设置
* @param
*/
public synchronized void setMoney(double mone) {
if (mone>0){
//睡3秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
money+=mone; //睡了一秒,甲还没有反应过来,乙就进来了,这样就发生了线程安全问题
System.out.println("存钱成功,余额为" + money);
}
}
}
小结:
wait(),notify(),notifyAll() 三个方法必须使用在同步方法或者代码块中
wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或者同步方法中的同步监视器
否则就会出现异常
面试题:
sleep()和wait()异同?
- 相同点: 一旦执行方法就会使得当前的线程进入阻塞状态
- 不同点:
- 两个方法的声明位置不同:Thread类中声明sleep(),Object类中声明wait()
- 调用的要求不同:sleep()可以在任何需要的场景下调用,wait()必须使用在同步代码中
- 关于是否释放同步监视器(锁)如果两个方法都使用在同步代码块或者同步方法中,sleep()方法不会释放锁wait()会自动释放锁
11.生产者和消费者问题
生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处 取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者试图 生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通 知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如 果店中有产品了再通知消费者来取走产品。
代码如下
package com.xjggb.security;
public class Test07 {
public static void main(String[] args) {
Clerk clerk = new Clerk(); //创建店员
Productor productor = new Productor(clerk); //创建生产者
productor.setName("我是生产者");
Customer customer = new Customer(clerk); //创建生产者
customer.setName("我是消费者");
//启动线程
productor.start();
customer.start();
}
}
//生产者
class Productor extends Thread {
private Clerk clerk;
//创建构造函数
public Productor(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println("生产者开始生产产品");
while (true) {
//睡一秒中
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//开始生产产品
clerk.addProduct();
}
}
}
//消费者
class Customer extends Thread {
private Clerk clerk;
//创建构造函数
public Customer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
while (true) {
//睡一秒钟
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//开始消费产品
clerk.getProduct();
}
}
}
//店员
class Clerk {
private int product = 0; //定义产品数量
public synchronized void addProduct() { //生产产品
if (product < 20) {
product++;
System.out.println(Thread.currentThread().getName() + "生产了第" + product + "个");
notifyAll(); //唤醒
} else {
try {
wait(); // 阻塞线程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void getProduct() { //消费产品
if (product > 0) {
System.out.println(Thread.currentThread().getName() + "取走了第" + product + "个");
product--;
notifyAll();
} else {
try {
wait(); // 阻塞线程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
小结:
- 分析是否是多线程问题?是,生产者和消费者
- 分析是否有共享数据,是,店员 (或者产品)
- 如何结局线程的安全问题?同步代码块,同步方法,和lock锁等
- 是否涉及线程通信
12.JDK5.0 新增线程创建方式
1. 新增方式一:实现Callable接口
与Runnable相比Callable功能更加强大
- 相比于run()方法,call()可以有返回值
- 方法可以抛出异常,被外面的操作捕获
- 支持范型的返回值
- 需要借助FutureTask类,比如获取返回结果
Future接口
可以对具体Runnable、Callable任务的执行结果进行取消、查询是 否完成、获取结果等。
FutrueTask是Futrue接口的唯一的实现类
FutureTask 同时实现了Runnable, Future接口。它既可以作为 Runnable被线程执行,又可以作为Future得到Callable的返回值
代码如下:
package com.xjggb.account;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test03 {
public static void main(String[] args) {
//创建子类线程
Thread1 thread1 = new Thread1();
//将实现类对象作为参数传递待FutureTask的构造器中,创建FutureTask的对象
FutureTask futureTask = new FutureTask(thread1);
//将FutureTask的对象传到Thread的构造器中,创建Thread类的对象并start()
Thread thread = new Thread(futureTask);
//启动线程
thread.start();
//获取返回值
try {
System.out.println("偶数总和" + futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class Thread1 implements Callable {
@Override
public Object call() throws Exception {
int sum=0;
for (int i = 0; i < 100; i++) {
if (i%2==0){ //获取模于2等于零的累加
sum+=i;
}
}
return sum;
}
}
2.新增方式二:使用线程池
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程, 对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完 放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交 通工具。
好处:
提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
便于线程管理 corePoolSize:核心池的大小 maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
线程API
JDK 5.0起提供了线程池相关API:ExecutorService 和 Executors
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行 Runnable
Future submit(Callable task):执行任务,有返回值,一般又来执行 Callable
void shutdown() :关闭连 接池 Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池 Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池 Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池 Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池 Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运 行命令或者定期地执行。
创建线程池
//Executors工具类、线程池的工厂类,用法语创建并返回不同类型的线程池
//1、创建一个可根据需要创建性线程的线程池
Executors.newCachedThreadPool();
//2、创建一个可重用固定线程书的线程池
Executors.newFixedThreadPool(n);
//3、创建一个只有一个线程的线程池
Executors.newSingleThreadExecutor();
//4、创建一个线程池,它可安排再给定延迟后运行命令或者定期地执行
Executors.newScheduledThreadPool(n);
package com.xjggb.account;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test04 {
public static void main(String[] args) {
//创建指定的线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
//创建线程的实现对象
PoolTest poolTest = new PoolTest();
executorService.execute(poolTest);
}
}
class PoolTest implements Runnable{
@Override
public void run() {
int sum=0;
for (int i = 0; i < 100; i++) {
if (i%2==0){
sum+=i;
}
}
System.out.println("sum = " + sum);
}
}
Callable
service.submit(new FutureTask(new PoolThread1()));
关闭连接池
executorService.shutdown();
设置连接池属性
//类型强转
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
service1.setCorePoolSize(15);
service1.setKeepAliveTime();