小伙伴晚上好,今天有看到一句话跟小伙伴们分享一下:理想的热情是有棱角的,而工作的琐碎却一直在磨平这些棱角。这句话每个人的理解都不一样,共同点就是emmm容易引起共鸣吧。 OK,今天给大家带来的是Java当中,多线程的一些基础内容的总结。(有需要改正的地方,欢迎大家在评论区直接提出来哦)

一、基本概念

1.程序:指为完成特定任务编写的一组指令的集合。

2.进程:程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:自身的产生–存在–消亡—生命周期。

3.线程:指程序内部的一条执行路径;若一个进程同一时间并行执行多个线程,就是支持多线程的(多个线程共享内存区域的方法区和堆);线程是调度和执行的最小单位,每个线程拥有独立的运行栈和程序计数器(pc)。

4.并行和并发:

并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。

并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀。

1.多线程的优点:

1.提高应用程序的响应。

2.提高计算机系统CPU的利用率

3.改善程序结构。将即长又复杂的进程分为多个线程;独立运行,利于理解和修改。

2.何时需要多线程

1.程序需要同时执行两个或多个任务。

2.程序需要实现一些需要等待的任务时,如用户输入、文件读写、网络操作、搜索等。

3.需要一些后台运行的程序时。

二、线程的创建和使用

通过java.lang.Thread类实现

1.多线程的创建方式一:

继承于Thread类(步骤):

1.创建一个继承于Thread类和子类。

2.重写Thread类的run()---->将此线程执行的操作声明在run方法中。

3.创建Thread类的子类的对象。

4.通过此对象调用start()。

例子:遍历100以内的所有偶数

package com.TestThread.java;

// 1.创建一个继承于Thread类和子类

class MyThread extends Thread{

// 2.重写Thread类的run()---->将此线程执行的操作声明在run方法中

@Override

public void run() {

for (int i = 0;i<100;i++){

if(i%2 == 0){

System.out.println(i);

}

}

}

}

public class ThreadTest {

public static void main(String[] args) {

// 3.创建Thread类的子类的对象

MyThread t1= new MyThread();

// 4.通过此对象调用start()

t1.start();//

}

}

start的作用:

1.启动当前线程。

2.调用当前线程的run()。

Thread类的api

void start():启动线程,并执行对象的run()方法

run():线程在被调度时执行的操作

String getName():返回线程的名称

void setName(String name):设置该线程名称

static Thread currentThread():返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable实现类

static void yield():线程让步

1.暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程

2.若队列中没有相同优先级,忽略此方法

join():当某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到join()方法加入的join线程执行完为止

static void sleep(long mills):一旦执行使得线程进入阻塞状态。sleep()不会释放锁。

Java的调度:同优先级线程组成先进先出队列(先到先服务),使用时间片策略;高优先级,使用优先调度的抢占式策略

2.多线程的创建方式二:

实现Runnable接口(步骤):

1.创建一个实现Runnable接口的类

2.实现类去实现Runnable中的抽象方法:run()

3.创建实现类的对象

4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象

5.通过Thread类的对象调用start():① 启动线程 ② 调用当前线程的run()—>调用Runnable类型的target

// 1.创建一个实现Runnable接口的类

class MyThread 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 ThreadTest{

public static void main(String[] args) {

// 3.创建实现类的对象

MyThread mThread = new MyThread();

// 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象

Thread t1= new Thread(mThread);

// 5.通过Thread类的对象调用start():① 启动线程 ② 调用当前线程的run()--->调用Runnable类型的target

t1.start();

}

}

创建线程两种方式的对比

开发中优先选择:实现Runnable接口的方式

原因:

1.实现的方式没有类的单继承性的局限性

2.实现的方式更适合来处理多个线程有共享数据的情况

联系:public class Thread implement Runnable

相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。

案例:(方式一实现)

package TestThread.java;

/**

* 例子:创建三个窗口卖票,总票数为100张.使用继承Thread类的方式

* 存在线程的安全问题,待解决。

*/

class Window extends Thread{

private static int ticket = 100;

@Override

public void run() {

while(true){

if(ticket > 0){

System.out.println(getName() + ":卖票,票号为:" + ticket);

ticket--;

}else{

break;

}

}

}

}

public class WindowTest {

public static void main(String[] args) {

Window t1 = new Window();

Window t2 = new Window();

Window t3 = new Window();

t1.setName("窗口1");

t2.setName("窗口2");

t3.setName("窗口3");

t1.start();

t2.start();

t3.start();

}

}

(方式二实现):

