青岚和卿云入住博客园了快来看看(点击查看)

Java线程

Java线程

1、线程简介

进程和线程是两基本的单元被执行,相比较来说在并发程序中更多的是关注的JAVA线程。

Java多线程就是多个任务。

进程

一个进程是一个独立(self contained)的运行环境,它可以被看作一个程序或者一个应用,在一个程序中包含多个进程。JAVA运行环境中作为一个进程包含不同的类和程序流程。

线程

线程可以被叫做轻量级的进程,线程需要更少的资源来创建和存在在这个过程中,线程共享进程的资源。 每个java应用程序有至少一个线程-(main方法主线程),虽然在后台有很多其它java 线程运行例如内存管理,系统管理和单进程等,但是对于应用程序来说-主线程是第一个Java线程并且我们在其中可以创建多线程。多线程是指两个或多个线程同时执行在一个程序中,在单核处理器的计算机中一次只能执行一个线程,操作系统中存在一个“时间切片” 特性用于切分处理器时间在不同的进程和线程中。

Java线程的优势

1.Java线程是轻量级的相比进程来说,当创建一个线程是可以花费少的时间和资源。
2.线程共享它们父进程的数据和代码。
3.上下文切换中通常线程比进程代价更少。
4.线程的内部通讯中是相对容易的比进程通讯。

**Java提供两种方式编写创建线程

1.实现 the java.lang.Runnable 接口.**

确保这个类是可以运行的,我们可以实现java.lang.Runnable中的public void run()方法,为了使得类作为一个线程,我们需要创建一个线程对象,通过这个实例化的对象,然后调用start()方法为了来执行run()方法在一个单独的线程中。
这是一个 java thread 举例 通过实现 Runnable 接口.

复制代码
package com.journaldev.threads;
public class HeavyWorkRunnable implements Runnable {
@Override
public void run() {
System.out.println("Doing heavy processing - START "+Thread.currentThread().getName());
try {
Thread.sleep(1000);
//Get database connection, delete unused data from DB
doDBProcessing();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Doing heavy processing - END "+Thread.currentThread().getName());
}
private void doDBProcessing() throws InterruptedException {
Thread.sleep(5000);
}
}

2.继承 the java.lang.Thread 类.

我们能够继承java.lang.Thread类为了创建我们自己java线程类并且重写run()方法,同时我们能够创建这个对象和调用start()方法为了执行我们自定义的java线程类run()方法。

这是一个 java thread 举例说明了如何继承Thread类

复制代码
package com.journaldev.threads;
public class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
@Override
public void run() {
System.out.println("MyThread - START "+Thread.currentThread().getName());
try {
Thread.sleep(1000);
//Get database connection, delete unused data from DB
doDBProcessing();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("MyThread - END "+Thread.currentThread().getName());
}
private void doDBProcessing() throws InterruptedException {
Thread.sleep(5000);
}
}

小结

  1. 线程就是独立执行路径;
  2. 在程序运行时,即使没有自己创建线程,后台也会有对个线程,如主线程,gc线程;
  3. main() 称之为主线程,为系统的入口,用于执行整个程序;
  4. 在一个进程当中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的;
  5. 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制;
  6. 线程会带来额外的开销,如CPU调度时间,并发控制开销;
  7. 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。

2、线程创建

  • Thread
  • Runnable
  • Callable

Thread

复制代码
package com.q;
/*
* 创建线程方式一
* 继承Thread
* 重写run()方法
* 调用Start()方法,开启线程
*
* 测试作用:
* 可以看出线程开启不一定立即执行,由CPU调度执行*/
public class TestThread extends Thread{
@Override
public void run() {
for (int i=0;i<=1000;i++){
System.out.println(i+" 我在看代码 thread");
}
}
public static void main(String[] args) {
TestThread testThread = new TestThread();
testThread.start();
//testThread.run();
for (int i=0;i<=10;i++){
System.out.println(i+" **** thread+++++");
}
}
}

下载图片实现

复制代码
package com.q;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
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() {
WebDownLoad webDownLoadm = new WebDownLoad();
webDownLoadm.downloader(url,name);
System.out.println(url + " "+name);
}
public static void main(String[] args) {
TestThread2 testThread1 = new TestThread2("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fbpic.588ku.com%2Felement_origin_min_pic%2F16%2F10%2F29%2F2ac8e99273bc079e40a8dc079ca11b1f.jpg&refer=http%3A%2F%2Fbpic.588ku.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1634566265&t=7344ed1c5f3b20fccda1387f1256f016",
"中秋1.png");
TestThread2 testThread2 = new TestThread2("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fbpic.588ku.com%2Felement_origin_min_pic%2F16%2F10%2F29%2F2ac8e99273bc079e40a8dc079ca11b1f.jpg&refer=http%3A%2F%2Fbpic.588ku.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1634566265&t=7344ed1c5f3b20fccda1387f1256f016",
"中秋2.png");
TestThread2 testThread3 = new TestThread2("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fbpic.588ku.com%2Felement_origin_min_pic%2F16%2F10%2F29%2F2ac8e99273bc079e40a8dc079ca11b1f.jpg&refer=http%3A%2F%2Fbpic.588ku.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1634566265&t=7344ed1c5f3b20fccda1387f1256f016",
"中秋3.png");
testThread1.start();
testThread2.start();
testThread3.start();
}
}
/*
* 下载图片类
* */
class WebDownLoad{
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异常,downloader方法出现问题");
}
}
}

