多线程

单例模式

​ 保证一个只能存在一个实例

​ 懒汉式:实例等到调用方法时候创建(调用的时候,最后一刻)

​ 饿汉式:类第一次加载之后就会创建实例

​ 静态内容(静态变量,静态块),在类第一次加载之后就会初始化

​ 实现方式:

​ 1.构造器私有化(外部才不能通过new随便创建对象,对象只能在自 己类的内部创建)

​ 2.私有的,静态的,该类的引用(存储创建了的对象)

​ 3.对外提供一个公共的静态的访问方式(方法)

public class SingleDemo1{public static void main(String[] args){
		System.out.prinln(Single.newInstance());
​		
​	}

}

//单例  饿汉式

class Single{//2.私有的静态的SIngle类型的引用public static Single single=new Single();//1.私有的构造器private Single(){}//3.公共的静态方法,返回一个当前类型的对象public static Single newInstance(){return single;}						

}
public class SingleTonDemo2{public static void main(String[] args){

​			System.out.println(SingleTon.newInstance());}

}

//懒汉式

class SingleTon{//2.私有的静态的该类的引用private static SingleTOn singleTon;//1.私有的构造器private SingleTon(){}//3.公共的静态的访问方式,返回该类实例private static SingleTon newInstance(){//创建对象//第一次调用方法时候才创建对象,第二次开始就不创建					了,因为已经存在了,直接返回if(singleTon==null){

​				singleTon=new SingleTon();}return singleTon;}	

}

多线程

1.什么是进程?什么是线程?

	进程是一个应用程序(1个进程是一个软件)。
	线程是一个进程中的执行场景/执行单元。
	一个进程可以启动多个线程。

​ 进程可以看做是现实生活当中的公司。
​ 线程可以看做是公司当中的某个员工。

	进程A和进程B的内存独立不共享。
			A魔兽游戏是一个进程
			B酷狗音乐是一个进程
			这两个进程是独立的,不共享资源。

	线程A和线程B呢?
		在java语言中:
			线程A和线程B,堆内存和方法区内存共享。
			但是栈内存独立,一个线程一个栈。

		假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,
		互不干扰,各自执行各自的,这就是多线程并发。
		
		火车站,可以看做是一个进程。
		火车站中的每一个售票窗口可以看做是一个线程。
		我在窗口1购票,你可以在窗口2购票,你不需要等我,我也不需要等		你,所以多线程并发可以提高效率。

		java中之所以有多线程机制,目的就是为了提高程序的处理效率。

思考一个问题:

​ 使用了多线程机制之后,main方法结束,是不是有可能程序也不会结束。
​ main方法结束只是主线程结束了,主栈空了,其它的栈(线程)可能还在
​ 压栈弹栈。

分析一个问题:对于单核的CPU来说,真的可以做到真正的多线程并发吗?

对于多核的CPU电脑来说,真正的多线程并发是没问题的。
4核CPU表示同一个时间点上,可以真正的有4个进程并发执行。

	什么是真正的多线程并发?
		t1线程执行t1的。
		t2线程执行t2的。
		t1不会影响t2,t2也不会影响t1。这叫做真正的多线程并发。

	单核的CPU表示只有一个大脑:
		不能够做到真正的多线程并发,但是可以做到给人一种“多线程并发”的	感觉。
		对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但	是由于
		CPU的处理速度极快,多个线程之间频繁切换执行,跟人来的感觉是:		多个事情同时在做!!!!!
			线程A:播放音乐
			线程B:运行魔兽游戏
			线程A和线程B频繁切换执行,人类会感觉音乐一直在播放,游戏一直在运行,
			给我们的感觉是同时并发的。
	
	电影院采用胶卷播放电影,一个胶卷一个胶卷播放速度达到一定程度之后,
	人类的眼睛产生了错觉,感觉是动画的。这说明人类的反应速度很慢,就像
	一根钢针扎到手上,到最终感觉到疼,这个过程是需要“很长的”时间的,在
	这个期间计算机可以进行亿万次的循环。所以计算机的执行速度很快。

2.java语言中,实现线程的两种方式

java支持多线程机制。并且java已经将多线程实现了,我们只需要继承就行了。

第一种方式:编写一个类,直接继承java.lang.Thread,重写run方法。
	// 定义线程类
	public class MyThread extends Thread{
		public void run(){
		
		}
	}
	// 创建线程对象
	MyThread t = new MyThread();
	// 启动线程。
	t.start();

第二种方式:编写一个类,实现java.lang.Runnable接口,实现run方法。
	// 定义一个可运行的类
	public class MyRunnable implements Runnable {
		public void run(){
		
		}
	}
	// 创建线程对象
	Thread t = new Thread(new MyRunnable());
	// 启动线程
	t.start();

注意:第二种方式实现接口比较常用,因为一个类实现了接口,它还可以去继承
其它的类,更灵活。
第三种方式: 实现Callable接口,重写call()方法,-->juc包下的       	可以抛出异常,可以有返回值 (知道就行)

