Day06 多线程与线程安全
一、 多线程
1.1多线程原理
每一个线程有一个自己的栈内存空间,程序运行时,JVM开启main线程,如果在main线程中调用了启动线程的satrt()方法,就会开辟新线程。
在java中,每次程序运行至少启动2 个线程 。一个是main线程 ,一个是垃圾收集线程 。因为每当使用java命令执行一个 类的时候,实际上都会启动一个JVM,每一个JVM其实在就是在操作系统中启 动了一个进程
1.2 Thread类
构造方法:
public Thread()
分配新线程对象public Thread(String name)
分配指定名字新线程public Thread(Runnable target)
分配带有指定目标的新线程public Thread(Runnable target,String name)
分配带有指定目标的新线程并赋名
常用方法:
public String getName()
获取当前线程的名称public void start()
使JVM调用此线程的run方法public void run()
此线程要执行的任务public static void sleep(long millsecond)
使当前正在执行的线程以指定的方式暂停数秒public static Thread currentThread()
返回当前正在执行的线程对象的引用
1.3 创建线程的第二种方式
第一种方式新建一个类继承于Thread类,重写其中的run()方法,再建一个此类的对象,调用start()方法来开启线程。
第二种方式通过实现Runnable接口,重写Runnable来定义一个线程执行体,再通过Thread来实例化这个内容的对象,通过线程对象的start()方法来启动线程。它又可以分为两种
run方法()是多线程程序的一个执行目标,所有多线程程序都在run方法里。
-
创建一个实现Runnable的类,实例化此类,再用Thred创建线程对象,执行线程
public class Day06 { public static void main(String[] args) { MyThread mt = new MyThread(); Thread thread = new Thread(mt,"新线程"); thread.start(); for(int i =10;i<100;i+=10) { System.out.println("."+i); } } } class MyThread implements Runnable{ @Override public void run() { for(int i = 0;i<10;i++) { System.out.println(","+i); } } }
-
采用匿名内部类的方式
public class Day06 { public static void main(String[] args) { Thread thread = new Thread(new Runnable() { @Override public void run() { for(int i = 0;i<10;i++) { System.out.println(","+i); } } },"新线程"); thread.start(); for(int i =10;i<100;i+=10) { System.out.println("."+i); } } }
1.4 Thread和Runnable的区别
- Runnable接口可以多实现,避免了Thread单继承的局限性
- 对于多个相同线程执行更方便
- 线程池中只能放入Runnable或Callable线程不能直接放入继承Thread的类
- 增加程序的健壮性,实现解耦操作,代码可以被多线程共享,代码与线程独立
二、 线程安全
2.1 线程安全问题
多个线程共享一个数据的时候,如果只对数据进行读的操作,数据的安全性较高,如果还带有写入的操作,具有一定的危险性。
public class Day06 {
public static void main(String[] args) {
Ticket window = new Ticket();
Thread windows1 = new Thread(window,"窗口1");
Thread windows2 = new Thread(window,"窗口2");
windows1.start();
windows2.start();
}
}
class Ticket implements Runnable{
private int num = 20;
public void run() {
while(num>0){
System.out.println(Thread.currentThread().getName()+"正在售票,余票还有:"+num--);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
这是因为在窗口1卖出的瞬间,还没有执行num–,窗口2就获取了此数。
2.2 线程同步
为了保证多线程在对同一资源进行写操作时的安全性,需要线程同步。有三种实现方式:
关键字:synchronized,用于某个方法的某个区块中,表示对该区块的资源实行互斥访问-----在一个瞬间,只有一个线程能访问
-
同步代码块
-
synchronized(同步锁){ 同步操作代码 }
同步锁相当于在一个对象上标记一个锁,锁对象可以是任意类型,多个线程的锁是同一个。在任何时刻,同步锁只能属于多个线程的其中一个
改造后的Ticket类,以Object 类的obj对象为锁:
class Ticket implements Runnable{
private Object obj = new Object();
private int num = 20;
public void run() {
while(num>0){
synchronized(obj) {
System.out.println(Thread.currentThread().getName()+"正在售票,余票还有:"+num--);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
-
同步方法
-
格式:
public synchronized void method(){ 可能会出现线程安全问题的代码 }
例子用同步方法修改后:
这种方法的同步锁:
- 对于非static方法,同步锁是this
- 对于static方法,是当前方法所在类的字节码对象(类名.class)
class Ticket implements Runnable{ private int num = 20; public synchronized void run() { while(num>0){ System.out.println(Thread.currentThread().getName()+"正在售票,余票还有:"+num--); try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
-
-
Lock锁
java.tuilconcurrent.locks.Lock
,它除了包含上面两种方法的功能,还有更强大的功能。将加锁解锁方法化:-
public void lock()
-
public void unlock()
构造方式:
Lock lock = new ReentrantLock();
class Ticket implements Runnable{ private int num = 100; Lock lock = new ReentrantLock(); public void run() { while(num>0){ lock.lock(); System.out.println(Thread.currentThread().getName()+"正在售票,余票还有:"+num--); try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); }lock.unlock(); } } }
-
三、 线程状态
- New:刚被创建,还没有启动(没有被start()方法调用)
- Runnable:可以在JVM里运行的状态,但是不一定正在运行(等待CPU执行)
- Blocked:锁阻塞,由于线程没有拿到对象锁(被其他线程占用),而停滞的状态,拿到锁后会转变为Runnable状态。调用wait()以及wait(millsecond)都会使线程进入等待状态,并且失去锁。
- TimedWaiting:等待唤醒状态,这是由于调用了含有超时参数的方法,如:Thread.sleep(5000),Object.wait()方法等。等到超时期满后或者在超时期前收到唤醒通知时,会转变为Runnable状态
- Waiting:无限等待状态,等待另一个线程执行唤醒动作的状态。原因是调用了Object中的wait方法,当另一个线程调用notify方法或者notifyAll方法才能唤醒。唤醒后,如果拿到对象锁,变为Runnable,拿不到转为Blocked状态。
- Terminate:run方法结束的状态,结束的原因又可分为
- 正常结束
- 由于出现异常,但是没有捕获而导致程序结束
无限等待状态举例:
public class Day06 {
public static Object obj = new Object();
public static Object obj2 = new Object();
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
while(true) { System.out.println(Thread.currentThread().getName()+"试图拿锁");
try {
Thread.sleep(3000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
synchronized(obj) {
try { System.out.println(Thread.currentThread().getName()+"抢到了锁") System.out.println(Thread.currentThread().getName()+"准备进入无限等待,失去同步锁!");
obj.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} System.out.println(Thread.currentThread().getName()+"唤醒成功!"); System.out.println("_________________________________________");
}
}
}
},"线程一").start();
new Thread(new Runnable() {
@Override
public void run() {
while(true) { System.out.println(Thread.currentThread().getName()+"试图拿锁");
try {
Thread.sleep(3000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
synchronized(obj) {
try {
System.out.println(Thread.currentThread().getName()+"抢到了锁");
System.out.println(Thread.currentThread().getName()+"准备进入无限等待,失去同步锁!");
obj.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"唤醒成功!"); System.out.println("_________________________________________");
}
}
}
},"线程三").start();
new Thread(new Runnable() {
@Override
public void run() {
while(true) { System.out.println(Thread.currentThread().getName()+"试图拿锁");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized(obj) { System.out.println(Thread.currentThread().getName()+"抢到了锁");
System.out.println("准备唤醒其他线程");
obj.notify();
}
}
}
},"唤醒线程:线程二").start();
}
}
notify改为notifyall之后,两个线程都会被唤醒此时出现两种情况
- 被唤醒的线程其中一个抢到对象锁,转为Runnable,另一个转为Blocked
- 唤醒线程抢到锁,剩下两个均为Blocked状态
-
wating状态不是线程的操作,它体现了多个线程间的通信,存在争取锁与协作完成任务两种关系