Runnable

复制代码
package com.q.runable;
import com.q.thread.TestThread;
/*
* 创建线程方式2
* 实现Runnable接口
* 重写run()方法
* 执行线程需要丢入Runnable接口的实现类,调用start方法
* */
public class TestRunnable implements Runnable{
@Override
public void run() {
for (int i=0;i<=100;i++){
System.out.println(i+" 我在看代码 thread");
}
}
public static void main(String[] args) {
//创建Runnable接口的实现类
TestRunnable testRunnable = new TestRunnable();
//创建线程对象,通过线程对象来开启线程,代理
Thread thread = new Thread(testRunnable);
//开启线程
thread.start();
for (int i=0;i<=1000;i++){
System.out.println(i+" **** thread+++++");
}
}
}

模拟购票

代码

复制代码
package com.q.并发;
public class Ticket implements Runnable{
private Integer ticketNum= 20;
@Override
public void run() {
while(true){
if(ticketNum<=0){
break;
}
//测试
if (ticketNum.equals(0)){
break;
}
System.out.println(Thread.currentThread().getName()+"--拿到了第"+ticketNum--+"张票");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
//线程对象
Ticket ticket = new Ticket();
//启动线程
new Thread(ticket,"青岚").start();
new Thread(ticket,"卿云").start();
new Thread(ticket,"飞羽").start();
}
}

运行结果

复制代码
青岚--拿到了第20张票
飞羽--拿到了第18张票
卿云--拿到了第19张票
青岚--拿到了第17张票
飞羽--拿到了第16张票
卿云--拿到了第15张票
青岚--拿到了第14张票
卿云--拿到了第13张票
飞羽--拿到了第13张票
青岚--拿到了第12张票
卿云--拿到了第11张票
飞羽--拿到了第10张票
青岚--拿到了第9张票
卿云--拿到了第8张票
飞羽--拿到了第7张票
青岚--拿到了第6张票
卿云--拿到了第5张票
飞羽--拿到了第4张票
卿云--拿到了第3张票
青岚--拿到了第2张票
飞羽--拿到了第1张票
Process finished with exit code 0

3、案列-模拟龟兔赛跑

代码(一版)

复制代码
package com.q.龟兔;
public class Rose implements Runnable{
private static String winner;
@Override
public void run() {
for (int i=0;i<=100;i++){
boolean flag= gameOver(i);
if(flag){
break;
}
System.out.println(Thread.currentThread().getName()+"-->跑了"+i+"步");
}
}
private boolean gameOver(Integer steps){
if(winner!=null){
return true;
}
else{
if (steps>=100){
winner = Thread.currentThread().getName();
System.out.println("胜利者是:"+winner);
return false;
}
}
return false;
}
public static void main(String[] args) {
Rose rose = new Rose();
new Thread(rose,"乌龟").start();
new Thread(rose,"兔子").start();
}
}

结果

复制代码
乌龟-->跑了0步
兔子-->跑了0步
乌龟-->跑了1步
兔子-->跑了1步
乌龟-->跑了2步
兔子-->跑了2步
乌龟-->跑了3步
兔子-->跑了3步
乌龟-->跑了4步
兔子-->跑了4步
乌龟-->跑了5步
兔子-->跑了5步
乌龟-->跑了6步
兔子-->跑了6步
乌龟-->跑了7步
兔子-->跑了7步
乌龟-->跑了8步
兔子-->跑了8步
乌龟-->跑了9步
兔子-->跑了9步
乌龟-->跑了10步
兔子-->跑了10步
乌龟-->跑了11步
乌龟-->跑了12步
兔子-->跑了11步
兔子-->跑了12步
兔子-->跑了13步
乌龟-->跑了13步
乌龟-->跑了14步
兔子-->跑了14步
乌龟-->跑了15步
兔子-->跑了15步
兔子-->跑了16步
兔子-->跑了17步
兔子-->跑了18步
兔子-->跑了19步
兔子-->跑了20步
兔子-->跑了21步
兔子-->跑了22步
兔子-->跑了23步
兔子-->跑了24步
兔子-->跑了25步
兔子-->跑了26步
兔子-->跑了27步
兔子-->跑了28步
兔子-->跑了29步
兔子-->跑了30步
乌龟-->跑了16步
乌龟-->跑了17步
乌龟-->跑了18步
兔子-->跑了31步
乌龟-->跑了19步
兔子-->跑了32步
乌龟-->跑了20步
兔子-->跑了33步
乌龟-->跑了21步
兔子-->跑了34步
乌龟-->跑了22步
兔子-->跑了35步
乌龟-->跑了23步
兔子-->跑了36步
乌龟-->跑了24步
兔子-->跑了37步
乌龟-->跑了25步
兔子-->跑了38步
乌龟-->跑了26步
兔子-->跑了39步
乌龟-->跑了27步
兔子-->跑了40步
乌龟-->跑了28步
兔子-->跑了41步
兔子-->跑了42步
兔子-->跑了43步
兔子-->跑了44步
兔子-->跑了45步
兔子-->跑了46步
兔子-->跑了47步
兔子-->跑了48步
兔子-->跑了49步
兔子-->跑了50步
兔子-->跑了51步
兔子-->跑了52步
兔子-->跑了53步
乌龟-->跑了29步
兔子-->跑了54步
兔子-->跑了55步
乌龟-->跑了30步
乌龟-->跑了31步
兔子-->跑了56步
乌龟-->跑了32步
兔子-->跑了57步
兔子-->跑了58步
兔子-->跑了59步
乌龟-->跑了33步
兔子-->跑了60步
兔子-->跑了61步
兔子-->跑了62步
兔子-->跑了63步
兔子-->跑了64步
乌龟-->跑了34步
兔子-->跑了65步
乌龟-->跑了35步
兔子-->跑了66步
乌龟-->跑了36步
兔子-->跑了67步
乌龟-->跑了37步
兔子-->跑了68步
乌龟-->跑了38步
兔子-->跑了69步
乌龟-->跑了39步
兔子-->跑了70步
乌龟-->跑了40步
兔子-->跑了71步
乌龟-->跑了41步
兔子-->跑了72步
乌龟-->跑了42步
兔子-->跑了73步
乌龟-->跑了43步
兔子-->跑了74步
乌龟-->跑了44步
兔子-->跑了75步
乌龟-->跑了45步
兔子-->跑了76步
乌龟-->跑了46步
兔子-->跑了77步
乌龟-->跑了47步
兔子-->跑了78步
乌龟-->跑了48步
兔子-->跑了79步
乌龟-->跑了49步
兔子-->跑了80步
乌龟-->跑了50步
兔子-->跑了81步
乌龟-->跑了51步
兔子-->跑了82步
乌龟-->跑了52步
兔子-->跑了83步
乌龟-->跑了53步
兔子-->跑了84步
乌龟-->跑了54步
兔子-->跑了85步
乌龟-->跑了55步
兔子-->跑了86步
乌龟-->跑了56步
兔子-->跑了87步
乌龟-->跑了57步
乌龟-->跑了58步
乌龟-->跑了59步
兔子-->跑了88步
乌龟-->跑了60步
兔子-->跑了89步
乌龟-->跑了61步
兔子-->跑了90步
乌龟-->跑了62步
兔子-->跑了91步
乌龟-->跑了63步
兔子-->跑了92步
乌龟-->跑了64步
兔子-->跑了93步
乌龟-->跑了65步
兔子-->跑了94步
乌龟-->跑了66步
兔子-->跑了95步
乌龟-->跑了67步
兔子-->跑了96步
乌龟-->跑了68步
兔子-->跑了97步
乌龟-->跑了69步
兔子-->跑了98步
乌龟-->跑了70步
兔子-->跑了99步
乌龟-->跑了71步
胜利者是:兔子
兔子-->跑了100步
Process finished with exit code 0

升级版(兔子休眠)

复制代码
package com.q.龟兔;
public class Rose implements Runnable{
private static String winner;
@Override
public void run() {
for (int i=0;i<=100;i++){
if (Thread.currentThread().getName().equals("兔子")&&i%20==0){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
boolean flag= gameOver(i);
if(flag){
break;
}
System.out.println(Thread.currentThread().getName()+"-->跑了"+i+"步");
}
}
private boolean gameOver(Integer steps){
if(winner!=null){
return true;
}
else{
if (steps>=100){
winner = Thread.currentThread().getName();
System.out.println("胜利者是:"+winner);
return false;
}
}
return false;
}
public static void main(String[] args) {
Rose rose = new Rose();
new Thread(rose,"乌龟").start();
new Thread(rose,"兔子").start();
}
}

结果

复制代码
乌龟-->跑了0步
乌龟-->跑了1步
乌龟-->跑了2步
乌龟-->跑了3步
乌龟-->跑了4步
乌龟-->跑了5步
乌龟-->跑了6步
乌龟-->跑了7步
兔子-->跑了0步
乌龟-->跑了8步
兔子-->跑了1步
乌龟-->跑了9步
兔子-->跑了2步
乌龟-->跑了10步
兔子-->跑了3步
乌龟-->跑了11步
兔子-->跑了4步
乌龟-->跑了12步
兔子-->跑了5步
乌龟-->跑了13步
兔子-->跑了6步
乌龟-->跑了14步
兔子-->跑了7步
乌龟-->跑了15步
兔子-->跑了8步
乌龟-->跑了16步
兔子-->跑了9步
乌龟-->跑了17步
兔子-->跑了10步
乌龟-->跑了18步
兔子-->跑了11步
乌龟-->跑了19步
兔子-->跑了12步
乌龟-->跑了20步
兔子-->跑了13步
乌龟-->跑了21步
兔子-->跑了14步
乌龟-->跑了22步
兔子-->跑了15步
乌龟-->跑了23步
兔子-->跑了16步
乌龟-->跑了24步
兔子-->跑了17步
乌龟-->跑了25步
兔子-->跑了18步
乌龟-->跑了26步
兔子-->跑了19步
乌龟-->跑了27步
乌龟-->跑了28步
乌龟-->跑了29步
乌龟-->跑了30步
乌龟-->跑了31步
乌龟-->跑了32步
乌龟-->跑了33步
乌龟-->跑了34步
乌龟-->跑了35步
乌龟-->跑了36步
乌龟-->跑了37步
乌龟-->跑了38步
乌龟-->跑了39步
乌龟-->跑了40步
乌龟-->跑了41步
乌龟-->跑了42步
乌龟-->跑了43步
乌龟-->跑了44步
乌龟-->跑了45步
乌龟-->跑了46步
乌龟-->跑了47步
乌龟-->跑了48步
乌龟-->跑了49步
乌龟-->跑了50步
乌龟-->跑了51步
乌龟-->跑了52步
乌龟-->跑了53步
乌龟-->跑了54步
乌龟-->跑了55步
乌龟-->跑了56步
乌龟-->跑了57步
乌龟-->跑了58步
乌龟-->跑了59步
乌龟-->跑了60步
乌龟-->跑了61步
乌龟-->跑了62步
乌龟-->跑了63步
乌龟-->跑了64步
乌龟-->跑了65步
乌龟-->跑了66步
乌龟-->跑了67步
乌龟-->跑了68步
乌龟-->跑了69步
乌龟-->跑了70步
乌龟-->跑了71步
乌龟-->跑了72步
乌龟-->跑了73步
乌龟-->跑了74步
乌龟-->跑了75步
乌龟-->跑了76步
乌龟-->跑了77步
乌龟-->跑了78步
乌龟-->跑了79步
乌龟-->跑了80步
乌龟-->跑了81步
乌龟-->跑了82步
乌龟-->跑了83步
乌龟-->跑了84步
乌龟-->跑了85步
乌龟-->跑了86步
乌龟-->跑了87步
兔子-->跑了20步
乌龟-->跑了88步
兔子-->跑了21步
乌龟-->跑了89步
兔子-->跑了22步
乌龟-->跑了90步
兔子-->跑了23步
乌龟-->跑了91步
兔子-->跑了24步
乌龟-->跑了92步
兔子-->跑了25步
乌龟-->跑了93步
兔子-->跑了26步
乌龟-->跑了94步
兔子-->跑了27步
乌龟-->跑了95步
兔子-->跑了28步
乌龟-->跑了96步
兔子-->跑了29步
乌龟-->跑了97步
兔子-->跑了30步
乌龟-->跑了98步
兔子-->跑了31步
乌龟-->跑了99步
兔子-->跑了32步
胜利者是:乌龟
乌龟-->跑了100步
Process finished with exit code 0

4、Callable接口

介绍

多线程之callable详解
面试有人会问:线程的实现方式有几种?
很多人可能回答:2种,继承Thread类,实现Runnable接口。
很多忽略了callable这种方式。
也许有人知道callable,也知道callable和Runnable的区别是callable可以有返回值,也可以抛出异常的特性,而Runnable没有。

这里估计很多人懵逼,接下来我们就从源码层次讲解这个问题。

注意callable可以有返回值,也可以抛出异常这点很关键。
很多时候我们让多线程去帮我们处理事情,是需要拿到返回值的,有了异常也可以处理,比如某宝的APP页面,一个页面展示3个块,而每个块展示的信息从后端获取的接口都不一样,那么是让前端调后端3次接口吗?
肯定不行,后端可以把3个块的信息,包装成一个接口,全部返回,那么问题来了,后端调用3个接口,比如第一个接口需要1秒,第二个需要2秒,第三个需要3秒,那么包装的这个接口响应时间最少6秒,怎么解决这个问题呢,可以用多线程来帮我们解决。
启动3个线程,每个线程去调用一个接口,那么3个线程一共执行完的时间就是最慢的那个线程的执行时间,这样接口的响应时间就变成了3秒,一下节省了一半的时间。
那么问题来了,线程如何把执行的业务代码的结果返回来呢?这时候就用到callable了。

实现Callable接口