3.关于线程对象的生命周期

​ 新生状态 : new的时候,线程处于新生状态

​ 就绪状态 : start()线程就会进入到就绪状态,线程就会进入到就绪队列进行等 待,等待cpu的调度

​ 运行状态 : cpu把资源分配给这个线程,线程才能进入运行状态

​ 阻塞状态 : sleep()可以让线程进入阻塞

​ 终止状态 : 线程结束

注意:

一个线程一旦进入阻塞状态,阻塞接触 无法直接恢复到运行状态,会直接进入就绪状态

如果一个线程一旦终止,无法恢复,重新创建也不是刚刚的线程

进入终止状态的情况

  1. stop destroy() 不推荐使用

2)通过标识判断–推荐

3)线程正常执行完毕

进入就绪状态的情况

  1. start()

  2. 阻塞解除

  3. 线程切换

  4. yield() 礼让线程 让出CPU资源 , 原线程直接 进入就绪 状态

/*

- 礼让线程 yield  --> 高风亮节

- 让出cpu资源 , 原线程直接 进入就绪 状态
  */
  public class YieldDemo04 implements Runnable{
  public static void main(String[] args) {
  	YieldDemo04 y = new YieldDemo04();
  	new Thread(y,"A").start();
  	new Thread(y,"B").start();
  }

  @Override
  public void run() {
  	System.out.println(Thread.currentThread().getName()+"开始----------------------");
  	Thread.yield();
  	System.out.println(Thread.currentThread().getName()+"结束----------------------");
  }

}

进入阻塞状态的情况

  1. sleep()

  2. wait()

  3. join()

/*

join()  插队线程|加入

插队之前先就绪
 */
public class JoinDemo06 implements Runnable{
public static void main(String[] args) {
	Thread th = new Thread(new JoinDemo06());
	

for(int i = 1;i<=10;i++) {
	System.out.println("main线程执行--------------"+i);
	if(i==5){
		th.start();  //就绪插队
		try {
			th.join(1000);   //th线程插队到当前主线程中,主线程等待th线程结束,才能继续执行
		} catch (InterruptedException e) {
			e.printStackTrace();
		} //插队
	}
}

}

@Override
public void run() {
	System.out.println("--------------------A线程开始执行--------------------");
	try {
		Thread.sleep(2000);
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
	System.out.println("--------------------A线程结束执行--------------------");
}



}

sleep(ms) : 睡眠

在sleep休眠时间过程当中,当前会让出CPU的资源, 当休眠时间解除,恢复到就绪,可供CPU选择

抱着资源睡觉,—> 对象资源(对象的锁),不会释放锁

  1. 网络延迟

2)放大问题的可能性

示例:

/*

根据标识做判断,控制线程的结束

龟兔赛跑

兔子每跑10步休息 , 乌龟不休息,  有人赢了就结束, 跑100步就赢了
 */
