1. 实现多线程

1.1 进程

  

1.2 线程

  

1.3 多线程的实现方式(方式一:继承Thread类)

  

代码示例:
定义类MyThread:

//1:定义一个类MyThread继承Thread类
public class MyThread extends Thread {
// 2:在MyThread类中重写run()方法
// 为什么要重写run方法,是因为在类中,并不是所有代码都需要执行多线程的,
// 所有就有了run方法,需要执行多线程的代码写在run方法中。
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
}

定义测试类:

public class MyThreadDemo {
public static void main(String[] args) {
// 3:创建MyThread类的对象
// 多线程就创建多个
MyThread my1 = new MyThread();
MyThread my2 = new MyThread(); // 4:启动线程
// my1.run(); //这样调用run方法是错误,并没有启动线程。
// my2.run(); // 正确方式
// void start ()导致此线程开始执行; Java虛拟机调用此线程的run方法
my1.start();
my2.start();
}
}

在前面了解到多线程,这个程序也是多线程,那么在运行程序的时候,应该是两个一起执行,而不是执行完一个再执行另一个。
运行结果:

1.4 设置和获取线程名称

  
代码示例:

String getName():返回此线程的名称
定义线程类:

public class MyThread extends Thread {

    @Override
public void run() {
for (int i = 0; i < 100; i++) {
//String getName():返回此线程的名称
System.out.println(getName() + ";" + i);
}
}
}

测试类:

public class MyThreadDemo {
public static void main(String[] args) { MyThread my1 = new MyThread();
MyThread my2 = new MyThread(); my1.start();
my2.start();
}
}

运行结果:
在线程类中,并没有设置线程名,而控制器显示输出了Thread-0和Thread-1。
说明在Thread类中有默认的线程名,而且第一个线程类是0,后面就会加一。

void setName(String name):将此线程的名称更改为等于参数name
线程类不变

public class MyThreadDemo {
public static void main(String[] args) { MyThread my1 = new MyThread();
MyThread my2 = new MyThread(); //void setName(String name):将此线程的名称更改为等于参数name
my1.setName("飞机");
my2.setName("坦克"); my1.start();
my2.start();
}
}

运行结果:
这次的输出结果就是我们设置的值了。

除了使用set方法设置线程名称,使用构造方法也是可以的
使用构造方法设置线程名称,必须在线程类中创建有参构造方法,然后将参数传到父类的(Thread)的有参构造方法。

线程类:

public class MyThread extends Thread {

    public MyThread() {
super();
} public MyThread(String name) {
//调用Thread类的构造方法,将参数传递过去。
super(name);
} @Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ";" + i);
}
}
}

测试类:

public class MyThreadDemo {
public static void main(String[] args) { MyThread my1 = new MyThread("飞机");
MyThread my2 = new MyThread("坦克"); my1.start();
my2.start();
}
}

运行结果:

那么如果获取main方法所在线程的名称那?
使用public static Thread currentThread():返回对当前正在执行的线程对象的引用

public class MyThreadDemo {
public static void main(String[] args) {
//public static Thread currentThread0:返回对当前正在执行的线程对象的引用
System.out.println(Thread.currentThread().getName()); }
}

运行结果:
由此可见,main方法的线程名是main。

1.5 线程调度

  

MIN_PRIORITY:最小线程优先级,1
MAX_PRIORITY:最大线程优先级:10
NORM_PRIORITY:默认线程优先级:5

1.6 线程控制

void join(); 等待这个线程死亡,调用它的线程终止,则主线程一起死亡

  

a.start();

a.join();

b.start();

执行过程:主线程开始->线程a开始->线程a结束->主线程结束->线程b开始->线程b结束,因为join,b会等a先跑完,join完了会带走主线程。

a.start();
b.start();
a.join();

执行过程:主线程开始->a、b随机谁先开始->a、b随机谁先死->若a先死,则主线程终止,b继续跑,跑完死或若b先死,则a继续跑,跑完a死,主线程死

代码示例:

static void sleep(long millis):使当前正在执行的线程停留 (暂停执行)指定的毫秒数

设置了sleep,在线程的执行过程中就会等待设置的时间过后,再执行。

创建线程类:

public class Thread1 extends Thread {
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ";" + i);
try {
//static void sleep(long millis):使当前正在执行的线程停留 (暂停执行)指定的毫秒数
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

创建测试类:

public class File1Demo {
public static void main(String[] args) { Thread1 t1 = new Thread1();
Thread1 t2 = new Thread1();
Thread1 t3 = new Thread1(); t1.setName("曹操");
t2.setName("刘备");
t3.setName("孙权"); t1.start();
t2.start();
t3.start(); }
}

运行结果:

这里就不放图了,线程输出应该是每停留1000毫秒(一秒)再输出一次。

void join():等待这个线程死亡

设置了join()方法,其他的线程都会等待这个线程死亡,才会执行。

创建线程类:

public class Thread1 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ";" + i);
}
}
}

创建测试类:

public class File1Demo {
public static void main(String[] args) { Thread1 t1 = new Thread1();
Thread1 t2 = new Thread1();
Thread1 t3 = new Thread1(); t1.setName("康熙");
t2.setName("四阿哥");
t3.setName("八阿哥"); t1.start();
try {
// void join():等待这个线程死亡
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
t3.start(); }
}

运行结果:

这次加入了join()方法后,明显是t1(康熙)执行的最快,因为加入了这个方法后,其他的线程都要等他执行完(死亡)后才能执行。

void setDaemon(boolean on)将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出

设置了守护线程后,主线程死亡一段时间,守护线程也要死亡。

创建线程类:

public class Thread1 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ";" + i);
}
}
}

创建测试类:

public class File1Demo {
public static void main(String[] args) { Thread1 t1 = new Thread1();
Thread1 t2 = new Thread1(); t1.setName("张飞");
t2.setName("关羽"); // 设置主线程(main)为刘备
Thread.currentThread().setName("刘备"); // 设置守护线程
// void setDaemon (boolean on): 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虛 拟机将退出
t1.setDaemon(true);
t2.setDaemon(true); t1.start();
t2.start(); for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ";" + i);
} }
}

运行结果:

在运行结果中,主线程刘备在运行第10次后死亡了,而守护他的线程也在执行一段后死亡了,并没有执行完毕。

主线程死亡后其他守护线程并不会立即死亡,而是一段时间后。

1.7 线程生命周期

正常的线程生命周期:

还有一种情况就是在运行的时候其他线程抢走了CPU的执行权的话,它就会回到就绪状态,然后再抢CPU的执行权,如果抢到了就继续执行。

还有一种情况就是在运行的时候执行了sleep()方法,执行后线程就会进入到阻塞状态,等到了设定的时间之后,它就会回到就绪状态,继续和其他的线程抢CPU的执行权。

1.8 多线程的实现方式(方式二:实现Runnable接口)

代码示例:

创建MyRunnable类

//1:定义一个类MyRunnable实现Runnable接口
public class MyRunnable implements Runnable { // 2:在MyRunnable类中重写run()方法
@Override
public void run() {
for (int i = 0; i < 10; i++) {
// 这里并不能使用getName方法,因为实现的是Runnable接口,和Thread并没有关系
// 使用想要使用必须先得到当前正在执行的线程,然后再调用getName方法
System.out.println(Thread.currentThread().getName() + ";" + i);
}
} }

创建测试类:

public class MyRunnableDemo {

    public static void main(String[] args) {

        // 3:创建MyRunnable类的对象
MyRunnable myRunnable = new MyRunnable(); // 4:创建Thread类的对象,把MyRunnable对象作为构造方法的参数
//Thread (Runnable target, String name),添加Runnable和它的线程名
Thread t1 = new Thread(myRunnable, "飞机");
Thread t2 = new Thread(myRunnable, "坦克"); // 5:启动线程
t1.start();
t2.start(); } }

运行结果:
和继承Thread类的效果是一样的,但是比继承Thread更加的灵活。

2. 线程同步

案例:卖票

  
代码示例:

创建SellTicket类:

//1:定义一个类SellTicket实现Runnable接口,里面定义一个成员变量: private int tickets = 100;
public class SellTicket implements Runnable { //表示有一百张票
private int tickets = 100; // 2:在SellTicket类中重写run()方法实现卖票,代码步骤如下
@Override
public void run() { // c:票没有了,也可能有人来问,所以这里用死循环让卖票的动作一直执行
while (true) {
// A:判断票数大于0,就卖票,并告知是哪个窗口卖的
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
// B:卖了票之后,总票数要减1
tickets--;
}
}
} }

创建SellTickDemo类:

//3:定义一一个测试类SellTicketDemo,里面有main方法,代码步骤如下
public class SellTickDemo {
public static void main(String[] args) {
// A:创建SellTicket类的对象
SellTicket sellTicket = new SellTicket(); // B:创建三个Thread类的对象,把sellTicket对象作为构造方法的参数,并给出对应的窗口名称
Thread t1 = new Thread(sellTicket, "窗口1");
Thread t2 = new Thread(sellTicket, "窗口2");
Thread t3 = new Thread(sellTicket, "窗口3"); // C:启动线程
t1.start();
t2.start();
t3.start();
} }

运行结果:

2.1 卖票案例的思考

代码示例:

SellTick类:

public class SellTicket implements Runnable {

    private int tickets = 100;

    @Override
public void run() { while (true) { if (tickets > 0) {
try {
// 通过sleep方法来模拟出票时间
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
}
}
}

SellTickDemo类:

public class SellTickDemo {
public static void main(String[] args) { SellTicket sellTicket = new SellTicket(); Thread t1 = new Thread(sellTicket, "窗口1");
Thread t2 = new Thread(sellTicket, "窗口2");
Thread t3 = new Thread(sellTicket, "窗口3"); t1.start();
t2.start();
t3.start(); }
}

运行结果:

出现了相同的票数,而且从第100张票直接就到了第97张票,还有出现了负数的情况。
这是为什么那?
解析:

// 出现负数的票
while(true)
{
// tickets=1
// t1, t2, t3
// 假设t1线程抢到CPU的执行权
if (tickets > 0) {
try {
// 通过sleep方法来模拟出票时间
Thread.sleep(100);
// t1线程休息100毫秒
// t2线程抢到了CPU的执行权,t2线程就开始执行,执行到这里的时候,t2线程 休息100毫秒
// t3线程抢到了CPU的执行权,t3线程就开始执行,执行到这里的时候,t3线程休息100毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
// 假设线程按照顺序醒过来
// t1抢到了CPU的执行权,在控制台输出:窗口1正在出售第1张票
// 假设t1继续拥有CPU的执行权,就会执行tickets--;操作, tickets = 0;
// t2抢到了CPU的执行权,在控制台输出:窗口1正在出售第o张票
// 假设t2继续拥有CPU的执行权,就会执行tickets--;操作,tickets = -1;
// t3抢到了CPU的执行权,在控制台输出:窗口3正在出售第-1张票
// 假设t2继续拥有CPU的执行权,就会执行tickets--;操作, tickets = -2;[、
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
}
}

2.2 卖票案例数据安全问题的解决

  

2.3 同步代码块

  
加上锁之后,线程进入synchronized内部之后就上了锁,其他的线程就进不去了,即使是内部的线程休眠了,内部只能有一个线程。
代码示例:

线程类:

public class SellTicket implements Runnable {

    private int tickets = 100;

    private Object obj = new Object();

    @Override
public void run() {
while (true) {
//上锁,锁必须是同一把
synchronized (obj) {
if (tickets > 0) {
try {
Thread.sleep(100); } catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
}
} }
}

测试类:

public class SellTickDemo {
public static void main(String[] args) { SellTicket sellTicket = new SellTicket(); Thread t1 = new Thread(sellTicket, "窗口1");
Thread t2 = new Thread(sellTicket, "窗口2");
Thread t3 = new Thread(sellTicket, "窗口3"); t1.start();
t2.start();
t3.start(); }
}

运行结果:
这次就正常输出了,没有重复的票,也没有负数的票。

2.4 同步方法

  

代码示例:

同步方法:

public class SellTicket implements Runnable {

    private int tickets = 100;
private int x = 0; @Override
public void run() {
while (true) {
if (x % 2 == 0) {
//锁对象是this
synchronized (this) {
if (tickets > 0) {
try {
Thread.sleep(100); } catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
}
} else {
sellTicket();
}
x++;
}
} //同步方法
private synchronized void sellTicket() {
if (tickets > 0) {
try {
Thread.sleep(100); } catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
}
}

同步静态方法:

public class SellTicket implements Runnable {

    private static int tickets = 100;
private int x = 0; @Override
public void run() {
while (true) {
if (x % 2 == 0) {
//锁对象是,类名.class,这里用到了反射
synchronized (SellTicket.class) {
if (tickets > 0) {
try {
Thread.sleep(100); } catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
}
} else {
sellTicket();
}
x++;
}
} // 同步静态方法
private static synchronized void sellTicket() {
//将tickets 也变成静态
if (tickets > 0) {
try {
Thread.sleep(100); } catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
}
}

2.5 线程安全的类

synchronized() 关键字的类,就是线程安全的类

  

  

在Collections(工具类)中有一个方法可以返回指定类型的线程安全

  

2.6 Lock锁

  

代码示例:

创建线程类:

public class SellTicket implements Runnable {

    private int tickets = 100;
// 创建一个Lock的实例,因为Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化、
// ReentrantLock (): 创建一个ReentrantL ock的实例
private Lock lock = new ReentrantLock(); @Override
public void run() { while (true) {
// 加入try...finally的原因是,如果在运行中发生了错误,释放锁的操作照样会执行。
try {
// 获得锁
lock.lock();
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
} finally {
// 释放锁
lock.unlock();
}
}
}
}

创建测试类:

public class SellTickDemo {
public static void main(String[] args) { SellTicket sellTicket = new SellTicket(); Thread t1 = new Thread(sellTicket, "窗口1");
Thread t2 = new Thread(sellTicket, "窗口2");
Thread t3 = new Thread(sellTicket, "窗口3"); t1.start();
t2.start();
t3.start(); }
}

运行结果:

没有问题

3. 生产者消费者

3.1 生产者消费者模式概述

生产者消费者模式详解

  

等待:生产者生产好了数据,消费者没有去消费,这个时候生产者就会提醒消费者去消费。
唤醒:消费者去消费的时候,发现没有数据,这个时候消费者就去提醒生产者生产数据。

  

3.2 生产者消费者案例

  
代码示例:

创建奶箱类:

//1:奶箱类(Box):定义一个成员变量,表示第x瓶奶,提供存储牛奶和获取牛奶的操作
public class Box { // 奶的数量
private int milk;
// 定义一个成员变量,表示奶箱的状态
private boolean state = false; // 存储牛奶,加入synchronized 防止线程安全问题
public synchronized void put(int milk) {
// 如果有奶等待消费
if (state) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} // 如果没有奶就生产奶
this.milk = milk;
System.out.println("送奶工将第" + milk + "瓶奶放入奶箱"); // 生产完毕之后,修改奶箱状态
state = true; // 唤醒其他等待的线程
notifyAll(); } // 获取牛奶,加入synchronized 防止线程安全问题
public synchronized void get() {
// 如果没有牛奶等待生产
if (!state) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} // 如果有牛奶就消费牛奶
System.out.println("用户拿到第" + milk + "瓶奶"); // 消费完毕之后,修改奶箱状态
state = false; // 唤醒其他等待的线程
notifyAll();
}
}

创建生产者类:

//2:生产者类(Producer):实现Runnable接口,重写run()方法,调用存储牛奶的操作
public class Producer implements Runnable { private Box b; public Producer(Box b) {
this.b = b;
} @Override
public void run() {
for (int i = 1; i <= 5; i++) {
b.put(i);
}
}
}

创建消费者类:

//3:消费者类(Customer):实现Runnable接口,重写run()方法,调用获取牛奶的操作
public class Customer implements Runnable { private Box b; public Customer(Box b) {
this.b = b;
} @Override
public void run() {
while (true) {
b.get();
}
}
}

创建测试类:

//4:测试类(BoxDemo):里面有main方法,main方法 中的代码步骤如下
public class BoxDemo {
public static void main(String[] args) {
// A:创建奶箱对象,这是共享数据区域
Box b = new Box(); // B:创建生产者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用存储牛奶的操作
Producer p = new Producer(b); // C:创建消费者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作
Customer c = new Customer(b); // D:创建2个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递
Thread t1 = new Thread(p);
Thread t2 = new Thread(c); // E:启动线程
t1.start();
t2.start(); }
}

运行结果:

Java基础00-多线程28的更多相关文章

  1. Java基础技术多线程与并发面试【笔记】

    Java基础技术多线程与并发 什么是线程死锁? ​死锁是指两个或两个以上的进程(线程)在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去,我们就可以称 ...

  2. Java基础之多线程框架

    一.进程与线程的区别 1.定义: 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位. 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比 ...

  3. Java基础之多线程篇(线程创建与终止、互斥、通信、本地变量)

    线程创建与终止 线程创建 Thread类与Runnable接口的关系 public interface Runnable { public abstract void run(); } public ...

  4. 关于java基础、多线程、JavaWeb基础、数据库、SSM、Springboot技术汇总

    作者 : Stanley 罗昊 本人自行总结,纯手打,有疑问请在评论区留言 [转载请注明出处和署名,谢谢!] 一.java基础 1.多态有哪些体现形式? 重写.重载 2. Overriding的是什么 ...

  5. Java基础(00)

    Java发展史 Java之父:詹姆斯.高斯林(James Gosling). SUN(Stanford University Network 斯坦福大学网络公司)产物. 1995年5月23日,java ...

  6. Java基础之多线程详细分析

    在了解多线程之前,先来了解一下进程与线程之间的关系. 进程和线程: 进程是指在系统中正在执行的一个程序,每个进程之间是独立的. 线程是进程的一个基本执行单元.一个进程要想执行任务,必须得有线程(每1个 ...

  7. java基础知识 多线程

    package org.base.practise9; import org.junit.Test; import java.awt.event.WindowAdapter; import java. ...

  8. Java基础之多线程

    1.进程和线程: 进程:正在进行的程序.每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元. 线程:进程内部的一条执行路径或者一个控制单元. 两者的区别: 一个进程至少有一个线程 ...

  9. Java基础之 多线程

    一.创建多线程程序的第一种方式: 继承(extends) Thread类 Thread类的子类: MyThread //1.创建一个Thread类的子类 public class MyThread e ...

  10. 黑马程序员——【Java基础】——多线程

    ---------- android培训.java培训.期待与您交流! ---------- 一.概述 (一)进程 正在执行中的程序,每一个进程执行都有一个执行顺序.该顺序是一个执行路径,或者叫一个控 ...

随机推荐

  1. 08.ElementUI 2.X 源码学习:源码剖析之工程化(三)

    0x.00 前言 项目工程化系列文章链接如下,推荐按照顺序阅读文章 . 1️⃣ 源码剖析之工程化(一):项目概览.package.json.npm script 2️⃣ 源码剖析之工程化(二):项目构 ...

  2. Mobileye高级驾驶辅助系统(ADAS)

    Mobileye高级驾驶辅助系统(ADAS) Mobileye is the global leader in the development of vision technology for Adv ...

  3. ContOS8 使用yum安装MariaDB

    首先全部删除MySQL/MariaDB(若是首次安装可根据需要跳过此步) 若不清楚MySQL和MariaDB的关系请移步至 Mariadb百科 1.查看系统版本(以下任一命令即可). # cat /p ...

  4. 笔记-13-多线程 Thread方法 线程安全 生产者和消费者 死锁和阻塞 练习

    题目1 编写程序,创建两个线程对象,一根线程循环输出"播放背景音乐",另一根线程循环输出"显示画面";要求: 1: 1个线程使用Runnable接口的匿名内部类 ...

  5. 如何提升springboot服务吞吐量

    生产环境偶尔会有一些慢请求导致系统性能下降,吞吐量下降,下面介绍几种优化建议. 方案 1.undertow替换tomcat 电子商务类型网站大多都是短请求,一般响应时间都在100ms,这时可以将web ...

  6. 关于MySQL索引面试题的六连炮!招架的住吗?

    1.面试真题 MySQ索引的原理和数据结构能介绍一下吗? b+树和b-树有什么区别? MySQL聚簇索引和非聚簇索引的区别是什么? 他们分别是如何存储的? 使用MySQL索引都有哪些原则? MySQL ...

  7. 【Python报错】ValueError: If using all scalar values, you must pass an index

    问题: 1.ValueError: If using all scalar values, you must pass an index.意思是:如果使用所有标量值,则必须传递索引 2.再看错误发生代 ...

  8. Task异常捕获的方式

    这节来讲一下如果捕获Task的异常. 当Task运行中出现了异常,正常情况下我们在主线程的Try是捕获不到的,而如果在Task内部写try,出现了异常我们会完全不知道.下面就来介绍几个主线程捕获Tas ...

  9. MySQL数据库复制技术应用实战(阶段二)

    MySQL数据库复制技术应用实战(阶段二)文档 作者 刘畅 时间 2020-9-27 服务器版本:CentOS Linux release 7.5.1804 主机名 ip地址 服务器配置 安装软件 密 ...

  10. POJ 1039 直线和线段相交

    题意: 题意很好理解,从左边射过来的光线,最远能经过管道到右边多少距离. 分析: 光线一定经过一个上端点和一个下端点,这一点很容易想到.然后枚举上下端点即可 #include <iostream ...