  1. 实现Callable接口,需要返回值类型;
  2. 重写Call()方法,需要抛出异常;
  3. 创建目标对象;
  4. 创建执行服务:
  5. 提交执行:
  6. 获取结果:
  7. 关闭服务:

代码

复制代码
package com.q.Call;
import com.q.thread.TestThread2;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.sql.PreparedStatement;
import java.util.concurrent.*;
import static java.util.concurrent.Executors.*;
/*
* 实现线程方式三
* 编写图片下载器
* */
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() throws Exception {
WebDownLoad webDownLoadm = new WebDownLoad();
webDownLoadm.downloader(url,name);
System.out.println(url + " "+name);
return true;
}
public static void main(String[] args) {
TestCallable c1 = new TestCallable("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fbpic.588ku.com%2Felement_origin_min_pic%2F16%2F10%2F29%2F2ac8e99273bc079e40a8dc079ca11b1f.jpg&refer=http%3A%2F%2Fbpic.588ku.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1634566265&t=7344ed1c5f3b20fccda1387f1256f016",
"中秋4.png");
TestCallable c2 = new TestCallable("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fbpic.588ku.com%2Felement_origin_min_pic%2F16%2F10%2F29%2F2ac8e99273bc079e40a8dc079ca11b1f.jpg&refer=http%3A%2F%2Fbpic.588ku.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1634566265&t=7344ed1c5f3b20fccda1387f1256f016",
"中秋5.png");
TestCallable c3 = new TestCallable("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fbpic.588ku.com%2Felement_origin_min_pic%2F16%2F10%2F29%2F2ac8e99273bc079e40a8dc079ca11b1f.jpg&refer=http%3A%2F%2Fbpic.588ku.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1634566265&t=7344ed1c5f3b20fccda1387f1256f016",
"中秋6.png");
//创建执行服务
ExecutorService service = newFixedThreadPool(3);
//提交执行
Future<Boolean> r1 = service.submit(c1);
Future<Boolean> r2 = service.submit(c2);
Future<Boolean> r3 = service.submit(c3);
//获取结果
try {
boolean b1 = r1.get();
boolean b2 = r2.get();
boolean b3 = r3.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
//关闭服务
service.shutdown();
}
}
/*
* 下载图片类
* */
class WebDownLoad{
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异常,downloader方法出现问题");
}
}
}

