第七章——多线程
1、多线程概述
1、多线程概述
进程:
正在运行的程序,是系统进行资源分配和调用的独立单位。
每一个进程都有它自己的内存空间和系统资源。
线程:
是进程中的单个顺序控制流,是一条执行路径
一个进程如果只有一条执行路径,则称为单线程程序。
一个进程如果有多条执行路径,则称为多线程程序。
举例
扫雷游戏,百度网盘下载等
2、Java程序运行原理:
java 命令会启动 java 虚拟机,启动 JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。所以 main方法运行在主线程中。在此之前的所有程序都是单线程的。
思考:
jvm虚拟机的启动是单线程的还是多线程的?主线程 垃圾回收线程
答:虚拟机(VM)的启动通常是由单个线程进行的,即主线程(main thread)。在 Java 虚拟机中,当 Java 程序启动时,JVM 会启动一个主线程来执行 main 方法,这是程序的入口点。
以下是关于主线程和垃圾回收线程的几个要点:
主线程:
主线程负责启动 JVM 和执行 main 方法。
main 方法是程序的入口点,它通常由用户或命令行工具触发。
在 main 方法执行期间,程序的其他线程(如果有)会等待主线程的执行。
垃圾回收线程:
垃圾回收(GC)是 JVM 自动管理内存的过程,用于回收不再使用的对象占用的内存。
垃圾回收线程是 JVM 内部的一个守护线程(daemon thread),它在后台运行,不需要主线程的干预。
守护线程会等待除守护线程之外的所有线程都结束时才会结束,因此它会在主线程执行完毕后继续运行,直到 JVM 关闭。
2、多线程的实现方案1 继承Thread类:
继承Thread类:
/*
休眠线程
public static void sleep(long millis)
*/
class MyThread2 extends Thread{
@Override
public void run() {
System.out.println("我是李刚,现在开始睡觉了...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我醒了,现在开始学习!!");
}
}
public class ThreadDemo2 {
public static void main(String[] args) {
MyThread2 myThread2 = new MyThread2();
myThread2.start();
}
}
============================================================
/*
休眠线程
public static void sleep(long millis)
*/
class MyThread2 extends Thread{
@Override
public void run() {
System.out.println("我是李刚,现在开始睡觉了...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我醒了,现在开始学习!!");
}
}
public class ThreadDemo2 {
public static void main(String[] args) {
MyThread2 myThread2 = new MyThread2();
myThread2.start();
}
}
============================================================/*
加入线程:
public final void join()
*/
class MyThread3 extends Thread {
@Override
public void run() {
for (int i = 1; i <= 200; i++) {
System.out.println(getName() + "-" + i);
}
}
}
public class ThreadDemo3 {
public static void main(String[] args) throws InterruptedException {
MyThread3 t1 = new MyThread3();
MyThread3 t2 = new MyThread3();
MyThread3 t3 = new MyThread3();
t1.start();
t1.join();
t2.start();
t3.start();
}
}
/*
礼让:
public static void yield()
*/
class MyThread4 extends Thread{
@Override
public void run() {
for(int i=1;i<=200;i++){
System.out.println(getName()+"- "+i);
Thread.yield();
}
}
}
public class ThreadDemo4 {
public static void main(String[] args) {
MyThread4 t1 = new MyThread4();
MyThread4 t2 = new MyThread4();
t1.start();
t2.start();
}
}
============================================================
/*
后台线程:
public final void setDaemon(boolean on)
用户线程:优先级高于守护线程
守护线程【后台线程】:当一个程序没有了用户线程,守护线程也就没有了
*/
class MyThread5 extends Thread{
@Override
public void run() {
for(int i=1;i<=200;i++){
System.out.println(getName()+"- "+i);
}
}
}
public class ThreadDemo5 {
public static void main(String[] args) {
MyThread5 t1 = new MyThread5();
MyThread5 t2 = new MyThread5();
MyThread5 t3 = new MyThread5();
t1.setName("刘备");
t2.setName("关羽");
t3.setName("张飞");
//将t2和t3线程设置为守护线程
t2.setDaemon(true);
t3.setDaemon(true);
t1.start();
t2.start();
t3.start();
}
}
============================================================ /*
中断线程:
public final void stop()
public void interrupt()
java所有的线程要想变成运行状态,必须经过抢cpu执行权
*/
class MyThread6 extends Thread{
@Override
public void run() {
System.out.println("胡海祥准备睡觉了....睡足10秒钟");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("睡醒了,继续敲代码!!!");
}
}
public class ThreadDemo6 {
public static void main(String[] args) {
MyThread6 t1 = new MyThread6();
t1.start();
try {
Thread.sleep(5000);
// t1.stop();
t1.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3、多线程的实现方案2 实现Runnable接口
实现接口方式的好处
可以避免由于Java单继承带来的局限性。
适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想。
/*
创建线程的第二种方式:实现Runnable接口,借助Thread类创建线程对象
若将来每一个线程执行的逻辑是一样的话,推荐采用第二种实现Runnable接口方式实现多线程
若将来每一个线程执行逻辑不一样的话,推荐采用第一种继承Thread类方式实现多线程
*/
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 200; i++) {
//可以先通过获取当前执行的线程对象,调用内部getName()方法
System.out.println(Thread.currentThread().getName()+"-"+i);
}
}
}
public class RunnableDemo1 {
public static void main(String[] args) {
//创建Runnable对象
MyRunnable r1 = new MyRunnable();
//创建线程对象
// Thread t1 = new Thread(r1);
// Thread t2 = new Thread(r1);
// Thread t3 = new Thread(r1);
//创建线程对象同时起名字
Thread t1 = new Thread(r1, "李刚");
Thread t2 = new Thread(r1, "祝帅");
Thread t3 = new Thread(r1, "吴问强");
//获取线程优先权 默认线程优先级是 5
// System.out.println("t1: "+t1.getPriority());
// System.out.println("t2: "+t2.getPriority());
// System.out.println("t3: "+t3.getPriority());
t1.setPriority(1); // 1-10
t1.setPriority(10);
t1.setPriority(1);
t1.start();
t2.start();
t3.start();
}
}
4、解决线程安全问题的基本思想
首先想为什么出现问题?(也是我们判断是否有问题的标准)
是否是多线程环境
是否有共享数据
是否有多条语句操作共享数据
如何解决多线程安全问题呢?
基本思想:让程序没有安全问题的环境。
怎么实现呢?
把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可。
1、解决线程安全问题实现1
同步代码块
格式:
synchronized(对象){需要同步的代码;}
同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。
同步的前提
多个线程
多个线程使用的是同一个锁对象
同步的好处
同步的出现解决了多线程的安全问题。
同步的弊端
当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
2、解决线程安全问题实现2
同步方法
就是把同步关键字加到方法上
同步方法的锁对象是什么呢?
如果是静态方法,同步方法的锁对象又是什么呢?
那么,我们到底使用谁?
如果锁对象是this,就可以考虑使用同步方法。
否则能使用同步代码块的尽量使用同步代码块
3、某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
/*
使用继承Thread类来实现售票案例
*/
class Window extends Thread{
static int tickets = 100;
@Override
public void run() {
// int tickets = 100;
while (true){
if(tickets>0){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前 " + getName()+" 正在出售第 "+(tickets--)+" 张票");
}
}
}
}
public class SellTicketsDemo1 {
public static void main(String[] args) {
Window w1 = new Window();
Window w2 = new Window();
Window w3 = new Window();
w1.setName("窗口1");
w2.setName("窗口2");
w3.setName("窗口3");
w1.start();
w2.start();
w3.start();
}
}
============================================================
/*
使用实现Runnable接口的方式实现售票
问题1:我们加入了循环和延迟模拟现实生活售票的场景后发现
1. 出现售卖重复的票号 【计算机中cpu的计算是具备原子性的】
2. 出现非法的票号 【随机性导致的,cpu小小的时间片,足以执行很多次】
上述的问题1实际上是属于线程安全的问题。
如何判断一个程序是否存在线程安全的问题呢?
三要素,缺一不可:
1、是否存在多线程环境? 是
2、是否存在共享数据/共享变量?是tickets
3、是否存在多条语句操作着共享数据/共享变量? 是
怎么解决?
方案1:同步代码块
synchronized(对象){需要同步的代码;} 这里对象要保证多个线程对象唯一的
同步方法:锁对象是this
同步静态方法:锁对象是当前类的class文件对象 类.class
方案2:lock锁
*/
class Window2 implements Runnable{
int tickets = 200;
Object obj = new Object();
@Override
public void run() {
while (true){
synchronized (obj){
if(tickets>0){ // 1
try {
// t1
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前 " + Thread.currentThread().getName()+" 正在出售第 "+(tickets--)+" 张票");
}
}
}
}
}
public class SellTicketsDemo2 {
public static void main(String[] args) {
Window2 window2 = new Window2();
Thread t1 = new Thread(window2, "窗口1");
Thread t2 = new Thread(window2, "窗口2");
Thread t3 = new Thread(window2, "窗口3");
t1.start();
t2.start();
t3.start();
}
}
============================================================
/*
同步方法的锁对象
*/
class Window3 implements Runnable {
static int tickets = 200;
int i = 0;
@Override
public void run() {
while (true) {
if (i % 2 == 0) {
synchronized (Window3.class) {
if (tickets > 0) { // 1
try {
// t1
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前 " + Thread.currentThread().getName() + " 正在出售第 " + (tickets--) + " 张票");
}
}
} else {
sell();
}
i++;
}
}
public synchronized static void sell() {
if (tickets > 0) { // 1
try {
// t1
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前 " + Thread.currentThread().getName() + " 正在出售第 " + (tickets--) + " 张票");
}
}
// public synchronized void sell() {
// if (tickets > 0) { // 1
// try {
// // t1
// Thread.sleep(20);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
//
// System.out.println("当前 " + Thread.currentThread().getName() + " 正在出售第 " + (tickets--) + " 张票");
// }
// }
}
public class SellTicketsDemo3 {
public static void main(String[] args) {
Window3 window3 = new Window3();
Thread t1 = new Thread(window3, "窗口1");
Thread t2 = new Thread(window3, "窗口2");
Thread t3 = new Thread(window3, "窗口3");
t1.start();
t2.start();
t3.start();
}
}
============================================================
/*
使用lock锁来解决线程安全的问题
*/
class Window4 implements Runnable {
int tickets = 200;
Object obj = new Object();
ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
// 加锁
lock.lock();
if (tickets > 0) { // 1
try {
// t1
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前 " + Thread.currentThread().getName() + " 正在出售第 " + (tickets--) + " 张票");
}
//释放锁
lock.unlock();
}
}
}
public class SellTicketsDemo4 {
public static void main(String[] args) {
Window4 window4 = new Window4();
Thread t1 = new Thread(window4, "窗口1");
Thread t2 = new Thread(window4, "窗口2");
Thread t3 = new Thread(window4, "窗口3");
t1.start();
t2.start();
t3.start();
}
}
============================================================
/*
死锁:线程之间存在相互等待的现象
案例:中国人和外国人
前提:中国人吃饭必须要两支筷子,外国人吃饭必须一把刀和一把叉
现在:
中国人:一支筷子和一把刀
外国人:一支筷子和一把叉
*/
class Person extends Thread{
boolean flag;
public Person(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if(flag){
synchronized (Locks.LOCK1){
System.out.println("if lock1");
// p1
synchronized (Locks.LOCK2){
System.out.println("if lock2");
}
}
}else {
synchronized (Locks.LOCK2){
System.out.println("else lock2");
// p2
synchronized (Locks.LOCK1){
System.out.println("else lock1");
}
}
}
}
}
public class DieLock {
public static void main(String[] args) {
Person p1 = new Person(true);
Person p2 = new Person(false);
p1.start();
p2.start();
}
}
5、线程组 线程池
/*
线程组:将属于同一类的线程划分到同一组中,可以直接对线程组进行设置。
ThreadGroup
构造方法:
ThreadGroup(String name) 构造一个新的线程组。
*/
class MyThread1 extends Thread{
public MyThread1() {
}
public MyThread1(ThreadGroup group, String name) {
super(group, name);
}
@Override
public void run() {
System.out.println("这是帅哥线程");
}
}
public class ThreadGroupDemo1 {
public static void main(String[] args) {
//创建一个线程组,组名叫做帅哥组
ThreadGroup tg1 = new ThreadGroup("帅哥组");
ThreadGroup tg2 = new ThreadGroup("美女组");
//创建两个线程对象,分配到线程组中
// MyThread1 t1 = new MyThread1();
// t1.setName("李刚");
//Thread(ThreadGroup group, String name)
//分配一个新的 Thread对象。
MyThread1 t1 = new MyThread1(tg1, "李刚");
MyThread1 t2 = new MyThread1(tg1, "钱志强");
MyThread1 t3 = new MyThread1(tg2, "李世博");
MyThread1 t4 = new MyThread1(tg2, "杨珊珊");
System.out.println(t1.getName()+"属于 "+t1.getThreadGroup().getName());
System.out.println(t2.getName()+"属于 "+t2.getThreadGroup().getName());
System.out.println(t3.getName()+"属于 "+t3.getThreadGroup().getName());
System.out.println(t4.getName()+"属于 "+t4.getThreadGroup().getName());
// t1.setDaemon(true);
// t2.setDaemon(true);
tg1.setDaemon(true);
}
}
============================================================
/*
线程池:ThreadPool
Executors:
static ExecutorService newCachedThreadPool() 创建一个根据需要创建新线程的线程池,但在可用时将重新使用以前构造的线程。
static ExecutorService newFixedThreadPool(int nThreads) 创建一个线程池,该线程池重用固定数量的从共享无界队列中运行的线程。
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 创建一个线程池,可以调度命令在给定的延迟之后运行,或定期执行。
*/
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + " - " + i);
}
}
}
class MyCallable implements Callable{
@Override
public Object call() throws Exception {
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + " - " + i);
}
return null;
}
}
public class ThreadPoolDemo1 {
public static void main(String[] args) {
//创建一个固定大小的线程池
ExecutorService pool = Executors.newFixedThreadPool(2);
//Future<?> submit(Runnable task);
// pool.submit(new MyRunnable());
// pool.submit(new MyRunnable());
// pool.submit(new MyRunnable());
// <T> Future<T> submit(Callable<T> task);
// pool.submit(new MyCallable());
// pool.submit(new MyCallable());
pool.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + " - " + i);
}
return null;
}
});
// pool.submit(new Callable<Object>() {
// @Override
// public Object call() throws Exception {
// //....
// return null;
// }
// });
//关闭线程池
pool.shutdown();
}
}
6、定时器
定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务以后台线程的方式执行。在Java中,可以通过Timer和TimerTask类来实现定义调度的功能
Timer
public Timer()
public void schedule(TimerTask task, long delay)
public void schedule(TimerTask task,long delay,long period)
TimerTask
public abstract void run()
public boolean cancel()
开发中
Quartz是一个完全由java编写的开源调度框架。
/*
定时器:Timer
定时任务:TimerTask
*/
public class TimerDemo1 {
public static void main(String[] args) {
//创建一个定时器
Timer timer = new Timer();
//public void schedule(TimerTask task, long delay) 延迟多少毫秒后执行定义任务
timer.schedule(new MyTask(timer), 5000);
//public void schedule(TimerTask task,long delay,long period) 延迟delay毫秒后执行定义任务,后续每间隔period毫米执行一次
timer.schedule(new MyTask(timer), 5000,2000);
}
}
class MyTask extends TimerTask{
Timer timer;
public MyTask(Timer timer) {
this.timer = timer;
}
@Override
public void run() {
System.out.println("砰!爆炸了.....");
// timer.cancel();
}
}
7、单例设计模式
/*
单例模式:在整个java程序运行期间,内存中某一个对象有且仅只能有一个。笔试
1. 饿汉式 工作开发中
2. 懒汉式 面试的时候说,可能会涉及线程安全的问题。
*/
public class DanLiDemo1 {
public static void main(String[] args) {
// Student1 s1 = Student1.getStudent1();
// Student1 s2 = Student1.getStudent1();
// System.out.println(s1==s2);
Student2 s1 = Student2.getStudent2();
Student2 s2 = Student2.getStudent2();
System.out.println(s1==s2);
}
}
/*
饿汉式
*/
public class Student1 {
private static Student1 student1 = new Student1();
private Student1(){}
public static Student1 getStudent1(){
return student1;
}
}
/*
懒汉式
*/
// t1, t2, t3
public class Student2 {
private static Student2 student2;
private Student2(){}
public synchronized static Student2 getStudent2(){
if(student2==null){
// t1 , t2, t3
student2 = new Student2();
}
return student2;
}
}
8、设计模式
/*
设计模式:
创建型模式
简单工厂模式
工厂方法模式
单例模式
行为型模式
结构型模式
*/
public class FactoryDemo1 {
public static void main(String[] args) {
//养一只狗
Animal d1 = AnimalFactory.createAnimal("dog");
//养一只猫
Animal c1 = AnimalFactory.createAnimal("cat");
}
}
============================================================
public abstract class Animal {
public abstract void eat();
public abstract void sleep();
}
============================================================
public class AnimalFactory {
private AnimalFactory() {
}
public static Animal createAnimal(String name){
if("dog".equals(name)){
return new Dog();
}else if("cat".equals(name)){
return new Cat();
}else {
System.out.println("没有该动物");
return null;
}
}
}
============================================================public class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫吃鱼");
}
@Override
public void sleep() {
System.out.println("猫蜷着睡");
}
}
============================================================public class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃肉");
}
@Override
public void sleep() {
System.out.println("狗侧着睡");
}
}
标签:Thread,void,class,线程,new,多线程,public
From: https://www.cnblogs.com/snzjz/p/18475758