多线程1
多线程概述
进程 : 是一个正在执行中的程序
每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫控制单元
- 线程 : 就是进程中的一个独立的控制单元,线程在控制进程的执行
一个进程中至少有一个线程 - java虚拟机启动的时候会有一个进程java.exe
该进程至少有一个线程负责java程序的执行
而且这个线程运行的代码存在于main方法中
该线程称为主线程. - 扩展 : 其实jvm运行不止有一个线程,还有负责回收机制的线程
如何在自定义代码中一个自定义线程?(Thread)
通过对API的查找, java已经提供了对线程这类事物的描述-----Thread类
创建线程第一种方式 : 继承Thread 类
步骤:
- 定义继承Thread 类
- 覆写Thread类中的 run() 方法, 目的:讲自定义代码存储在run中,让线程运行
- 调用线程中的start方法:该方法作用: 启动线程.调用run方法
为什么要覆盖run()方法,
Thread 类用于描述线程,该类就定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法
也就是说,Thread类中的run 方法, 是用于存储线程要运行的代码
getName();获取线程名称
Thread.currentThread() 获取当前线程对象,相当于this.
class ThreadDemo extends Thread{
public void run(){
for (int i=0;i<60;i++){ //自定义线程执行这段代码
System.out.println(Thread.currentThread().getName()+" run"+i);
}}}
public class DEMO {
public static void main(String[] args) { //java虚拟机开启一个线程执行主函数内的代码
ThreadDemo t=new ThreadDemo(); //创建好一个线程
// t.run(); //仅仅是对象调用方法,线程是创建了,但是没有运行
t.start(); //启动创建好的线程,并调用run方法(用新的执行路径调用run)
for (int i=0;i<60;i++){ //主线程则执行这段代码
System.out.println("Main run"+i);
}
}
}
发现运算结果每次都不同
因为多个线程都在获取cpu执行权,cpu执行到谁,谁就运行
在某个时刻,只能有一个程序在运行(多核除外)
cpu其实是在做着快速的切快,达到看上去是同时运行的结果(一遍打游戏一遍听音乐)
这就是多线程的一个特性—随机性,CPU选择谁,谁就运行
线程运行状态(单核状态)
创建线程第二种方式 : 实现Runnable接口
步骤:
- 定义类,实现Runnable 接口
- 覆盖Runnable 接口中的run()方法 : 将线程要运行的代码存放到run方法中
- 通过Thread 类建立线程对象
- 将Runnable 接口中的子类对象作为实际参数传入Thread类的构造函数
原因:自定义的 run() 方法所属的对象是Runnable 接口的子类对象
所以要让线程去执行指定对象的 run()方法,就必须传入 run()方法的所属对象 - 调用Thread 类的 start() 方法开启线程并调用Runnable 接口子类的run() 方法
实现方式(Runnable)和继承方式(Thread)区别
- 实现方式(Runnable) 好处:避免的单继承的局限性
例如,Cat 类 需要继承 Animal 类,若 Cat 类需要被多线程执行,此时就不能继承Tread来定义线程(java不支持多继承)…所以在定义线程时,建议使用实现方式(Runnable) - 继承方式(Thread) : 线程需要运行的代码存放到Thread 子类run方法中
实现方式(Runnable) : 线程代码存放到 接口子类的run() 方法中
代码示例-----
/*
售票情景:
100张票
4个窗口同时卖票
使用实现方式(Runnable)
*/
class TicketSell implements Runnable{
private int tick=100; //定义票数
public void run(){ //将线程运行的代码存入
while (tick>0){
System.out.println(Thread.currentThread().getName()+tick--);
}
}
}
public class DEMO {
public static void main(String[] args) {
TicketSell ts=new TicketSell();
Thread t1=new Thread(ts,"窗口1-"); //创建了一个线程
Thread t2=new Thread(ts,"窗口2-"); //创建了一个线程
Thread t3=new Thread(ts,"窗口3-"); //创建了一个线程
Thread t4=new Thread(ts,"窗口4-"); //创建了一个线程
t1.start(); //线程开启 ,调用运行指定对象ts 的run() 方法
t2.start();
t3.start();
t4.start();
//private static int tick = 100; 将票数设置为静态static ,共享,,使用继承方式也是可以的,但是数据生命周期长
}
}
synchronized : 处理多线程安全问题
多线程的运行出现了安全问题
- 问题原因:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没执行完,这时CPU切换到了另外一个线程参与进来执行,导致共享数据错误 - 解决方法:
对多条操作共享数据的语句,只能让一个线程执行完,在执行过程中,其他线程不能参与执行该语句 - Java 对于多线程的安全问题提供了解决方式
synchronized(同步代码块)
格式 : synchronized(对象){ 需要被同步的语句 }
其中,对象如同锁,拥有锁的线程可以在同步代码中执行
没有持有锁的线程即使获取CPU执行权,也不可以执行同步代码 - 同步的前提
1,必须要有两个或者两个以上的线程
2,必须是多个线程使用同一个锁(调用同一run())
. - 好处:解决了多线程安全问题
- 弊端:多个线程需要判断锁,较为耗费资源
.
上部分代码示例,会出现多线程安全问题
在代码中加入让线程进入休眠的步骤,打印后的结果可以明显看出有重复的数据出现
线程安全写法—
public void run() {
while (tick > 0) { //当tick=1的时候,此位置有3个线程等待
synchronized (TicketSell.class) {
if (tick > 0) { //需要再判断一次值是否大于0;
try {
Thread.sleep(10); //休眠10毫秒操作
} catch (Exception e) { }
System.out.println(Thread.currentThread().getName() + tick--);
}}}}
同步函数
public synchronized void show() { 代码 -----}
同步函数用的是哪个锁?
函数需要被对象调用,那么函数都有一个所属对象引用:this
所以同步函数使用的锁是this
通过程序来验证
一个线程运行同步代码块
一个线程运行同步函数
都在执行卖票动作
如果使用同一个锁,那数据将安全
如果使用不同的锁,则数据会出现重复或者错误
线程1执行flag为真的同步代码块
线程2执行flag为假的同步函数
class TicketSell1 implements Runnable {
private int tick = 100; //定义票数
Object o = new Object();
boolean flag = true;
/*
一开始 线程1 flag = true 进去指定位置循环
然后 到线程2 flag = false 进去指定位置循环
运行验证: 如果是同一个锁,则数据不会重复
*/
public void run() {
if (flag) {
while (tick > 0) {
synchronized (o) { //Object锁打印一次后,再换成this再打印一次
if (tick > 0) {
try {
Thread.sleep(10);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + tick--);
}
}
}
} else {
while (tick > 0) {
show(); //调用同步函数
}
}
}
public synchronized void show() { //定义同步函数
if (tick > 0) {
try {
Thread.sleep(10);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + tick-- + " show");
}
}
}
public class Demo3 {
public static void main(String[] args) {
TicketSell1 ts = new TicketSell1();
Thread t1 = new Thread(ts, "线程1 :");
Thread t2 = new Thread(ts, "线程2 :");
t1.start();
try {
Thread.sleep(10); //需要让主线程main休眠一下,不然会一次执行完
} catch (Exception e) {
}
ts.flag = false;
t2.start();
}
}
如果同步函数被静态修饰后,使用的是什么锁呢?
public static synchronized void show(){}
- 通过验证,发现不是this,因为静态方法中,不可以定义this
静态进内存后,内存中没有本类对象,但是一定有该类对应的字节码文件对象
格式为 类名.class 此为静态同步函数的锁
死锁:同步中嵌套同步
代码演示1
class Lock {
static Object o1 = new Object();
static Object o2 = new Object();
}
class Test implements Runnable {
private boolean fal;
Test(boolean fal) {
this.fal = fal;
}
public void run() {
if (fal) { //线程1运行此段程序
synchronized (Lock.o1) {
System.out.println(Thread.currentThread().getName() + " 拿到了锁 Lock.o1");
synchronized (Lock.o2) {
System.out.println(Thread.currentThread().getName() + "拿到了锁 Lock.o2");
}
}
} else { //线程2运行此段程序
synchronized (Lock.o2) {
System.out.println(Thread.currentThread().getName() + "拿到了锁 Lock.o2");
synchronized (Lock.o1) {
System.out.println(Thread.currentThread().getName() + " 拿到了锁 Lock.o1");
}
}
}
}
}
public class demo2 {
public static void main(String[] args) {
Thread t = new Thread(new Test(true), "线程1");
Thread t1 = new Thread(new Test(false), "线程2");
t.start();
t1.start();
}
}
从结果可以看出,程序卡着无法继续运行,需要手动停止
- 原因: 线程1运行 if ,拿到了Lock.o1的锁,接着需要拿Lock.o2的锁,但是此时线程2运行了else已经拿了Lock.o2的锁
线程2如果想继续往下运行,也需要拿Lock.o1的锁,
所以线程1占据了 o1 的锁,等待 o2 的锁,线程2占据 o2 的锁,等待 o1 的锁,谁也互不相让,最终程序卡住
线程存在随机性,这里只是说出了出现死锁的情况,也可能不会出现
.
.
单例设计模式—懒汉式
class Single {
private Single() {
}
private static Single s = null;
public static Single getSingle() {
if (s == null) {
synchronized (Single.class) {
if (s == null) {
s = new Single();
}
}
}
return s;
}
}