结果

复制代码
https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fbpic.588ku.com%2Felement_origin_min_pic%2F16%2F10%2F29%2F2ac8e99273bc079e40a8dc079ca11b1f.jpg&refer=http%3A%2F%2Fbpic.588ku.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1634566265&t=7344ed1c5f3b20fccda1387f1256f016 中秋6.png
https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fbpic.588ku.com%2Felement_origin_min_pic%2F16%2F10%2F29%2F2ac8e99273bc079e40a8dc079ca11b1f.jpg&refer=http%3A%2F%2Fbpic.588ku.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1634566265&t=7344ed1c5f3b20fccda1387f1256f016 中秋5.png
https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fbpic.588ku.com%2Felement_origin_min_pic%2F16%2F10%2F29%2F2ac8e99273bc079e40a8dc079ca11b1f.jpg&refer=http%3A%2F%2Fbpic.588ku.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1634566265&t=7344ed1c5f3b20fccda1387f1256f016 中秋4.png
Process finished with exit code 0

5、线程的静态代理

演示

  • 你:真实角色
  • 婚庆公司:代理你,处理结婚相关事宜
  • 结婚:实现结婚接口即可

代码

复制代码
package com.q.staticProxy;
public class StaticProxy {
public static void main(String[] args) {
WeddingCompany weddingCompany = new WeddingCompany(new 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 after() {
System.out.println("结婚之后,收费");
}
private void before() {
System.out.println("结婚之前,布置现场");
}
}

分析

代理模式中真实角色与代理角色都必须实现接口;

代理角色处理真实目标角色的要求

自己要做的是交给代理,由代理完成,真实角色是参与者,代理公司是行动者。

6、Lamda表达式

Lambda 表达式 − Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中)

Lambda表达式是JAVA8中提供的一种新的特性,它支持JAVA也能进行简单的“函数式编程”。

它是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数。

传统的类结构:

复制代码
package com.q.lamda;
/*
* 推导Lamda表达式
* */
public class TestLamda {
public static void main(String[] args) {
LamdaLike lamdaLike = new Like();
lamdaLike.lamda();
}
}
/*
* 定义一个函数接口
* */
interface LamdaLike{
void lamda();
}
class Like implements LamdaLike{
@Override
public void lamda() {
System.out.println("I am a Lamda ");
}
}

升级版:

复制代码
package com.q.lamda;
/*
* 推导Lamda表达式
* */
public class TestLamda {
/* //定义静态内部类
* */
static class Like2 implements LamdaLike{
@Override
public void lamda() {
System.out.println("I am a Lamda2");
}
}
public static void main(String[] args) {
LamdaLike lamdaLike = new Like();
lamdaLike.lamda();
lamdaLike = new Like2();
lamdaLike.lamda();
/*\
* 局部类
* */
class Like3 implements LamdaLike{
@Override
public void lamda() {
System.out.println("I am a Lamda3");
}
}
lamdaLike = new Like3();
lamdaLike.lamda();
/*
* 匿名内部类
* 必须借助接口或者父类
* */
lamdaLike = new Like(){
@Override
public void lamda() {
System.out.println("I am a Lamda4");
}
};
lamdaLike.lamda();
/*
* Lamdba 简化
* */
lamdaLike = ()->{
System.out.println("I am a Lamdba5");
};
lamdaLike.lamda();
}
}
/*
* 定义一个函数接口
* */
interface LamdaLike{
void lamda();
}
class Like implements LamdaLike{
@Override
public void lamda() {
System.out.println("I am a Lamda ");
}
}

避免代码过多,过多的匿名内部类;

案列(带参数)

复制代码
package com.q.lamda;
public class TestLamdba {
static class Love1 implements LamdbaLove{
@Override
public void love(String x, String y) {
System.out.println(x+"@@"+y);
}
}
public static void main(String[] args) {
LamdbaLove love = new Love();
love.love("青岚","卿云");
love = new Love1();
love.love("青岚","卿云");
love = new LamdbaLove() {
@Override
public void love(String x, String y) {
System.out.println(x+"@@@"+y+"匿名内部类");
}
};
love.love("青岚","卿云");
love = (String x,String y)->{
System.out.println(x+"@@@"+y+"Lamdba表达式");
};
love.love("青岚","卿云");
/*
* 去掉参数类型
* */
LamdbaLove love1 = (x,y)->{
System.out.println(x+"@@@"+y+"Lamdba表达式");
};
love1.love("青岚","卿云");
}
}
interface LamdbaLove{
void love(String x,String y);
}
class Love implements LamdbaLove{
@Override
public void love(String x, String y) {
System.out.println(x+"@"+y);
}
}

7、线程状态

线程有五个状态

线程停止

复制代码
package com.q.线程状态;
/*
* 线程测试stop
* 建议线程自动停止,---》利用次数,不建议死循环
* 不使用JDK不建议使用的方法
* */
public class TestStop implements Runnable{
//设置表示位
private boolean flag = true;
@Override
public void run() {
int i=0;
while(flag){
System.out.println("run.....Thread"+i++);
}
}
public void stop(){
this.flag=false;
}
public static void main(String[] args) {
TestStop testStop = new TestStop();
Thread thread = new Thread(testStop);
thread.start();
for (int i=0;i<1000;i++){
System.out.println(i);
if( i==900 ){
//调用stop方法切换标志位,停止线程
testStop.stop();
System.out.println("停止线程");
break;
}
}
}
}

线程休眠

复制代码
package com.q.线程状态;
import java.text.SimpleDateFormat;
import java.util.Date;
/*
* 模拟倒计时
* */
public class TestSleep {
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(){
int num = 10;
while(true){
try {
Thread.sleep(1000);
System.out.println(num);
num--;
} catch (InterruptedException e) {
e.printStackTrace();
}
if(num==0){
break;
}
}
}
}

线程礼让

yield