package TestThread.java;

/**

* 例子:创建三个窗口卖票,总票数为100张.使用实现Runnable接口的方式。

* 存在线程的安全问题,待解决。

*/

class Window1 implements Runnable{

private int ticket = 100;

@Override

public void run() {

while(true){

if(ticket > 0){

System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);

ticket--;

}else{

break;

}

}

}

}

public class WindowTest1 {

public static void main(String[] args) {

Window1 w = new Window1();

Thread t1 = new Thread(w);

Thread t2 = new Thread(w);

Thread t3 = new Thread(w);

t1.setName("窗口1");

t2.setName("窗口2");

t3.setName("窗口3");

t1.start();

t2.start();

t3.start();

}

}

三、线程的生命周期

四、线程同步(主要解决线程的安全问题)

1.问题:卖票过程中出现了重票、错票------->出现了线程安全问题

2.问题出现的原因:当某个线程操作车票的过程中,尚未完成时,其他线程参与进来,也操作车票。

3.如何解决:当一个线程在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。

4.Java中,通过同步机制,来解决线程的安全问题。

解决线程安全问题的方式一:同步代码块

synchronized(同步监视器){

//需要被同步的代码

}

明:1.操作共享数据的代码,即为需要被同步的代码。---->不能包含代码多了,也不能包含代码少了。

共享数据:多个线程共同操作的变量。(只有有了共享数据,才会存在线程安全问题)。比如:ticket就是共享数据。

2.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。Object obj = new Object();//不能放到run()函数里面,放到外面。

要求:多个线程必须要用同一把锁,一定要唯一。设置对象唯一时,可以把其设置为static,设为共享的

**补充:**在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。在继承Tread类创建多线程的方式中,慎用this来从当同步监视器。可以考虑使用当前类,来充当同步监视器

解决线程安全问题的方式二:同步方法

如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明为同步(synchroniezd)的。

关于同步方法的总结:

1.同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。

2.非静态的同步方法:(使用同步方法处理实现Runnable接口方式中的线程安全问题),同步监视器:this

静态的同步方法:(使用同步方法处理继承Thread类的方式中的线程安全问题),同步监视器:当前类 window.class

3.同步方式。解决了线程安全的问题。-------好处

操作同步代码时,只能有一个线程参与。相当于一个单线程的过程,效率低。-------局限性

class Window1 implements Runnable{

private int ticket = 100;

// Object obj = new Object();

// Dog dog = new Dog();

@Override

public void run() {

// Object obj = new Object();

while(true){

synchronized (this){//此时的this:唯一的Window1的对象 //方式二:synchronized (dog) {

if (ticket > 0) {

try {

Thread.sleep(100);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);

ticket--;

} else {

break;

}

}

}

}

}

public class WindowTest1 {

public static void main(String[] args) {

Window1 w = new Window1();

Thread t1 = new Thread(w);

Thread t2 = new Thread(w);

Thread t3 = new Thread(w);

t1.setName("窗口1");

t2.setName("窗口2");

t3.setName("窗口3");

t1.start();

t2.start();

t3.start();

}

}

class Dog{

}

线程的死锁问题:

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。出现死锁后,不会出现异常,不会出现提示,只是所以的线程都处于阻塞状态,无法继续。

解决的办法:

1.专门的算法、原则

2.尽量减少同步资源的定义

3.尽量避免嵌套同步

解决线程安全问题的方式三:LOCK(锁) (重要)

java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁、释放锁。

package com.TestLock.java1;

import java.util.concurrent.locks.ReentrantLock;

/**

* 解决线程安全问题的方式三:Lock锁 --- JDK5.0新增

*/

class Window implements Runnable{

private int ticket = 100;

//1.实例化ReentrantLock

private ReentrantLock lock = new ReentrantLock();

@Override

public void run() {

while(true){

try{

//2.调用锁定方法lock()

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 {

//3.调用解锁方法:unlock()

lock.unlock();

}

}

}

}

public class LockTest {

public static void main(String[] args) {

Window w = new Window();

Thread t1 = new Thread(w);

Thread t2 = new Thread(w);

Thread t3 = new Thread(w);

t1.setName("窗口1");

t2.setName("窗口2");

t3.setName("窗口3");

t1.start();

t2.start();

t3.start();

}

}

面试题:synchronized 与 Lock的异同?

相同:二者都可以解决线程安全问题

不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器。 Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())

优先使用顺序:Lock —>同步代码块(已经进入了方法体,分配了相应资源)—>同步方法(在方法体之外)

五、线程的通信

