Java 笔记 17:JavaSE:多线程基础,单例设计模式(重要)
程序员文章站
2022-04-15 19:12:40
Java 笔记 17JavaSE:多线程基础线程的生命周期线程安全问题死锁生产者,消费者问题单例设计模式(重要)JavaSE:多线程基础多线程相关知识的课程安排:1、JavaSE:多线程基础2、后面:多线程高级 juc多线程相关的一些概念:(了解)程序:当你要完成某个/些任务,功能时,选择一种编程语言而编写的一组指令的集合。软件:软件 = 程序 + 程序运行所需要的一些资源文件。一个软件中可能会有很多个程序构成。进程:程序的一次运行。每个进程之间是独立。操作系统在分配资源(例...
Java 笔记 17
JavaSE:多线程基础
多线程相关知识的课程安排:
- 1、JavaSE:多线程基础
- 2、后面:多线程高级 juc
- 多线程相关的一些概念:(了解)
- 程序:
- 当你要完成某个/些任务,功能时,选择一种编程语言而编写的一组指令的集合。
- 软件:
- 软件 = 程序 + 程序运行所需要的一些资源文件。
- 一个软件中可能会有很多个程序构成。
- 进程:
- 程序的一次运行。
- 每个进程之间是独立。操作系统在分配资源(例如:内存)时,是以进程为单位。
- 两个进程之间进行切换,通信(交换数据)等操作时,成本比较高。
- 线程:
- 进程中的其中一条执行路径。
- 同一个进程中的多个线程之间是可以共享部分内存(方法区、堆),每个线程的有些内存又是独立(虚拟机栈、本地方法栈、程序计数器)。
- 因为线程之间可能使用共享内存,那么在数据交换成本上就比较低。而且线程之间的切换,对于CPU和操作系统来说,成本比较低。
- 所以我们通常用多线程来代替多进程的方式,实现多任务开发。
- 线程是CPU调度的最小单位。
- 并行:
- 多个线程同时运行。
- 并行,要求同时进行。针对CPU多核,甚至多个CPU,同时运行多个线程任务。
- 并发:
- 多个进程同时运行
- 高并发,多个任务处理功能,但是不要求同时进行。
- CPU:一个CPU同一个时间只能够运行一个线程的任务。
- 如何实现多个线程同时运行的呢?
- 是因为CPU是非常快,这个速度远远高于内存、硬盘、人的大脑反应的速度。
- 那么CPU会在多个线程之间,快速的切换,人是感觉不到。
1、Java中如何去实现多线程?
- (1)Java的程序入口是main,其实也是main线程,主线程。
- 线程是进程的其中一条执行路径,即一个进程至少有一个线程。那么main线程就是Java程序进程的第一个线程了。
- (2)如何开启main线程以外的其他线程呢?
- 这里讲解JavaSE阶段2种,后面会发现还有其他方式。
- 方式有两种:①继承Thread类②实现Runnable接口
- 2、继承Thread类
- 步骤:
- (1)编写线程类去继承java.lang.Thread类
- (2)必须重写父类的public void run(){}
- 在run()中需要编写,你这个线程需要完成的任务。
- (3)创建线程类对象
- (4)启动线程:start()
- 3、实现Runnable 接口
- 步骤:
- (1)编写线程类去实现java.lang.Runnable接口
- (2)必须实现接口的抽象方法:public void run()
- 在run()中需要编写,你这个线程需要完成的任务。
- (3)创建线程类对象
- (4)启动线程:start()
- 这个start()方法只有Thread类中才有,说明我们要借用Thread类的对象。
线程的生命周期
线程的生命周期:
- (1)新建/出生
-
new好了一个线程对象,此时它和普通的Java对象并没有区别。
-
好比一个美女出生并慢慢长大。
- (2)就绪
-
就绪状态的线程是具备被CPU调用的能力和状态,也只有这个状态的线程才能被CPU调用。
-
即线程调用了start()
-
好比这个美女被送进了宫。她此时可以被皇帝宠幸。
- (3)运行
-
运行状态就是当前线程正在被CPU调度执行。
-
好比这个美女正在被宠幸。
-
运行->就绪 ①时间到②yield()
- (4)阻塞
-
从运行状态到阻塞状态有几种情况:
-
①sleep()
-
②wait()
-
③join()
-
④没锁
-
⑤suspend()已过时
-
从阻塞回到就绪状态
-
①sleep()时间到,sleep()被打断interrupt()
-
②notify()
-
③加塞的线程结束
-
④占用锁的线程释放锁
-
⑤resume()已过时
- (5)死亡
-
从运行到死亡:①run()正常结束②run()遇到异常但是没处理③其他线程把你stop()(已过时)
java.lang.Thread类的API:
- (1)public void run():子类必须重写,它的方法体也称为线程体,即线程的任务代码
- (2)public void start():线程启动必须用它
- (3)public static void sleep(毫秒):休眠
- (4)public String getName():线程的名称
-
主线程的名称:main
-
其他线程:默认是Thread-编号
- (5)public static Thread currentThread()
- (6)线程优先级
- getPriority()
- setPriority()
-
优先级的范围:MIN_PRIORITY - MAX_PRIORITY ,[1,10]
-
普通优先级:NORM_PRIORITY
-
一共10个等级。
- 优先级高:被CPU调度的概率增加,不表示低的没有机会。
- 所以:不能依赖于优先级来解决先后的任务问题。
- (7)public void interrupt()
- (8)public void join():加塞
- (9)public static void yield() :暂停当前线程,让出本次的CPU资源,加入下一次CPU的抢夺中
/*
* 2、案例:编写龟兔赛跑多线程程序,设赛跑长度为30米
兔子的速度是10米每秒,兔子每跑完10米休眠的时间10秒
乌龟的速度是1米每秒,乌龟每跑完10米的休眠时间是1秒
要求,
(1)每跑1米,显示一下结果:xxx跑了几米,
休息时,显示一下:xxx在休息...
(2)只要有跑完的,比赛就结束,最先跑完的就赢了,其他运动员就停下来别跑了
相当于,只比冠军
*/
public class TestExer08 {
public static void main(String[] args) {
//1、启动线程
Sportman t = new Sportman("兔子",30,100,10000);
Sportman w = new Sportman("乌龟",30,1000,1000);
t.start();
w.start();
//2、判断是否有运动员跑完了
while(true){
//如果有人跑完了,停下所有线程
if(t.isFinish() || w.isFinish()){
t.interrupt();
w.interrupt();
/* t.stop();
w.stop();*/
t.setStop(true);
w.setStop(true);
break;
}
}
//3、宣布结果
//看谁先跑完,谁就赢了
if(t.isFinish() && w.isFinish()){
System.out.println(t.getName() + "," + w.getName() + "平局");
}else if(t.isFinish()){
System.out.println(t.getName() + "赢了");
}else{
System.out.println(w.getName() + "赢了");
}
}
}
class Sportman extends Thread{
private int distance;//距离
private long runMillsPerMeter;//每米的时间,毫秒
private long restMillsPerTenMeter;//每10米休息的时间,毫秒
private long totalTime;
private volatile boolean finish;//默认值是false,如果跑完了,修改为true
private boolean stop;//默认值是false
public Sportman(String name, int distance ,long millsPerMeter, long restPerTenMeter) {
super(name);
this.distance = distance;
this.runMillsPerMeter = millsPerMeter;
this.restMillsPerTenMeter = restPerTenMeter;
}
public void run(){
long start = System.currentTimeMillis();
int i;
for (i = 1; i <= distance && !stop; i++) {
try {
Thread.sleep(runMillsPerMeter);//模拟跑1米的时间
} catch (InterruptedException e) {
e.printStackTrace();
}
//用线程名称代替运动员的名称
System.out.println(getName() + "跑了" + i + "米");
if(i<distance && i%10==0){
System.out.println(getName() + "在休息....");
try {
Thread.sleep(restMillsPerTenMeter);//休息n秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
long end = System.currentTimeMillis();
totalTime = end - start;
if(i >= distance){//不是中途结束,而是全程跑完的
finish = true;
}
}
public long getTotalTime() {
return totalTime;
}
public boolean isFinish() {
return finish;
}
public void setStop(boolean stop) {
this.stop = stop;
}
}
线程安全问题
举例:卖票
- 假设,有10张票,分三个窗口同时卖
- 1、线程安全问题:
- 当多个线程使用“共享数据”时,就会有线程安全问题。
- 当一个线程修改了“共享数据”,是会影响其他线程。
- 2、如何解决?
- 加锁
- 形式一:同步代码块
- 形式二:同步方法
- 3、同步代码块
- 语法格式:
- synchronized(锁对象){
-
需要加锁的代码
- }
- 锁对象,又称为监视器对象,同一时刻,某一段代码,只允许一个线程运行,这个锁就记录谁现在在运行,其他线程进不来。
- 锁对象的选择:
- (1)可以是任意类型的对象
- (2)必须是这几天线程要使用同一个锁对象
- 锁的代码的范围的选择:
- (1)太大了:不行
- (2)太小了:不行
- 锁一次任务
public class Test09 {
public static void main(String[] args) {
Ticket t1 = new Ticket("窗口一");
Ticket t2 = new Ticket("窗口二");
Ticket t3 = new Ticket("窗口三");
t1.start();
t2.start();
t3.start();
}
}
class Ticket extends Thread{
private static int total = 1000;
private static Object lock = new Object();//锁的选择之一,单独造一个锁对象
public Ticket(String name) {
super(name);
}
public void run(){
// synchronized (this) {//这里使用this不行,因为这个this,对于三个线程来说不是同一个
while(true){
synchronized (lock) {
if(total > 0){
System.out.println(getName() + "卖出一张票");
total--;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("剩余:" + total);
}else{
break;
}
}
}
}
}
这一种更好,因为没有用到静态数据,静态数据生命周期太长,会影响之后对于这一类的使用
/*
* 步骤:
* (1)编写线程类,实现Runnable
* (2)重写run
* (3)创建线程对象
* (4)启动线程
*/
public class Test10 {
public static void main(String[] args) {
Ticket t = new Ticket();
Thread t1 = new Thread(t,"窗口一");
Thread t2 = new Thread(t,"窗口二");
Thread t3 = new Thread(t,"窗口三");
t1.start();
t2.start();
t3.start();
}
}
class Ticket implements Runnable{
private int total = 10;
@Override
public void run() {
while(true){
synchronized (this) {//选择this当锁,可以,因为只有一个Ticket的对象
if(total>0){
System.out.println(Thread.currentThread().getName() +"卖出一张票");
total--;
System.out.println("剩余:" + total);
}else{
break;
}
}
}
}
}
/*
* 同步方法的语法格式:
* 【修饰符】 synchronized 返回值类型 方法名(【形参列表】)throws 异常列表{
* }
*
* synchronized 【修饰符】 返回值类型 方法名(【形参列表】)throws 异常列表{
* }
*
* 同步方法的锁对象,程序员无法选择:
* (1)非静态方法:this
* (2)静态方法:当前类的Class对象
*/
public class Test11 {
public static void main(String[] args) {
Ticket t1 = new Ticket("窗口一");
Ticket t2 = new Ticket("窗口二");
Ticket t3 = new Ticket("窗口三");
t1.start();
t2.start();
t3.start();
}
}
class Ticket extends Thread{
private static int total = 10;
public Ticket(String name) {
super(name);
}
public void run(){
while(total>0){//程序停止的条件
saleOneTicket();
}
}
public synchronized static void saleOneTicket(){
if(total > 0){//线程安全问题的条件
System.out.println(Thread.currentThread().getName() + "卖出一张票");
total--;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("剩余:" + total);
}
}
//同步方法,锁的是方法的一次调用过程
//非静态方法的锁对象是this,这里使用this,不是合格的锁对象
/*public synchronized void saleOneTicket(){
if(total > 0){//线程安全问题的条件
System.out.println(getName() + "卖出一张票");
total--;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("剩余:" + total);
}
}*/
}
死锁
- 两个线程,互相持有,占有对方想要的锁,不放手。
生产者,消费者问题
线程通信是用来解决生产者与消费者问题。
*
- 生产者与消费者问题:
- 有两个或者多个线程。
- 其中一个/一部分线程,生产“数据”,称为生产者线程;
- 另一个/一部分线程,消耗“数据”,称为消费者线程。
- 这些数据放在一个“共享”区域。
- 那么就会出现:
- 当“共享”区域中的数据空了,消费者线程必须"停/等待",等待到产者线程生产了新数据后,继续进行。
- 当“共享”区域中的数据满了,生产者线程必须"停下/等待",等到消费者线程消耗了数据后,继续进行。
- 生产者与消费者问题:
- (1)共享数据: 就会有线程安全问题,就需要同步
- (2)共享区域大小固定,有限的:就需要用到“协作”,线程通信。
- Object类中有:
- (1)wait():必须由锁对象(线程的监视器对象)来调用。
- (2)notify():必须由锁对象(线程的监视器对象)来调用。
- notify()作用就是唤醒一个正在等待的线程。唤醒的是同一个锁对象监视的等待线程。
public class Test14 {
public static void main(String[] args) {
Workbench tai = new Workbench();
Cook c = new Cook("崔志恒", tai);
Waiter w = new Waiter("翠花", tai);
c.start();
w.start();
}
}
class Workbench{
//假设工作台上最多能够放10盘
private static final int MAX = 10;
private int count;
//同步方法,非静态方法来说,锁对象就是this
public synchronized void put(){//往工作台上放一盘菜
if(count >= MAX){
try {
//生产者停下来,等待
wait();//默认是this.wait()
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count++;
System.out.println(Thread.currentThread().getName() + "放了一盘菜,剩余:" + count);
this.notify();
}
public synchronized void take(){//从工作台上取走一盘菜
if(count<=0){
try {
//工作台没有菜,消费者应该停下来
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count--;
System.out.println(Thread.currentThread().getName() + "取走一盘菜,剩余:" + count);
this.notify();
}
}
class Cook extends Thread{
private Workbench tai;
public Cook(String name, Workbench tai) {
super(name);
this.tai = tai;
}
public void run(){
while(true){
tai.put();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Waiter extends Thread{
private Workbench tai;
public Waiter(String name, Workbench tai) {
super(name);
this.tai = tai;
}
public void run(){
while(true){
tai.take();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
改良
/*
* 线程通信是用来解决生产者与消费者问题。
*
* 生产者与消费者问题:
* 有两个或者多个线程。
* 其中一个/一部分线程,生产“数据”,称为生产者线程;
* 另一个/一部分线程,消耗“数据”,称为消费者线程。
* 这些数据放在一个“共享”区域。
* 那么就会出现:
* 当“共享”区域中的数据空了,消费者线程必须"停/等待",等待到产者线程生产了新数据后,继续进行。
* 当“共享”区域中的数据满了,生产者线程必须"停下/等待",等到消费者线程消耗了数据后,继续进行。
*
* 生产者与消费者问题:
* (1)共享数据: 就会有线程安全问题,就需要同步
* (2)共享区域大小固定,有限的:就需要用到“协作”,线程通信。
*
* Object类中有:
* (1)wait():必须由锁对象(线程的监视器对象)来调用。
* (2)notify():必须由锁对象(线程的监视器对象)来调用。
* notify()作用就是唤醒一个正在等待的线程。唤醒的是同一个锁对象监视的等待线程。
* (3)notifyAll():唤醒所有和我是同一个监视器对象的正在等待的线程
*/
public class Test16 {
public static void main(String[] args) {
Workbench tai = new Workbench();
Cook c1 = new Cook("崔志恒", tai);
Cook c2 = new Cook("甄玉禄", tai);
Waiter w1 = new Waiter("翠花", tai);
Waiter w2 = new Waiter("如花", tai);
// Waiter w3 = new Waiter("秋香", tai);
c1.start();
c2.start();
w1.start();
w2.start();
// w3.start();
}
}
class Workbench{
//假设工作台上最多能够放10盘
private static final int MAX = 1;
private int count;
//同步方法,非静态方法来说,锁对象就是this
public synchronized void put(){//往工作台上放一盘菜
while(count >= MAX){
try {
//生产者停下来,等待
wait();//默认是this.wait()
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count++;
System.out.println(Thread.currentThread().getName() + "放了一盘菜,剩余:" + count);
this.notify();
}
public synchronized void take(){//从工作台上取走一盘菜
while(count<=0){
try {
//工作台没有菜,消费者应该停下来
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count--;
System.out.println(Thread.currentThread().getName() + "取走一盘菜,剩余:" + count);
// this.notify();
this.notifyAll();
}
}
class Cook extends Thread{
private Workbench tai;
public Cook(String name, Workbench tai) {
super(name);
this.tai = tai;
}
public void run(){
while(true){
tai.put();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Waiter extends Thread{
private Workbench tai;
public Waiter(String name, Workbench tai) {
super(name);
this.tai = tai;
}
public void run(){
while(true){
tai.take();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
单例设计模式(重要)
单例设计模式:
*
- 单例:某个类只能有唯一的一个实例对象。
- 如何实现单例?
- 1、饿/恶汉式
- 不管我们使用者是否需要这个对象,它都上来先给你创建好这个唯一的对象。
- (1)枚举类型
- (2)形式二
- ①构造器私有化
- ②用一个全局的静态的常量,来保存这个唯一的实例对象
- (3)形式三
- ①构造器私有化
- ②用一个私有的静态的常量,来保存这个唯一的实例对象
- ③提供一个静态方法,来返回这个常量对象
- 2、懒汉式
- 延迟创建对象。当使用者来或者这个对象,要用到对象时,我再创建。
- (1)形式一:
- 见下面,考虑线程安全问题和性能问题
- (2)形式二:内部类形式
public class Test17 {
@Test
public void test1(){
SingleEnum s1 = SingleEnum.INSTANCE;
SingleEnum s2 = SingleEnum.INSTANCE;
System.out.println(s1 == s2);
}
@Test
public void test2(){
// SingleEnum.test();//此时我并没有需要用到这个对象,但是它也创建出来了
}
@Test
public void test3(){
SingleClass s1 = SingleClass.INSTANCE;
SingleClass s2 = SingleClass.INSTANCE;
System.out.println(s1==s2);
}
@Test
public void test4(){
Single s1 = Single.getInstance();
Single s2 = Single.getInstance();
System.out.println(s1 == s2);
}
@Test
public void test5(){
LazyClass s1 = LazyClass.getInstance();
LazyClass s2 = LazyClass.getInstance();
System.out.println(s2 == s1);
}
LazyClass s1;
LazyClass s2;
@Test
public void test6(){
//匿名的内部类,继承Thread类
Thread t1 = new Thread(){
public void run(){
s1 = LazyClass.getInstance();
}
};
Thread t2 = new Thread(){
public void run(){
s2 = LazyClass.getInstance();
}
};
t1.start();
t2.start();
try {
//这里用join的目的是,为了两个子线程都执行完,再执行主线程的System.out.println(s1);
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2);
}
}
enum SingleEnum{
INSTANCE;
// public static void test(){
// //..
// }
}
class SingleClass{
public static final SingleClass INSTANCE = new SingleClass();
private SingleClass(){
}
}
class Single{
private static final Single INSTANCE = new Single();
private Single(){
}
public static Single getInstance(){
return INSTANCE;
}
}
class LazyClass{
private static LazyClass instance;
private LazyClass(){
}
public static LazyClass getInstance(){
if(instance == null){//提高效率
synchronized(LazyClass.class){//当前类的Class对象
if(instance == null){//安全判断
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new LazyClass();
}
}
}
return instance;
}
//安全没问题,但是认为不是最优的
/* public synchronized static LazyClass getInstance(){
if(instance == null){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new LazyClass();
}
return instance;
}*/
//有安全问题
/* public static LazyClass getInstance(){
// return new LazyClass();//错误的
if(instance == null){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new LazyClass();
}
return instance;
}*/
}
class Lazy{
private Lazy(){
}
private static class Inner{
public static final Lazy INSTANCE = new Lazy();//在内部类中,创建外部类的唯一对象
}
public static Lazy getInstance(){
return Inner.INSTANCE;
}
}
本文地址:https://blog.csdn.net/qq_40473204/article/details/107575951