  • 礼让线程,让当前正在执行的线程暂停,但不阻塞
  • 将线程从运行状态转为就绪状态
  • 让CPU重新调度,礼让不一定成功!看CPU心情
复制代码
package com.q.线程状态;
/*
* 测试礼让线程
* */
public class TestYield {
public static void main(String[] args) {
MyYield myYield = new MyYield();
new Thread(myYield,"卿云").start();
new Thread(myYield,"青岚").start();
}
}
class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始执行");
Thread.yield();
System.out.println(Thread.currentThread().getName()+"线程停止执行");
}
}

线程强制执行

复制代码
package com.q.线程状态;
import org.junit.rules.TestRule;
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) {
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
thread.start();
for (int i = 0; i < 50; i++) {
if(i==30){
try {
thread.join();
System.out.println("------------------");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("main()"+i);
}
}
}

观测线程状态

复制代码
package com.q.线程状态;
public class TestState {
public static void main(String[] args) throws InterruptedException {
Runnable target;
Thread thread = new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("*****************");
}
});
//观察状态
Thread.State state = thread.getState();
System.out.println(state);
//观察启动后
thread.start();
state = thread.getState();
System.out.println(state);
while (state !=Thread.State.TERMINATED){
Thread.sleep(100);
state = thread.getState();
System.out.println(state);
}
}
}

