Java-线程-多线程详解
线程,进程,多线程
- 线程与线程之间互不干扰,独立执行
- 程序运行时,没有建立自己的线程,后台也会有多个线程,比如主线程,gc线程(垃圾回收线程)
- main() 称之为主线程,为系统入,用于执行整个程序
- 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的。
- 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
- 线程会带来额外的开销,如cpu调度时间,并发控制开销
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
法一创建线程 - 继承Thread类
三种创建方式
- 继承thread类
- 重写run方法
- 调用start()开启线程
public class TestThread1 extends Thread{
@Override
public void run(){
//run方法
for (int i=0;i<20;i++){
System.out.println("我在看代码---"+i);
}
}
public static void main(String[] args) {
//main线程、主线程
//创建一个线程对象
//调用start()方法
TestThread1 testThread1 = new TestThread1();
testThread1.start();
for (int i = 0; i < 20; i++) {
System.out.println("我在学习多线程---"+i);
}
}
}
我在看代码---0
我在看代码---1
我在学习多线程---0
我在看代码---2
我在学习多线程---1
我在看代码---3
我在学习多线程---2
我在看代码---4
我在学习多线程---3
我在看代码---5
我在学习多线程---4
我在学习多线程---5
我在看代码---6
我在学习多线程---6
我在学习多线程---7
我在学习多线程---8
我在学习多线程---9
我在学习多线程---10
我在学习多线程---11
我在看代码---7
我在看代码---8
我在学习多线程---12
我在学习多线程---13
我在看代码---9
我在学习多线程---14
我在看代码---10
我在学习多线程---15
我在学习多线程---16
我在学习多线程---17
我在学习多线程---18
我在学习多线程---19
我在看代码---11
我在看代码---12
我在看代码---13
我在看代码---14
我在看代码---15
我在看代码---16
我在看代码---17
我在看代码---18
我在看代码---19
注意
- 线程开启不一定立即执行,由cpu调度执行
案例:下载图片
package Demo01;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
//练习Thread,实现多线程同步下载图片
public class TestThread2 extends Thread{
private String url;
private String name;
public TestThread2(String url,String name){
this.url = url;
this.name = name;
}
//下载图片线程的执行体
@Override
public void run(){
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url,name);
System.out.println("下载了文件名为:"+name);
}
public static void main(String[] args) {
TestThread2 t1 = new TestThread2("http://static.runoob.com/images/demo/demo1.jpg","1.jpg");
TestThread2 t2 = new TestThread2("http://static.runoob.com/images/demo/demo2.jpg","2.jpg");
TestThread2 t3 = new TestThread2("http://static.runoob.com/images/demo/demo3.jpg","3.jpg");
t1.start();
t2.start();
t3.start();
}
}
//下载器
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异常,download方法出现问题");
}
}
}
法二创建线程 - 实现Runnable接口
- 实现runnable接口,
- 重写run方法,
- 执行
package Demo01;
//创建线程方式二,实现runnable接口,重写run方法,执行
public class TestThread3 implements Runnable{
@Override
public void run(){
//run方法
for (int i=0;i<20;i++){
System.out.println("我在看代码---"+i);
}
}
public static void main(String[] args) {
//main线程、主线程
//创建runnable接口的实现类对象
TestThread3 testThread3 = new TestThread3();
//创建线程对象,通过线程对象来开启我们的线程,代理
Thread thread = new Thread(testThread3);
thread.start();
//简写
// new Thread(testThread3).start();
for (int i = 0; i < 20; i++) {
System.out.println("我在学习多线程---"+i);
}
}
}
我在看代码---0
我在看代码---1
我在学习多线程---0
我在看代码---2
我在看代码---3
我在学习多线程---1
我在看代码---4
我在学习多线程---2
我在看代码---5
我在学习多线程---3
我在看代码---6
我在看代码---7
我在看代码---8
我在看代码---9
我在看代码---10
我在看代码---11
我在看代码---12
我在看代码---13
我在看代码---14
我在看代码---15
我在看代码---16
我在看代码---17
我在看代码---18
我在学习多线程---4
我在看代码---19
我在学习多线程---5
我在学习多线程---6
我在学习多线程---7
我在学习多线程---8
我在学习多线程---9
我在学习多线程---10
我在学习多线程---11
我在学习多线程---12
我在学习多线程---13
我在学习多线程---14
我在学习多线程---15
我在学习多线程---16
我在学习多线程---17
我在学习多线程---18
我在学习多线程---19
小结
- 继承Thread类
- 子类继承Thread类具备多线程能力
- 启动线程:子类对象.start()
- 不建议使用:避免OOP单继承局限性
- 实现Runnable接口
- 实现接口Runnable具有多线程能力
- 启动线程:传入目标对象+Thread()对象.start()
- 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
初识并发问题
package Demo01;
//多个线程同时操作同一个对象
//买火车票的例子
//发现问题,多个线程操作同一个资源,线程不安全,数据紊乱
public class TestThread4 implements Runnable{
//票数
int ticketNums = 10;
@Override
public void run(){
while (true){
if (ticketNums<=0){
break;
}
//模拟延时
try{
Thread.sleep(200);
} catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNums--+"票");
}
}
public static void main(String[] args) {
TestThread4 ticket = new TestThread4();
new Thread(ticket,"小明").start();
new Thread(ticket,"老师").start();
new Thread(ticket,"黄牛").start();
}
}
黄牛拿到了第9票
老师拿到了第10票
小明拿到了第8票
老师拿到了第7票
黄牛拿到了第7票
小明拿到了第6票
老师拿到了第5票
小明拿到了第3票
黄牛拿到了第4票
小明拿到了第2票
黄牛拿到了第0票
老师拿到了第1票
小明拿到了第-1票
龟兔赛跑
package Demo01;
public class Race implements Runnable{
//胜利者
private static String winner;
@Override
public void run(){
for (int i = 0; i<=100;i++){
//模拟兔子休息
if(Thread.currentThread().getName().equals("兔子") && i%10==0){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//判断比赛是否结束
boolean flag = gameover(i);
//比赛结束就停止程序
if(flag){
break;
}
System.out.println(Thread.currentThread().getName()+"-->跑了"+i+"步");
}
}
//判断是否完成比赛
private boolean gameover(int steps){
//判断是否有胜利者
if(winner!=null){
//已经存在胜利者了
return true;
}{
if (steps>=100){
winner = Thread.currentThread().getName();
System.out.println("Winner is"+winner);
return true;
}
}
return false;
}
public static void main(String[] args) {
Race race = new Race();
new Thread(race,"兔子").start();
new Thread(race,"乌龟").start();
}
}
法三创建线程 - 实现Callable接口
- 实现Callable接口,需要返回值类型
- 重写call方法,需要抛出异常
- 创建目标对象
- 创建执行服务:
- 提交执行:
- 获取结果:
- 关闭服务:
package Demo02;
import Demo01.TestThread2;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;
//线程创建方式三:实现Callable接口
public class TestCallable implements Callable<Boolean> {
private String url;
private String name;
public TestCallable(String url, String name){
this.url = url;
this.name = name;
}
//下载图片线程的执行体
@Override
public Boolean call(){
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url,name);
System.out.println("下载了文件名为:"+name);
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
TestCallable t1 = new TestCallable("http://static.runoob.com/images/demo/demo1.jpg","1.jpg");
TestCallable t2 = new TestCallable("http://static.runoob.com/images/demo/demo2.jpg","2.jpg");
TestCallable t3 = new TestCallable("http://static.runoob.com/images/demo/demo3.jpg","3.jpg");
//创建执行服务
ExecutorService ser = Executors.newFixedThreadPool(3);
//提交执行
Future<Boolean> r1 = ser.submit(t1);
Future<Boolean> r2 = ser.submit(t2);
Future<Boolean> r3 = ser.submit(t3);
//获取结果:
boolean rs1 = r1.get();
boolean rs2 = r2.get();
boolean rs3 = r3.get();
System.out.println(rs1);
System.out.println(rs2);
System.out.println(rs3);
//关闭服务
ser.shutdownNow();
}
}
//下载器
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异常,download方法出现问题");
}
}
}
下载了文件名为:1.jpg
下载了文件名为:3.jpg
下载了文件名为:2.jpg
true
true
true
静态代理
- 真实对象与代理对象实现同一个接口
- 代理对象要代理真实角色
- 好处:
- 代理对象可以做真实对象做不了的事情
- 真实对象专注做自己的事情
package proxystatic;
public class StaticProxy {
public static void main(String[] args) {
// new Thread(()-> System.out.println("I Love You")).start();
// new WeddingCompany(new You()).HappyMarry();
You you = new You();
WeddingCompany weddingCompany = new WeddingCompany(you);
weddingCompany.HappyMarry();
}
}
interface Marry{
void HappyMarry();
}
class You implements Marry{
@Override
public void HappyMarry(){
System.out.println("秦老师要结婚了,超开心");
}
}
class WeddingCompany implements Marry{
private Marry target;
public WeddingCompany(Marry target){
this.target = target;
}
@Override
public void HappyMarry(){
before();
this.target.HappyMarry();
after();
}
private void before(){
System.out.println("结婚之前,布置现场");
}
private void after(){
System.out.println("结婚之后,收尾款");
}
}
结婚之前,布置现场
秦老师要结婚了,超开心
结婚之后,收尾款
- Thread类是一个代理,实现了Runnable接口
- 里面真实的类也实现了Runnable接口
- 所以Thread类可以做真实类的代理
Lambda表达式
目的是简化程序
各种类与lambda表达式
越来越方便书写
package lambdaShow;
/*
* 推导lambda表达式
* */
public class TestLambda1 {
//3.静态内部类
static class Like2 implements ILike{
@Override
public void lambda(){
System.out.println("i like lambda2");
}
}
public static void main(String[] args) {
ILike like = new Like();
like.lambda();
like = new Like2();
like.lambda();
//4.局部内部类
class Like3 implements ILike{
@Override
public void lambda(){
System.out.println("i like lambda3");
}
}
like = new Like3();
like.lambda();
//5.匿名内部类,没有类的名称,必须借助接口或者父类
like = new ILike() {
@Override
public void lambda() {
System.out.println("i like lambda4");
}
};
like.lambda();
//6.用lambda简化
like = () -> {
System.out.println("i like lambda5");
};
like.lambda();
}
}
//1.定义一个函数式接口
interface ILike{
void lambda();
}
//2.实现类
class Like implements ILike{
@Override
public void lambda(){
System.out.println("i like lambda");
}
}
i like lambda
i like lambda2
i like lambda3
i like lambda4
i like lambda5
带参数的lambda表达式
package lambdaShow;
public class TestLambda2 {
public static void main(String[] args) {
ILove love = (int a)->{
System.out.println("i love you-->"+a);
};
love.love(2);
}
}
interface ILove{
void love(int a);
}
i love you-->2
微小简化lambda表达式
package lambdaShow;
public class TestLambda2 {
public static void main(String[] args) {
ILove love = null;
// ILove love = (int a)->{
// System.out.println("i love you-->"+a);
// };
// love.love(2);
//简化1.去掉参数类型
// love = (a) ->{
// System.out.println("i love you-->"+a);
// };
// love.love(2);
//简化2.去掉括号
// love = a->{
// System.out.println("i love you-->"+a);
// };
// love.love(2);
//简化3.去掉花括号
love = a-> System.out.println("i love you-->"+a);
love.love(2);
//总结:代码如果有多行,不可以简化花括号
//多个参数传递,要带回来括号,但是可以都省略参数类型
}
}
interface ILove{
void love(int a);
}
i love you-->2
回顾
- Runnable接口中,就只有一个方法,所以我们可以使用lambda表达式来实现这个接口
线程停止
- 线程状态
- 线程方法
- 停止线程
- 不推荐使用JDK提供的stop(), destroy()方法【已废弃】
- 推荐线程自己停下来
- 建议使用一个标志位进行终止变量,当flag=false,则终止线程运行
run...Thread816
run...Thread817
main899
main900
run...Thread818
run...Thread819
run...Thread820
run...Thread821
线程停止了
main901
main902
main903
线程休眠_sleep
- sleep指定当前线程阻塞的毫秒数
- sleep存在异常InterruptedException
- sleep时间达到后,线程进入就绪状态
- sleep可以模拟网络延时,倒计时等
- 每一个对象都有一个锁,sleep不会释放锁
// cpu太快了,没有延迟,可能看不到代码背后的问题,比如线程不安全问题
// 网络延时可以放大问题的发生性
比如之前的买票代码,没有延迟,就可能一个人买完了票
模拟倒计时
package lambdaShow;
// 模拟倒计时
public class TestSleep2 {
public static void main(String[] args) {
try {
tenDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void tenDown() throws InterruptedException {
int num = 10;
while(true){
Thread.sleep(1000);
System.out.println(num--);
if(num<=0){
break;
}
}
}
}
10
9
8
7
6
5
4
3
2
1
0
模拟时间
package state;
import java.text.SimpleDateFormat;
import java.util.Date;
// 模拟倒计时
public class TestSleep2 {
public static void main(String[] args) {
Date startTime = new Date(System.currentTimeMillis());
while (true){
try{
Thread.sleep(1000);
System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
startTime = new Date(System.currentTimeMillis());
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public static void tenDown() throws InterruptedException {
int num = 10;
while(true){
Thread.sleep(1000);
System.out.println(num--);
if(num<=0){
break;
}
}
}
}
14:54:58
14:54:59
14:55:00
14:55:01
14:55:02
线程礼让
- 礼让线程,让当前正在执行的线程暂停,但不阻塞
- 将线程从运行态,转为就绪状态
- 让cpu重新调度,礼让不一定成功!看cpu心情
package state;
public class TestYield {
public static void main(String[] args) {
MyYield myYield = new MyYield();
new Thread(myYield,"a").start();
new Thread(myYield,"b").start();
}
}
class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始执行");
Thread.yield();
System.out.println(Thread.currentThread().getName()+"线程停止执行");
}
}
礼让成功
b线程开始执行
a线程开始执行
b线程停止执行
a线程停止执行
礼让失败
a
a
b
b
例子不好没试出来
线程强制执行_join
相当于插队
package state;
public class TestJoin implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("线程vip来了"+i);
}
}
public static void main(String[] args) throws InterruptedException {
//启动线程
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
thread.start();
thread.sleep(1000);
//主线程
for (int i = 0; i < 1000; i++) {
if(i==200){
thread.join();
}
System.out.println("main"+i);
}
}
}
线程状态观测
- Thread.State
- NEW //尚未启动的线程处于此状态
- RUNNABLE //在Java虚拟机中执行的线程处于此状态
- BLOCKED //被阻塞等待监视器锁定的线程处于此状态
- WAITING //正在等待另一个线程执行特定动作的线程处于此状态
- TIMED_WAITING //正在等待另一个线程执行动作达到指定等待时间的线程处于此状态
- TERMINATED //已退出的线程处于此状态
线程优先级
- Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按学号优先级决定应该调度哪个线程来执行。
- 线程优先级用数字表示,范围从1~10,在Thread中定义了几个常量来表示优先级
- Thread.MIN_PRIORITY = 1;
- Thread.MAX_PRIORITY = 10;
- Thread.NORM_PRIORITY = 5; //默认优先级,不设置的话就是默认优先级
- 线程优先级高不一定先执行,但是给它的资源就会多一些
- 使用以下方式来改变或获取优先级
- getPriority() //获取优先级
- setPriority(int xxx) //改变优先级
守护线程
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 守护线程:后台记录操作日志,监控内存,垃圾回收等待
通过 setDaemon(true) 方法来设置守护线程,参数为true是守护线程,默认是false用户线程
线程同步
并发:同一个对象被多个线程同时操作
- 上万人同时抢100张票
- 两个银行同时取钱
在现实生活中,我们遇到“同一个对象被多个线程同时操作”这种问题,比如食堂打饭,解决办法就是排队
处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这时候我们就需要线程同步。线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程在使用。
队列和锁:在线程访问对象时,加入锁机制 synchronized, 当一个线程获得对象的排他锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题
- 一个线程持有锁会导致其他所有需要此锁的线程挂起
- 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换,和调度延时,引起性能问题
- 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级导致,引起性能问题
三大不安全案例
买票案例
线程不安全,票数出现负数
每个线程都在自己的工作内存交互,内存控制不当会造成数据不一致
买票时,每个线程都把票数拿到自己的内存,当票数只剩一张时,所以它们看到的都是1,然后都去拿这一张票,当第一个线程拿到这张票时,票数修改为0,第二个线程再去拿时,票数就变成了-1.
银行取钱案例
线程不安全的集合
以ArrayList 为例
我们加入了10000个元素,实际加入集合的只有7761个,原因是当这些线程在同一瞬间把集合元素添加到集合的同一位置,就覆盖掉了前面的元素,所以集合中的元素数量才会少