一.多线程的两种实现方式对比
第一种方式:
1)自定义类MyThread类,继承自Thread类
2)重写Thread类中的run()方法
3)创建MyThread类对象,分别去启动线程
注意:
1)启动线程的时候,为什么不使用run()方法?
run()不能作为启动线程的方法,因为该方法的调用相当于调用一个普通方法,并不会出现线程的执行的一种随机性!
2)启动线程用的start()方法,start方法的执行,通过Jvm调用run()方法.
一个线程不要连续启动,会出现非法线程状态异常!
第二种方式:
1)自定义一个类;MyRunnable实现Runnable接口
2)实现接口的run()方法
3)创建MyRunnable类对象,创建Thread类对象,将MyRunnable类对象作为参数进行传递,分别启动线程.
问题:
既然有了第一种实现方式,为什么要出现第二种实现方式呢?
第二种方式优于第一种方式的好处:
1)避免了Java单继承的一种局限性
2)更符合Java面向对象的一种设计原则:面向接口编程
将代码的实现和资源对象(
MyRunnable)有效的分离开来(数据分离原则!)
二.线程的生命周期
线程从开始创建的时候,一直到线程的执行,最终到线程的终止!
新建线程:此时此刻该线程没有执行资格,没有执行权
线程就绪:线程有执行资格了,但是没有执行权
在执行线程之前,线程还可能会阻塞
线程执行:线程有执行资格并且有执行权
线程死亡:线程执行完毕,会被垃圾回收线程中的垃圾回收器及时从内存中释放掉.
三.多线程的同步机制
问题引入:
需求:某电影院出售某些电影的票(复联3,....),有三个窗口同时进行售票(100张票),请您设计一个程序,模拟电影院售票;为了模拟更真实的场景,加入延迟操作(让我们线程睡100毫秒)
public class SellTicket implements Runnable {
//定义100张票
private int tickets = 100 ;
@Override
public void run() {
while(true) {
try {
//t1睡 t2睡
Thread.sleep(100); //t2睡
} catch (InterruptedException e) {
e.printStackTrace();
}
if(tickets>0) {
//t1,t2,t3 三个线程执行run里面代码
//为了模拟更真实的场景(网络售票有延迟的),稍作休息
System.out.println(Thread.currentThread().getName()
+"正在出售第"+(tickets--)+"张票");//0
}
}
}
}
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();
}
程序的设计是好的,但是结果有一些问题:
1)同一张票被卖了多次
CPU的执行有一个特点(具有原子性操作:最简单最基本的操作)
2)出现了0或者负票
(延迟操作+线程的执行随机性)
1)同一张票被卖了多次
CPU的执行有一个特点(具有原子性操作:最简单最基本的操作)
2)出现了0或者负票
(延迟操作+线程的执行随机性)
1.通过刚才的这个程序,有安全问题(同票还是负票)
如何解决多线程的安全问题?
校验一个多线程程序是否有安全问题的隐患的前提条件:
1)当前程序是否是多线程环境
2)是否有共享数据
3)是否有多条语句对共享数据进行操作
如何解决多线程的安全问题?
校验一个多线程程序是否有安全问题的隐患的前提条件:
1)当前程序是否是多线程环境
2)是否有共享数据
3)是否有多条语句对共享数据进行操作
2.解决方案:就是将多条语句对共享数据操作的代码,用一个代码包起来---->同步代码块(理解为门的开和关)
格式:
synchronized(锁对象){
针对多条语句对共享数据操作代码;
}
synchronized(锁对象){
针对多条语句对共享数据操作代码;
}
注意:
锁对象:一定要同一个锁(每个线程只能使用同一把锁)
锁对象:任何的Java类(引用类型)
public class SellTicket implements Runnable {
//定义100张票
private int tickets = 100 ;
private Object obj = new Object() ;
@Override
public void run() {
while(true) {
synchronized(obj) { //门的开和关
if(tickets>0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+"正在出售第"+(tickets--)+"张票");
}
}
}
}
}
3. 如果一个方法一进来就是同步代码块,那么可不可以将同步放到方法来进行声明呢? 可以
同步方法 :里面锁对象是谁?
(1)非静态的同步方法: 锁对象:this
private synchronized void sellTicket() {
if(tickets>0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+"正在出售第"+(tickets--)+"张票");
}
}
(2)静态的同步方法:和反射有关 (
静态同步方法的锁对象:类名.class)
private synchronized static void sellTicket() {
if(tickets>0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+"正在出售第"+(tickets--)+"张票");
}
}
4.线程安全的类
StringBuffer sb = new StringBuffer() ;
Vector<String> v = new Vector<String>() ;
Hashtable<String, String> hm = new Hashtable<String,String>() ;
Vector<String>它是线程安全的类,还是不习惯使用这个集合,
使用ArrayList集合:线程不安全的类,通过Collections集合中的synchronizedList()变成线程安全的类
public static <T> List<T> synchronizedList(List<T> list):返回指定列表支持的同步(线程安全的)列表
List<String> array = new ArrayList(); //线程不安全的类
//public static <T> List<T> synchronizedList(List<T> list)
//返回指定列表支持的同步(线程安全的)列表
List list = Collections.synchronizedList(array) ; //线程安全的方法