结果:

复制代码
NEW
RUNNABLE
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
*****************
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
*****************
TERMINATED

8、线程的优先级

复制代码
package com.q.priority;
public class TestPriority {
public static void main(String[] args) {
//主线程优先级
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
MyPriority myPriority = new MyPriority();
Thread t1 = new Thread(myPriority);
Thread t2 = new Thread(myPriority);
Thread t3 = new Thread(myPriority);
Thread t4 = new Thread(myPriority);
Thread t5 = new Thread(myPriority);
Thread t6 = new Thread(myPriority);
//设置线程优先级
//先设置优先级,在启动
t1.start();
t2.setPriority(4);
t3.setPriority(5);
t3.start();
t4.start();
t5.setPriority(3);
t5.start();
t6.setPriority(Thread.MAX_PRIORITY);
t6.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
}
}

9、守护线程

daemon

  • 线程分为用户线程和守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
  • 如,后台记录操作日志,监控内存,垃圾回收等待

10、线程同步机制

并发: 同一个对象被多个线程同时操作

要说明线程同步问题首先要说明Java线程的两个特性,可见性和有序性。多个线程之间是不能直接传递数据交互的,它们之间的交互只能通过共享变量来实现。拿上篇博文中的例子来说明,在多个线程之间共享了Count类的一个对象,这个对象是被创建在主内存(堆内存)中,每个线程都有自己的工作内存(线程栈),工作内存存储了主内存Count对象的一个副本,当线程操作Count对象时,首先从主内存复制Count对象到工作内存中,然后执行代码count.increment(),改变了num值,最后用工作内存Count刷新主内存Count。当一个对象在多个内存中都存在副本时,如果一个内存修改了共享变量,其它线程也应该能够看到被修改后的值,此为可见性。多个线程执行时,CPU对线程的调度是随机的,我们不知道当前程序被执行到哪步就切换到了下一个线程,一个最经典的例子就是银行汇款问题,一个银行账户存款100,这时一个人从该账户取10元,同时另一个人向该账户汇10元,那么余额应该还是100。那么此时可能发生这种情况,A线程负责取款,B线程负责汇款,A从主内存读到100,B从主内存读到100,A执行减10操作,并将数据刷新到主内存,这时主内存数据100-10=90,而B内存执行加10操作,并将数据刷新到主内存,最后主内存数据100+10=110,显然这是一个严重的问题,我们要保证A线程和B线程有序执行,先取款后汇款或者先汇款后取款,此为有序性。本文讲述了JDK5.0之前传统线程的同步方式,更高级的同步方式可参见Java线程(八):锁对象Lock-同步问题更完美的处理方式。

