如果有多个线程在同时运行, 而这些线程可能会同时运行这段代码. 程序每次运行结果和单线程运行的结果是一样的, 而且其他的变量的值也和预期的是一样的, 就是线程安全的.
其实, 线程安全问题都是由全局变量及静态变量引起的. 若每个线程中对全局变量, 静态变量只有读操作, 而无写操作, 一般来说, 这个全局变量是线程安全的; 若有多个线程同时执行写操作, 一般都需要考虑线程同步, 否则的话就可能影响线程安全.
演示卖票多线程案例:
- 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 类中
锁对象可以是任意类型的对象
多线程 2
来源: http://www.bubuko.com/infodetail-2622941.html