多线程2
如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
其实,线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
演示卖票多线程案例:
package cn.itcast.selltickets;
public class TheadDemo {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Thread t0 = new Thread(ticket);
Thread t1 = new Thread(ticket);
Thread t2 = new Thread(ticket);
t0.start();
t1.start();
t2.start();
}
}
package cn.itcast.selltickets;
public class Ticket implements Runnable {
private int num = 100;
public void run() {
while(true) {
if (num > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "出售第" + num--);
}
}
}
}
Thread-0出售第100
Thread-1出售第99
Thread-2出售第100
Thread-0出售第98
Thread-1出售第97
Thread-2出售第96
Thread-0出售第95
Thread-1出售第94
Thread-2出售第93
Thread-0出售第92
Thread-2出售第91
Thread-1出售第90
Thread-0出售第88
Thread-2出售第89
Thread-1出售第87
Thread-0出售第86
Thread-1出售第85
Thread-2出售第84
Thread-0出售第83
Thread-1出售第82
Thread-2出售第81
Thread-0出售第80
Thread-2出售第79
Thread-1出售第78
Thread-0出售第77
Thread-1出售第76
Thread-2出售第75
Thread-0出售第74
Thread-2出售第73
Thread-1出售第72
Thread-0出售第71
Thread-2出售第70
Thread-1出售第69
Thread-0出售第68
Thread-1出售第67
Thread-2出售第66
Thread-0出售第65
Thread-1出售第64
Thread-2出售第63
Thread-0出售第62
Thread-2出售第61
Thread-1出售第60
Thread-0出售第59
Thread-2出售第58
Thread-1出售第57
Thread-0出售第56
Thread-1出售第55
Thread-2出售第54
Thread-0出售第53
Thread-2出售第52
Thread-1出售第51
Thread-0出售第50
Thread-1出售第49
Thread-2出售第49
Thread-0出售第48
Thread-1出售第47
Thread-2出售第46
Thread-0出售第45
Thread-2出售第44
Thread-1出售第43
Thread-0出售第42
Thread-1出售第41
Thread-2出售第40
Thread-0出售第39
Thread-2出售第38
Thread-1出售第37
Thread-0出售第36
Thread-1出售第35
Thread-2出售第34
Thread-0出售第33
Thread-2出售第32
Thread-1出售第31
Thread-0出售第30
Thread-2出售第29
Thread-1出售第28
Thread-0出售第27
Thread-2出售第26
Thread-1出售第25
Thread-0出售第24
Thread-1出售第23
Thread-2出售第22
Thread-0出售第21
Thread-2出售第20
Thread-1出售第19
Thread-0出售第18
Thread-2出售第17
Thread-1出售第16
Thread-0出售第15
Thread-2出售第14
Thread-1出售第13
Thread-0出售第12
Thread-1出售第11
Thread-2出售第10
Thread-0出售第9
Thread-2出售第8
Thread-1出售第7
Thread-0出售第6
Thread-1出售第5
Thread-2出售第4
Thread-0出售第3
Thread-1出售第2
Thread-2出售第1
Thread-0出售第0
Thread-1出售第-1
用同步代码块synchronized解决
package cn.itcast.selltickets;
public class Ticket implements Runnable {
private int num = 100;
private Object o = new Object();
public void run() {
while (true) {
synchronized (o) {
if (num > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "出售第" + num--);
}
}
}
}
}
用同步方法解决
package cn.itcast.selltickets;
public class Ticket implements Runnable {
private int num = 100;
public synchronized void run() {
while (true) {
if (num > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "出售第" + num--);
}
}
}
}
java中提供了线程同步机制,它能够解决上述的线程安全问题。
线程同步的方式有两种:
? 方式1:同步代码块
? 方式2:同步方法
同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全。
1.2.2 同步方法
? 同步方法:在方法声明上加上synchronized
public synchronized void method(){
可能会产生线程安全问题的代码
}
同步方法中的锁对象是 this
? 静态同步方法: 在方法声明上加上static synchronized
public static synchronized void method(){
可能会产生线程安全问题的代码
}
静态同步方法中的锁对象是 类名.class
用lock接口解决安全性
package cn.itcast.selltickets;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Ticket implements Runnable {
private int num = 100;
private Lock lock = new ReentrantLock();
public void run() {
while (true) {
lock.lock();
if (num > 0) {
try {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "出售第" + num--);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
}
}
同步锁使用的弊端:当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。这时容易引发一种现象:程序出现无限等待,这种现象我们称为死锁。
死锁代码实现
package cn.itcast.deadlock;
public class DeadLock implements Runnable {
private int i = 0;
@Override
public void run() {
// TODO Auto-generated method stub
while(true) {
if (i % 2 == 0) {
synchronized(LockA.locka){
System.out.println("if...locka" + Thread.currentThread().getName());
synchronized(LockB.lockb) {
System.out.println("if...lockb" + Thread.currentThread().getName());
}
}
}else {
synchronized (LockB.lockb) {
System.out.println("else...lockb" + Thread.currentThread().getName());
synchronized (LockA.locka) {
System.out.println("else...locka" + Thread.currentThread().getName());
}
}
}
//i=8,刚从if出来,正准备加1,这时cpu被另一个线程抢了,另一个想成走if,把A锁拿了,这时,cpu又被抢了另一个线程i++,走else,把B锁抢了
//,这时拿到A锁的线程即使抢到cpu资源由于没有b锁,也阻塞了,另一个线程同样的.
i++;
}
}
}
package cn.itcast.deadlock;
public class Demo {
public static void main(String[] args) {
DeadLock dead = new DeadLock();
Thread t0 = new Thread(dead);
Thread t1 = new Thread(dead);
t0.start();
t1.start();
}
}
package cn.itcast.deadlock;
public class LockA {
private LockA() {
}
public static final LockA locka = new LockA();
}
package cn.itcast.deadlock;
public class LockB {
private LockB(){}
public static final LockB lockb = new LockB();
}
if...lockaThread-0
if...lockbThread-0
else...lockbThread-0
else...lockaThread-0
if...lockaThread-0
if...lockbThread-0
else...lockbThread-0
else...lockaThread-0
if...lockaThread-0
if...lockbThread-0
else...lockbThread-0
if...lockaThread-1
线程通信:
package cn.itcast.threadnotify;
public class Demo {
public static void main(String[] args) {
Resource r = new Resource();
Output out = new Output(r);
Input in = new Input(r);
Thread t0 = new Thread(in);
Thread t1 = new Thread(out);
t0.start();
t1.start();
}
}
package cn.itcast.threadnotify;
public class Input implements Runnable {
private Resource r;
public Input(Resource r) {
this.r = r;
}
@Override
public void run() {
// TODO Auto-generated method stub
int i = 0;
while (true) {
synchronized (r) {
if (r.flag) {
try {
r.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (i % 2 == 0) {
r.name = "张三";
r.sex = "男";
} else {
r.name = "lisi";
r.sex = "nv";
}
//将对方线程唤醒,标记改为true
r.flag = true;
r.notify();
}
i++;
}
}
}
package cn.itcast.threadnotify;
public class Output implements Runnable {
private Resource r;
public Output(Resource r) {
this.r = r;
}
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
synchronized (r) {
if (!r.flag) {
try {
r.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(r.name + ".." + r.sex);
r.flag = false;
r.notify();
}
}
}
}
package cn.itcast.threadnotify;
public class Resource {
public String name;
public String sex;
public boolean flag = true;
}
在开始讲解等待唤醒机制之前,有必要搞清一个概念——线程之间的通信:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制。
等待唤醒机制所涉及到的方法:
l wait() :等待,将正在执行的线程释放其执行资格 和 执行权,并存储到线程池中。
l notify():唤醒,唤醒线程池中被wait()的线程,一次唤醒一个,而且是任意的。
l notifyAll(): 唤醒全部:可以将线程池中的所有wait() 线程都唤醒。
其实,所谓唤醒的意思就是让 线程池中的线程具备执行资格。必须注意的是,这些方法都是在 同步中才有效。同时这些方法在使用时必须标明所属锁,这样才可以明确出这些方法操作的到底是哪个锁上的线程。
仔细查看JavaAPI之后,发现这些方法 并不定义在 Thread中,也没定义在Runnable接口中,却被定义在了Object类中,为什么这些操作线程的方法定义在Object类中?
因为这些方法在使用时,必须要标明所属的锁,而锁又可以是任意对象。能被任意对象调用的方法一定定义在Object类中。
l 同步锁
多个线程想保证线程安全,必须要使用同一个锁对象
n 同步代码块
synchronized (锁对象){
可能产生线程安全问题的代码
}
同步代码块的锁对象可以是任意的对象
n 同步方法
public synchronized void method()
可能产生线程安全问题的代码
}
同步方法中的锁对象是 this
n 静态同步方法
public synchronized void method()
可能产生线程安全问题的代码
}
静态同步方法中的锁对象是 类名.class
l 多线程有几种实现方案,分别是哪几种?
a, 继承Thread类
b, 实现Runnable接口
c, 通过线程池,实现Callable接口
l 同步有几种方式,分别是什么?
a,同步代码块
b,同步方法
静态同步方法
l 启动一个线程是run()还是start()?它们的区别?
启动一个线程是start()
区别:
start: 启动线程,并调用线程中的run()方法
run : 执行该线程对象要执行的任务
l sleep()和wait()方法的区别
sleep: 不释放锁对象, 释放CPU使用权
在休眠的时间内,不能唤醒
wait(): 释放锁对象, 释放CPU使用权
在等待的时间内,能唤醒
l 为什么wait(),notify(),notifyAll()等方法都定义在Object类中
锁对象可以是任意类型的对象