线程通信的例子:使用两个线程打印 1-100。线程1, 线程2 交替打印。

涉及到的三个方法:

wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。

notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。

notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。

说明:

1.wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。

2.wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。

否则,会出现IllegalMonitorStateException异常

3.wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中。

**面试题:**sleep()和wait()的异同?

1.相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。

2.不同点:1)两个方法声明的位置不同:Thread类中声明sleep() , Object类中声明wait()

2)调用的要求不同:sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中

3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。

**sleep 方法:**是属于 Thread 类中的,sleep 过程中线程不会释放锁,只会阻塞线程,让出cpu给其他线程,但是他的监控状态依然保持着,当指定的时间到了又会自动恢复运行状态,可中断,sleep 给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会。

**yield和sleep的区别:**和 sleep 一样都是 Thread 类的方法,都是暂停当前正在执行的线程对象,不会释放资源锁,和 sleep 不同的是 yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。还有一点和 sleep 不同的是 yield 方法只能使同优先级或更高优先级的线程有执行的机会。

小结:释放锁的操作

1.当前线程的同步方法、同步代码块执行结束

2.当前线程同步代码块、同步方法中遇到break、return终止了该代码块,该方法继续执行

3.当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束

4.当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁

不会释放锁的操作:

1.线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行

2.线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)。应尽量避免使用suspend()和resume()来控制线程。

class Number implements Runnable{

private int number = 1;

private Object obj = new Object();

@Override

public void run() {

while(true){

synchronized (obj) {

obj.notify();

if(number <= 100){

try {

Thread.sleep(10);

} 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();

}

}

六、JDK5.0新增线程创建方式

创建多线程的方式三:实现Callable接口

创建线程的方式三:实现Callable接口。 — JDK 5.0新增

如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?

1. call()可以有返回值的。

2. call()可以抛出异常,被外面的操作捕获,获取异常的信息

3. Callable是支持泛型的

步骤:

1.创建一个实现Callable的实现类

2.实现call方法,将此线程需要执行的操作声明在call()中

3.创建Callable接口实现类的对象

4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象

5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()

6.获取Callable中call方法的返回值

实现代码:

import java.util.concurrent.Callable;

import java.util.concurrent.ExecutionException;

import java.util.concurrent.FutureTask;

/**

*/

//1.创建一个实现Callable的实现类

class NumThread implements Callable{

//2.实现call方法,将此线程需要执行的操作声明在call()中

@Override

public Object call() throws Exception {

int sum = 0;

for (int i = 1; i <= 100; i++) {

if(i % 2 == 0){

System.out.println(i);

sum += i;

}

}

return sum;

}

}

public class ThreadNew {

public static void main(String[] args) {

//3.创建Callable接口实现类的对象

NumThread numThread = new NumThread();

//4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象

FutureTask futureTask = new FutureTask(numThread);

//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()

new Thread(futureTask).start();

try {

//6.获取Callable中call方法的返回值

//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。

Object sum = futureTask.get();

System.out.println("总和为:" + sum);

} catch (InterruptedException e) {

e.printStackTrace();

} catch (ExecutionException e) {

e.printStackTrace();

}

}

}

创建多线程的方式四:使用线程池(开发中真正使用的方式)

思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似于生活中的公共交通工具。

corePoolSize:核心池的大小

maximumPoolSize:最大线程数

keepAliveTime:线程没有任务时最多保持多长时间后会终止

步骤:

1. 提供指定线程数量的线程池

2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象。(告诉线程是要干什么)

3.关闭连接池

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);

}

}

}

}

//线程需要实现的东西

class NumberThread1 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 service1 = (ThreadPoolExecutor) service;

//设置线程池的属性

// System.out.println(service.getClass());

// service1.setCorePoolSize(15);

// service1.setKeepAliveTime();

//2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象。(告诉线程是要干什么)

service.execute(new NumberThread());//适合适用于Runnable

service.execute(new NumberThread1());//适合适用于Runnable

// service.submit(Callable callable);//适合使用于Callable

//3.关闭连接池

service.shutdown();

}

}