每个锁对象(JLS中叫monitor)都有两个队列,一个是就绪队列,一个是阻塞队列,就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程,当一个线程被唤醒(notify)后,才会进入到就绪队列,等待CPU的调度,反之,当一个线程被wait后,就会进入阻塞队列,等待下一次被唤醒,这个涉及到线程间的通信,下一篇博文会说明。看我们的例子,当第一个线程执行输出方法时,获得同步锁,执行输出方法,恰好此时第二个线程也要执行输出方法,但发现同步锁没有被释放,第二个线程就会进入就绪队列,等待锁被释放。一个线程执行互斥代码过程如下:

复制代码
1.获得同步锁;
2. 清空工作内存;
3. 从主内存拷贝对象副本到工作内存;
4. 执行代码(计算或者输出等);
5. 刷新主内存数据;
6. 释放同步锁。

所以,synchronized既保证了多线程的并发有序性,又保证了多线程的内存可见性。

同步锁

ava 中的 synchronized 关键字可以在多线程环境下用来作为线程安全的同步锁。本文主要对 synchronized 的作用,以及其有效范围进行讨论。
Java中的对象锁和类锁:java的对象锁和类锁在锁的概念上基本上和内置锁是一致的,但是,两个锁实际是有很大的区别的,对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态方法或者一个类的class对象上的。我们知道,类的对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定实例方法和静态方法的区别的。

