1)基本概念区分:
程序:是一段静态的代码,是我们解决问题思维方式在计算机中的描述语言,是应用程序执行的蓝本,是一个静态的概念,存放在外存上,还没有运行的软件叫程序。
进程:是程序的一个运行例程,用来描述程序的动态执行过程。是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。
线程:是程序中相对独立的一个程序段的执行单元。是指进程中的一个执行流程,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。线程总是属于某个进程,进程中的多个线程共享进程的内存。
多线程编译:一个程序运行时,可以分成几个并发的子程序任务同时处理(“同时”执行是人的感觉,在线程之间实际上轮换执行)每个子任务都称为一个线程,彼此相互独立。
2)Java线程的运行机制
JVM(Java虚拟机)很多程序都依赖线程调度,执行代码的任务是由线程完成的,每一个线程都有一个独立的程序计数器和方法调用栈。
程序计数器:是记录线程当前执行程序代码位置的寄存器,在线程执行的过程中,程序计数器指向下一条要执行的指令。
方法调用栈:用来描述程序执行时一系列方法调用过程。栈中的每一个元素对应一个浅帧,每一个浅帧对应一个方法调用,帧中保存方法调用的参数、局部变量和程序执行过程中的临时参数。
2.1)线程的执行:使用java.lang.Thread类或者java.lang.Runnable接口编写代码来定义、实例化和启动新线程。
方法 | 方法说明 |
void run() | 线程运行时所执行的代码都会在这个方法中, 是Runnable接口声明中的唯一方法 |
void start() | 是该线程开始执行,Java虚拟机调用run()方法 |
start int activeCount( ) | 返回当前线程的线程组中的活动线程数目 |
start Thread currentThread( ) | 返回对当前正在执行线程对象的引用 |
start int enumerate(Thread[ ] t ) | 将当前线程的每一个活动线程复制到指定的数组中 |
String getName( ) | 返回该线程的名称 |
int getPriority( ) | 返回该线程的优先级 |
Tstatic void yield( ) | 暂停当前正在执行的线程,并执行其他线程 |
Thread Group getState( ) | 返回该线程的状态 |
Thread Group getThreadGroup( ) | 返回该线程所属的线程组 |
final boolean isAlive( ) | 测试线程是否属于活动状态 |
void setDaemon(boolean on) | 将该线程标记为守护线程或用户线程 |
void setName(String name) | 改变线程名称,使之与参数name相同 |
void interrupt( ) | 中断线程 |
void join( ) | 等待该线程终止,它有多个重载方法 |
检验多线程安全问题的标准:
1) 是否是多线程环境
2) 是否有共享数据
3) 是否有多条语句对共享数据进行操作
优化第三个前提条件,使用synchronized同步代码块的方式将3)包起来!
格式: 理解为:门的开和关
synchronized(锁对象(隐示锁)){
多条语句对共享数据进行操作;
}
锁对象:可以是任意的Java类,每一个子线程共有同一个锁(不能使用多个锁)
需求:某电影院出售某些电影的票(复联3,红高粱....),有三个窗口同时进行售票(100张票),请您设计一个程序,模拟电影院售票(两种方式:继承、 接口)
[例一]继承SellTicket st1 = new SellTicket() ;//创建三个子线程,分别代码三个窗口
SellTicket st2 = new SellTicket() ;
SellTicket st3 = new SellTicket() ;
st1.setName("窗口1");//设置线程名称
st2.setName("窗口2");
st3.setName("窗口3");
st1.start();//启动线程
st2.start();
st3.start();
}
}
(子程序)
package org.westos_01;//SellTicket线程
public class SellTicket extends Thread {
//为了不让外界更改这个类中的数据 //private//定一个票数
//要让每一个线程都要共同使用同一个数据,应该被static修饰// private int tickets = 100 ;
private static int tickets = 100 ;
@Override
public void run() {
//为了模拟电影卖票(模拟一直有票)//死循环
//st1,st2,st3都有执行这个里面的方法//st1 100//st2 100//st3 100
while(true) {
if(tickets>0) {
System.out.println(getName()+"正在出售第"+(tickets--)+"张票");
}}}}
[例二]接口
(子程序)package org.westos_02;
public class SellTicket implements Runnable {
private int tickets = 100 ;//定义100张票
@Override
public void run() {
while(true) {
if(tickets>0) {
System.out.println(Thread.currentThread().getName()
+"正在出售第"+(tickets--)+"张票");
}}}}
package org.westos_02;//第二种方式实现
//给继续加一些内容(延迟操作实现)
(主程序) public class SellTicketDemo {
public static void main(String[] args) {
//创建资源类对象(共享资源类/目标对象)
SellTicket st = new SellTicket() ;
//创建线程类对象
Thread t1 = new Thread(st, "窗口1") ;
Thread t2 = new Thread(st ,"窗口2") ;
Thread t3 = new Thread(st, "窗口3") ;
//启动线程
t1.start();
t2.start();
t3.start();
}
}
//现实中网络售票有延时,故让程序每一次抢票的进程休息100毫秒
public class SellTicket implements Runnable {
private int tickets = 100 ;//定义100张票
@Override
public void run() {
while(true) {
//t1先进来 t2
if(tickets>0) {//为了模拟更真实的场景(网络售票有延迟的),稍作休息
try {
//t1睡 t2睡
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票");
//100张票 但此时打印结果出现0、-1并且窗口2票数是73张、窗口1票数是73张、窗口3票数是73张出现一张票被卖多次的现象
问题:为什么会出现同一张票被卖多次?
答: 出现同票的原因:CPU的执行有一个特点(具有原子性操作:最简单最基本的操作)
t1线程进来,睡完了(延时100ms),100张票
原子性操作:记录以前的值
接着tickets-- :票变成99张票
在马上输出99张票之前,t2/t3进来,直接输出记录的以前那个tickets的值,故出现一票多卖现象。
问题:为什么会出现0、-1张票
答:(延迟操作+线程的执行随机性)
2.2)在程序中synchronized(锁对象) {
多条语句对共享数据操作的代码;
}
注意:锁对象:一定要同一个锁(每个线程只能使用同一把锁),锁对象:任何的Java类(引用类型)
改进后的程序:package or_west_01;
public class StDemo implements Runnable {
private int ticket=100;//要让每一个线程都要共同使用同一个数据,应该被static修饰
private Object obj=new Object();
@Override
public void run() {
synchronized(obj) {//在循环入喉处加入锁开关,可解决下面两个问题
while(true) {
if(ticket>0) {
try {
Thread.sleep(100);//现实中网络售票有延时,故让程序每一次抢票的进程休息100毫秒
//但此时打印结果出现0、-1并且窗口2票数是73张、窗口1票数是73张、窗口3票数是73张出现一张票被卖多次的现象
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"票数是"+(ticket--)+"张");
}}}}}
//如果一个方法一进来就是同步代码块,那么可不可以将同步放到方法来进行声明呢? 可以
private void sellTicket() {
synchronized(d) {
if(tickets>0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+"正在出售第"+(tickets--)+"张票");
}
}
}
//非静态的方法:同步方法(需要底层源码,一些方法会声明synchronized)的锁对象:this
//同步方法 :里面锁对象是谁? this
private synchronized void sellTicket() {
if(tickets>0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+"正在出售第"+(tickets--)+"张票");
}
}
//静态的同步方法:和反射有关 (静态同步方法的锁对象:类名.class)
2.3)接口:Lock锁(Jdk5.0以后,java提供了一个具体的锁: )
private Lock lock= new ReentrantLock(); //显示获取锁的前提,一定要创建Lock接口对象
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构 可以使用Lock锁进行具体的锁定操作类 提供了具体的实现类:ReentrantLock加锁并且去释放锁
子 程序可改为: package org.westos_07;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SellTicket implements Runnable {
// 定义票
private int tickets = 100;
// Object obj = new Object();
private Lock lock= new ReentrantLock(); //显示获取锁的前提,一定要创建Lock接口对象
@Override
public void run() {
while (true) {
try { //try...finally
lock.lock(); // 获取锁 syncrhonized(obj)
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
}
} finally {//释放锁
if(lock!=null) {
lock.unlock();
}
}}}
2.4)线程安全的类:
StringBuffer sb = new StringBuffer() ;
Vector<String> v = new Vector<String>() ;
Hashtable<String, String> hm = new Hashtable<String,String>() ;
//Vector<String>它线程安全的类,还是不习惯使用这个集合,通过ArrayList集合:线程不安全的类
List<String> array = new ArrayList(); //线程不安全的类
//public static <T> List<T> synchronizedList(List<T> list)
//返回指定列表支持的同步(线程安全的)列表
List list = Collections.synchronizedList(array) ; //线程安全的方法
2.5)解决了多线程安全问题,但是还是有些问题:
1)执行效率低
2)会产生死锁(两个或两个以上的线程,在执行的过程中出现互相等待的情况,就叫做死锁!)
死锁示例程序:
(主程序)public class DieLockDemo {
public static void main(String[] args) {
//创建线程了对象
DieLock dl1 = new DieLock(true) ;
DieLock dl2 = new DieLock(false) ;
//启动线程
dl1.start();
dl2.start();}}
public class DieLock extends Thread {
private boolean flag ;//声明一个成语变量
public DieLock(boolean flag) {
this.flag = flag ;}
//重写run方法
@Override
public void run() {
if(flag) {
synchronized (MyLock.objA) {
System.out.println("if ObjA");
synchronized (MyLock.objB) {
System.out.println("if objB");
}
}
}else {
synchronized (MyLock.objB) {
System.out.println("else objB");
synchronized (MyLock.objA) {
System.out.println("else objA");
}
}
}
}}
在程序执行后打印情况出现以下三种:
第一种情况:if ObjA
else objB
第二种情况
else objB
if ObjA
第三种情况:
理想状态
else objB
else objA
if ObjA
if objB
if ObjA
if objB
else objB
else objA