-多线程安全
当多个线程同时共享,同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。但是做读操作是不会发生数据冲突问题。
案例
案例:需求现在有100张火车票,有两个窗口同时抢火车票,请使用多线程模拟抢票效果。
package com.flyfish.thread.test;
class ThreadTrain1 implements Runnable {
private int count = 100;
private static Object oj = new Object();
@Override
public void run() {
while (count > 0) {
try {
Thread.sleep(50);
} catch (Exception e) {
// TODO: handle exception
}
sale();
}
}
public void sale() {
// 前提 多线程进行使用、多个线程只能拿到一把锁。
// 保证只能让一个线程 在执行 缺点效率降低
// synchronized (oj) {
// if (count > 0) {
System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - count + 1) + "票");
count--;
// }
// }
}
}
public class ThreadDemo {
public static void main(String[] args) {
ThreadTrain1 threadTrain1 = new ThreadTrain1();
Thread t1 = new Thread(threadTrain1, "①号窗口");
Thread t2 = new Thread(threadTrain1, "②号窗口");
t1.start();
t2.start();
}
}
运行结果
部分火车票会重复出售。
结论发现,多个线程共享同一个全局成员变量时,做写的操作可能会发生数据冲突问题。
线程安全解决办法:
什么是多线程之间同步?
同步代码块
就是将可能会发生线程安全问题的代码,给包括起来。
synchronized(同一个数据){
可能会发生线程冲突问题
}
就是同步代码块
synchronized(对象)//这个对象可以为任意对象
{
需要被同步的代码
}
对象如同锁,持有锁的线程可以在同步中执行
没持有锁的线程即使获取CPU的执行权,也进不去
同步的前提:
1.必须要有两个或者两个以上的线程
2. 必须是多个线程使用同一个锁
必须保证同步中只能有一个线程在运行
好处:解决了多线程的安全问题
弊端:多个线程需要判断锁,较为消耗资源、抢锁的资源。
代码示例
public void sale() {
// 前提 多线程进行使用、多个线程只能拿到一把锁。
// 保证只能让一个线程 在执行 缺点效率降低
synchronized (oj) {
if (count > 0) {
System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - count + 1) + "票");
count--;
}
}
}
运行效果
同步函数
在方法上修饰synchronized 称为同步函数
代码示例
public synchronized void sale() {
if (trainCount > 0) {
try {
Thread.sleep(40);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "张票.");
trainCount--;
}
}
this锁(对象锁)和Static(类锁)
Mythread extends Thread
synchronized (Mythread.class) {
//锁类 即此类的实例拥有共同的锁 即锁共有
//可以当成是类变量的感觉..所以对象共享
}
synchronized (this) {
//锁对象 即各实例都有各自的锁
//无意义
}
}
但如果是实现自runable接口则可以使用this
MyThread implements Runnable MyThread my =new MyThread();
Thread t = new Thread(my);
Thread t2 = new Thread(my);
Thread t3 = new Thread(my);
因创建线程对象不同 因为runable方式的几条线程是共享一个对象
查阅文档 JDK5以后有Lock锁可代替 可不知因何测后仍有同步问题…可能是方式不对
其实为了避免问题 俩种线程方式都可直接锁 xxx.class 即可
class ClassA {
public synchronized void A()
{
System.out.println("AAAAAAAAAAAAAAAAA");
while (true)
{
}
}
public synchronized void B()
{
System.out.println("BBBBBBBBBBBBBBBBB");
while (true)
{
}
}
}
public class MethodSynchronizedTest {
public static void main(String[] args) {
final ClassA clazz = new ClassA();
// 启动一个线程
new Thread(new Runnable() {
@Override
public void run() {
clazz.A();// 调用A方法
}
}).start();
// 启动另一个线程
new Thread(new Runnable() {
@Override
public void run() {
clazz.B();// 调用B方法
}
}).start();
}
}
运行结果
class TicketWindow3 implements Runnable {
private int max_value = 0;
private Object lock = new Object();
private boolean flag = true;
@Override
public void run() {
if (flag) {
while (true) {
//同步函数其实用到的锁就是 this 锁, this锁针对的是对象
//静态锁,锁是类的字节码信息,因此如果一个类的函数为静态方法,那么我们需要通过该类的 class 信息进行加锁;
synchronized (lock) {
if (max_value > 50) {
break;
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + ":lock..." + max_value++);
}
}
} else {
while (true) {
if (ticket()) {
break;
}
}
}
}
private synchronized boolean ticket() {
if (max_value > 50) {
return true;
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + ": method.." + max_value++);
return false;
}
public void change() throws InterruptedException {
Thread.sleep(30);// 读者可以自行思考为什么要sleep
this.flag = false;
}
}
public class Bank3 {
public static void main(String[] args) throws InterruptedException {
TicketWindow3 tw3 = new TicketWindow3();
Thread t1 = new Thread(tw3);
Thread t2 = new Thread(tw3);
t1.start();
tw3.change();
t2.start();
}
}
文字说明: 可能到输出性信息,其中会有 51 这样的信息输出,为什么会这样呢?因为上述的代码 两处业务逻辑同步锁是两把锁,如果您将lock 换成 this,这个现象就不会出现
class TicketWindow4 implements Runnable {
private static int max_value = 0;
private boolean flag = true;
@Override
public void run() {
if (flag) {
while (true) {
//同步函数其实用到的锁就是 this 锁, this锁针对的是对象
//静态锁,锁是类的字节码信息,因此如果一个类的函数为静态方法,那么我们需要通过该类的 class 信息进行加锁;
synchronized (TicketWindow4.class) {
if (max_value > 50) {
break;
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + ":lock..." + max_value++);
}
}
} else {
while (true){
if (ticket()){
break;}}
}
}
private synchronized static boolean ticket() {
if (max_value > 50) {
return true;
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + ": method.." + max_value++);
return false;
}
public void change() throws InterruptedException {
Thread.sleep(30);// 读者可以自行思考为什么要sleep
this.flag = false;
}
}
public class Bank4 {
public static void main(String[] args) throws InterruptedException {
TicketWindow4 tw3 = new TicketWindow4();
Thread t1 = new Thread(tw3);
Thread t2 = new Thread(tw3);
t1.start();
tw3.change();
t2.start();
}
}
运行结果
两种锁同时使用
public class TestSynchronized {
public synchronized void test1() {
int i = 5;
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
public static synchronized void test2() {
int i = 5;
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
public static void main(String[] args) {
final TestSynchronized myt2 = new TestSynchronized();
Thread test1 = new Thread(new Runnable() {
@Override
public void run() {
myt2.test1();
}
}, "test1");
Thread test2 = new Thread(new Runnable() {
@Override
public void run() {
TestSynchronized.test2();
}
}, "test2");
test1.start();
test2.start();
}
}
上面代码synchronized同时修饰静态方法和实例方法,但是运行结果是交替进行的,这证明了类锁和对象锁是两个不一样的锁,控制着不同的区域,它们是互不干扰的。同样,线程获得对象锁的同时,也可以获得该类锁,即同时获得两个锁,这是允许的。
结论:
A: synchronized static是某个类的范围,synchronized static cSync{}防止多个线程中多个实例同时访问这个 类中的synchronized static 方法。它可以对类的所有对象实例起作用。
B: synchronized 是某实例的范围,synchronized isSync(){}防止多个线程中这一个实例同时访问这个类的synchronized 方法。
多线程死锁
同步中嵌套同步,导致锁无法释放
package com.flyfish.thread.test;
class ThreadTrain6 implements Runnable {
// 这是货票总票数,多个线程会同时共享资源
private int trainCount = 100;
public boolean flag = true;
private Object mutex = new Object();
@Override
public void run() {
if (flag) {
while (true) {
synchronized (mutex) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 锁(同步代码块)在什么时候释放? 代码执行完, 自动释放锁.
// 如果flag为true 先拿到 obj锁,在拿到this 锁、 才能执行。
// 如果flag为false先拿到this,在拿到obj锁,才能执行。
// 死锁解决办法:不要在同步中嵌套同步。
sale();
}
}
} else {
while (true) {
sale();
}
}
}
/**
* @methodDesc: 功能描述:(出售火车票)
* @author: 余胜军
* @param:
* @createTime:2017年8月9日 下午9:49:11
* @returnType: void
* @copyright:上海每特教育科技有限公司
*/
public synchronized void sale() {
synchronized (mutex) {
if (trainCount > 0) {
System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "张票.");
trainCount--;
}
}
}
}
public class DeadlockThread {
public static void main(String[] args) throws InterruptedException {
ThreadTrain6 threadTrain = new ThreadTrain6(); // 定义 一个实例
Thread thread1 = new Thread(threadTrain, "一号窗口");
Thread thread2 = new Thread(threadTrain, "二号窗口");
thread1.start();
Thread.sleep(40);
threadTrain.flag = false;
thread2.start();
}
}
线程一先启动 synchronized (mutex)锁定 mutex 对象 然后主线程休眠 flag=false 线程二启动 synchronized void sale()锁定当前对象两个线程各获得一把锁,线程死锁
多线程有三大特性
1. 是原子性
即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
一个很经典的例子就是银行账户转账问题:
比如从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。这2个操作必须要具备原子性才能保证不出现一些意外的问题。
我们操作数据也是如此,比如i = i+1;其中就包括,读取i的值,计算i,写入i。这行代码在Java中是不具备原子性的,则多线程运行肯定会出问题,所以也需要我们使用同步和lock这些东西来确保这个特性了。
原子性其实就是保证数据一致、线程安全一部分,
2. 是可见性
当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
若两个线程在不同的cpu,那么线程1改变了i的值还没刷新到主存,线程2又使用了i,那么这个i值肯定还是之前的,线程1对变量的修改线程没看到这就是可见性问题。
3. 有序性
程序执行的顺序按照代码的先后顺序执行。
一般来说处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。如下:
int a = 10; //语句1
int r = 2; //语句2
a = a + 3; //语句3
r = a*a; //语句4
则因为重排序,他还可能执行顺序为 2-1-3-4,1-3-2-4
但绝不可能 2-1-4-3,因为这打破了依赖关系。
显然重排序对单线程运行是不会有任何问题,而多线程就不一定了,所以我们在多线程编程时就得考虑这个问题了。