synchronized 关键字主要有以下几种用法:

  • 非静态方法的同步;
  • 静态方法的同步;
  • 代码块。

下面分对象锁和类锁来分别说明 synchronized 用法:

对象锁

非静态方法使用 synchronized 修饰的写法,修饰实例方法时,锁定的是当前对象:

复制代码
public synchronized void test(){
// TODO
}

代码块使用 synchronized 修饰的写法,使用代码块,如果传入的参数是 this,那么锁定的也是当前的对象:

复制代码
public void test(){
synchronized (this) {
// TODO
}
}

下面通过例子来说明对象锁:
定义一个类,方法如下,将 count 自减,从 5 到 0:

复制代码
public class TestSynchronized {
public synchronized void minus() {
int count = 5;
for (int i = 0; i < 5; i++) {
count--;
System.out.println(Thread.currentThread().getName() + " - " + count);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
}

测试调用方法如下:

复制代码
public class Run {
public static void main(String[] args) {
final TestSynchronized test = new TestSynchronized();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
​ test.minus();
​ }
​ });
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
​ test.minus();
​ }
​ });
​ thread1.start();
​ thread2.start();
}
}

两个线程 thread1 和 thread2,同时访问对象的方法,由于该方法是 synchronized 关键字修饰的,那么这两个线程都需要获得该对象锁,一个获得后另一个线程必须等待。所以我们可以猜测运行结果应该是,一个线程执行完毕后,另一个线程才开始执行

案例(购票)

复制代码
package syn;
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
new Thread(buyTicket,"青岚").start();
new Thread(buyTicket,"卿云").start();
new Thread(buyTicket,"致非").start();
}
}
class BuyTicket implements Runnable{
private Integer ticketNum=10;
boolean flag = true;
@Override
public void run() {
//买票
while(flag){
try {
Thread.sleep(100);
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private synchronized void buy(){
if (ticketNum<=0){
return;
}
System.out.println(Thread.currentThread().getName()+"-->拿到"+ticketNum--);
}
}

结果

复制代码
卿云-->拿到10
青岚-->拿到9
致非-->拿到8
卿云-->拿到7
青岚-->拿到6
致非-->拿到5
卿云-->拿到4
青岚-->拿到3
致非-->拿到2
卿云-->拿到1

同步锁代码

复制代码
package Lock;
import java.util.List;
/*死锁
* 多个线程互相拥有资源,对方却无法使用
* */
public class DeadLock {
public static void main(String[] args) {
MakeUp g1 = new MakeUp(1,"青岚");
MakeUp g2 = new MakeUp(0,"卿云");
g1.start();
g2.start();
}
}
class Lipstick{
}
class Mirror {
}
class MakeUp extends Thread{
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
int choise;
String name;
public MakeUp(int choise, String name) {
this.choise = choise;
this.name = name;
}
@Override
public void run() {
makeUp();
}
//互相持有对方的锁
private void makeUp(){
if (choise==0){
synchronized (lipstick){//获得口红的锁
System.out.println("获得口红的锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (mirror){
System.out.println("获得镜子的锁");
}
}else {
synchronized (mirror){//获得口红的锁
System.out.println("获得镜子的锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (lipstick){
System.out.println("获得口红的锁");
}
}
}
}

11、Lock 锁

12、线程协作

生产者与消费者模式

posted @ 2021-09-20 16:01  致非  阅读(234)  评论(0)    收藏  举报
编辑推荐:
· 源码浅析:SpringBoot main方法结束为什么程序不停止
· C#性能优化:为何 x * Math.Sqrt(x) 远胜 Math.Pow(x, 1.5)
· 本可避免的P1事故:Nginx变更导致网关请求均响应400
· 还在手写JSON调教大模型?.NET 9有新玩法
· 复杂业务系统线上问题排查过程
阅读排行:
· AI 的力量,开发者的翅膀:欢迎使用字节旗下的 AI 原生开发工具 TRAE
· 「闲聊文」准大三的我,思前想后还是不搞java了
· .NET 9 的免费午餐:GZip 性能提升38.3%
· 2025年:是时候重新认识System.Text.Json了
· 开源新旗舰 GLM-4.5:不想刷榜,只想干活儿
点击右上角即可分享
微信分享提示