java多个类共享的数据_Java---多线程基础总结相关推荐

  1. java 两个数组去重复数据_Java实现数组去除重复数据的方法详解

    本文实例讲述了Java实现数组去除重复数据的方法.分享给大家供大家参考,具体如下: 前一段时间被面试问到:如果一个数组中有重复元素,用什么方法可以去重?一时间会想到用一种方法,但是后来查阅资料后发现, ...

  2. java中定义类的关键字是_java中定义类的关键字是什么?

    java中定义类的关键字是:"class".在Java中定义一个类,需要使用"class"关键字.一个自定义的类名和一对表示程序体的大括号. 类是 Java 中 ...

  3. java如何保证类不被回收_Java垃圾回收机制

    大部分转自:http://blog.csdn.net/zsuguangh/article/details/6429592 1. 垃圾回收的意义 在C++中,对象所占的内存在程序结束运行之前一直被占用, ...

  4. Java语言所有异常类均继承自_Java将运行错误分为两类:(__)和(__), 其对应的类均派生自(__)类;...

    [单选题]设 x,y 均为已定义的类名,下列声明对象x1的语句中正确的是( ) [判断题]构造函数的方法名可以由编程人员任意命名. [单选题]能够实现对原文的鉴别和不可否认性的认证技术是( ). [单 ...

  5. Java为枚举类创建成员变量_Java学习——枚举类

    Java学习--枚举类 摘要:本文主要介绍了Java的枚举类. 部分内容来自以下博客: https://www.cnblogs.com/sister/p/4700702.html https://bl ...

  6. java筛选表格,java怎么获取excel中的数据_java筛选excel数据

    你好! 请教你个问题 java web程序如何将读取的excel表格里的数据插入到数据库,并显示在JSP页面上? 主要用poi.jar 包.包含两jar就可以了:poi-3.16.jar.poi-oo ...

  7. java 多线程 关键字_java多线程基础(synchronize关键字)

    线程:进程(process)就是一块包含了某些资源的内存区域.操作系统利用进程把它的工作划分为一些功能单元. 线程:进程中所包含的一个或多个执行单元称为线程(thread).进程还拥有一个私有的虚拟地 ...

  8. java子线程的创建_Java多线程基础(一):线程的创建

    多线程基础 并行与并发 并发是指一个处理器同时处理多个任务. 并行是指多个处理器或者是多核的处理器同时处理多个不同的任务. 打个比方:在并发的状态下,餐厅里只有一个厨师,尽管他做事利索,餐厅的客人等待 ...

  9. 主线程如何等待多线程完成 返回数据_多线程基础体系知识清单

    作者:Object 来源:https://juejin.im/user/5d53e1f6f265da03af19cae0/posts 前言 本文会介绍Java中多线程与并发的基础,适合初学者食用. 线 ...

最新文章

  1. 鸿蒙自研系统,华为已注册“华为鸿蒙”商标,自研操作系统最快秋季发布
  2. java 断点续传组件_chunkupload 文件上传断点续传组件(java) - 正式发布
  3. php call()函数,PHP中__call()方法详解
  4. python compare excel_python简单操作excle的方法
  5. python读取sqlserver的数据_Python实现读取SQLServer数据并插入到MongoDB数据库的方法示例...
  6. Flutter修仙传第一章:从Form入手学会组件使用
  7. 全栈性能测试修炼宝典jmeter实战电子版_JMeter实战(一) 体系结构
  8. 【CCCC】L2-013 红色警报 (25分),,并查集计算集合个数
  9. 吴恩达机器学习视频笔记——简单知识背景
  10. 数据可视化?不如用最经典的工具画最酷炫的图
  11. MacOS上禁用自动启动Adobe Creative Cloud
  12. Java毕设项目直播购物平台计算机(附源码+系统+数据库+LW)
  13. 使用css动画实现loding效果
  14. redisson + CacheManager缓存管理
  15. 芯片优缺点_“碳基芯片”的材料,将采用碳纳米管制成,或比传统芯片提升10倍...
  16. 基于STL的演讲比赛流程管理系统
  17. 浅谈MySQL查询优化
  18. SEGGER J-FLASH V7.82 下载链接
  19. Filter的过滤器链
  20. Mysql,如果几分钟不用,再次操作时就会很慢

热门文章

  1. Mysql学习总结(66)——设置MYSQL数据库编码为UTF-8
  2. Docker学习总结(39)——简析容器、无服务器和虚拟机的安全性差异
  3. Linux学习总结(44)——Linux操作系统基础知识
  4. Java基础学习总结(30)——Java 内存溢出问题总结
  5. 一阶倒立摆的输入和输出是什么_了解一阶高通滤波器传递函数
  6. 最大化窗口快捷键_计算机快捷键大全(最全篇)
  7. Windows 下使用 MinGW 和 CMake 进行开发
  8. 兼容谷歌的光标居中写法
  9. [转]如何使用WinPE硬盘安装Windows XP
  10. 洛谷 P1356 数列的整数性 解题报告