public class Race02 implements Runnable{
//标识  记录赢了人的姓名
private String winner;

@Override
public void run() {
	//i控制步数
	for(int i =1;i<=100;i++) {
		//  兔子    10步
		System.out.println(Thread.currentThread().getName()+"正在跑第"+i+"步");
		if("兔子".equals(Thread.currentThread().getName()) && i%10==0) {
			try {
				Thread.sleep(2);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		//判断是否要结束方法
		if(!checkOver(i)) {
			break;
		}
	}
}

//判断是否结束线程  返回值false结束线程  true继续跑步
public boolean checkOver(int steps) {
	//是否已经有人赢了, 结束
	if(winner != null) {
		return false;
	}
	//没有人赢了,判断我自己赢没赢
	if(steps == 100) {
		winner = Thread.currentThread().getName();
		return false;
	}
	return true;
}

public static void main(String[] args) {
	Race02 race = new Race02();
	new Thread(race,"兔子").start();
	new Thread(race,"乌龟").start();
}
}

4.线程的调度

4.1常见的线程调度模型有哪些?

抢占式调度模型:
那个线程的优先级比较高,抢到的CPU时间片的概率就高一些/多一些。
java采用的就是抢占式调度模型。

均分式调度模型:
平均分配CPU时间片。每个线程占有的CPU时间片时间长度一样。
平均分配,一切平等。
有一些编程语言,线程调度模型采用的是这种方式。

4.2java中提供了哪些方法是和线程调度有关系的呢?

实例方法:
			void setPriority(int newPriority) 设置线程的优先级
			int getPriority() 获取线程优先级
			最低优先级1
			默认优先级是5
			最高优先级10
			优先级比较高的获取CPU时间片可能会多一些。(但也不完全是,大概率是多的。)
		

静态方法:
​		static void yield()  让位方法
​		暂停当前正在执行的线程对象,并执行其他线程
​		yield()方法不是阻塞方法。让当前线程让位,让给其它线程使用。
​		yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”。
​		注意:在回到就绪之后,有可能还会再次抢到。
​	
实例方法:
​		void join()  
​		合并线程
​		class MyThread1 extends Thread {public void doSome(){
​				MyThread2 t = new MyThread2();
​				t.join(); // 当前线程进入阻塞,t线程执行,直到t线				程结束。当前线程才可以继续。}}class MyThread2 extends Thread{}

5.关于多线程并发环境下,数据的安全问题。

​ 以后在开发中,我们的项目都是运行在服务器当中,而服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现完了。这些代码我们都不需要编写。

​ 最重要的是:你要知道,你编写的程序需要放到一个多线程的环境下运行,你更需要关注的是这些数据在多线程并发的环境下是否是安全的。

​ 什么时候数据在多线程并发的环境下会存在安全问题呢?

三个条件:

​ 1.多线程并发

​ 2. 有共享数据

​ 3.共享数据有修改的行为

​ 满足以上3个条件之后,就会存在线程安全问题。

​ 当多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就存在线程安全问题,怎么解决这个问题?

​ 线程排队执行。(不能并发)。
​ 用排队执行解决线程安全问题。
​ 这种机制被称为:线程同步机制。

​ 专业术语叫做:线程同步,实际上就是线程不能并发了,线程必须排队执行——“线程同步机制”

​ 线程同步就是线程排队了,线程排队了就会牺牲一部分效率,没办法,数据安全第一位,只有数据安全了,我们才可以谈效率。数据不安全,没有效率的事儿。

线程同步涉及到两个专业术语:

异步编程模型:
		线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,
		谁也不需要等谁,这种编程模型叫做:异步编程模型。
		其实就是:多线程并发(效率较高。)

		异步就是并发。

同步编程模型:
		线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行
		结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,
		两个线程之间发生了等待关系,这就是同步编程模型。
		效率较低。线程排队执行。

		同步就是排队。

Java中有三大变量?【重要的内容。】

实例变量:在堆中。

静态变量:在方法区。

局部变量:在栈中。

以上三大变量中:
	局部变量永远都不会存在线程安全问题。
	因为局部变量不共享。(一个线程一个栈。)
	局部变量在栈中。所以局部变量永远都不会共享。

	实例变量在堆中,堆只有1个。
	静态变量在方法区中,方法区只有1个。
	堆和方法区都是多线程共享的,所以可能存在线程安全问题。

	局部变量+常量:不会有线程安全问题。
	成员变量:可能会有线程安全问题。

如果使用局部变量的话:
建议使用:StringBuilder。
因为局部变量不存在线程安全问题。选择StringBuilder。
StringBuffer效率比较低。

​ ArrayList是非线程安全的。
​ Vector是线程安全的。
​ HashMap HashSet是非线程安全的。
​ Hashtable是线程安全的。

如何控制线程安全问题:

synchronized 关键字 控制线程安全–> 控制多个线程排队执行

用法:

  • 同步方法 : 简单 效率较低

    ​ 成员方法

    ​ 静态方法

  • 同步块

    ​ synchronized(this|类.class| 资源(成员属性)){ --> this 类 资源(成员属性)要被你同步控制的代码区域–>有可能 会出现问题的代码区域}

  • double check 双重检查提高效率

  • 注意:

  • 同步一定要同步不变的东西,会变的锁不住,对象地址肯定不会变

  • 同步的代码范围过大,效率低,代码范围小,锁不住

    静态方法时属于 类的,同步静态方法,相当于同步类.class -->(类的class对象每一个类只有一个,不用你创建,在类加载到内存中时就已经存在)

public class SynDemo01 {
	public static void main(String[] args) {
		new Thread(()->{
			System.out.println(SingleTon.newInstance());
		}).start();
		new Thread(()->{
			System.out.println(SingleTon.newInstance());
		}).start();
		new Thread(()->{
			System.out.println(SingleTon.newInstance());
		}).start();
	}
}

//懒汉式
class SingleTon{
	//2私有的静态的该类的引用 
	private static SingleTon singleTon;
	

//1.私有的构造器
private SingleTon(){}

//A B C
//3.公共的静态的访问方式,返回该类实例
public static SingleTon newInstance() {
	System.out.println("-----------------");
	
	if(singleTon == null) {
		System.out.println("-----满足打印的话-----------");
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		//双重检查   double check  -->提高效率
		synchronized (SingleTon.class) { //同步类
			if(singleTon == null) {
				singleTon = new SingleTon();
			}
		}
	}
	return singleTon;
}
public static SingleTon newInstance() {
		System.out.println("-----------------");
		
		synchronized (SingleTon.class) { //同步类
			if(singleTon == null) {
				System.out.println("-----满足打印的话-----------");
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
					singleTon = new SingleTon();
			}
		}
					return singleTon;
	}
//同步方法
	public static synchronized SingleTon newInstance() {
		if(singleTon == null) {
			try {
					Thread.sleep(1000);
			} catch (InterruptedException e) {
					e.printStackTrace();
			}
				singleTon = new SingleTon();
		}
			return singleTon;
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值