目录
1、什么叫线程安全
当多个线程访问某个类(对象或者方法)时,这个类始终能表现出正确的行为,那么这个类(对象或方法)是安全的。
2、多窗口买票案例
package com.fly.thread_demo.demo_2;
/**
* 模拟火车站买票:一共库存 100张票 ,两个窗口同时卖票
*/
public class ThreadTrain {
/**
* 车票库存100张
*/
private int count = 100;
/**
* 买票方法
*/
public void sell(){
if (count > 0 ){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
count -- ;
System.out.println(Thread.currentThread().getName()+ ":正在出售第"+(100-count)+"张火车票,卖完还剩"+count+"张!");
}
}
public static void main(String[] args) {
ThreadTrain threadTrain = new ThreadTrain();
/**
* 窗口一
*/
new Thread(new Runnable() {
@Override
public void run() {
while (threadTrain.count > 0 ){
threadTrain.sell();
}
}
},"窗口一").start();
/**
* 窗口二
*/
new Thread(new Runnable() {
@Override
public void run() {
while (threadTrain.count > 0 ){
threadTrain.sell();
}
}
},"窗口二").start();
}
}
我们一共有100张火车票,但是最后我们卖了101张,这就是线程安全问题
3、如何解决线程安全问题
3.1、锁的特征
只能同时被一个线程持有。
3.2、内置锁(synchronized)
保证线程的原子性,当线程进入方法时,自动获取锁,一旦锁被获取,其他线程在执行该方法时候会等待,当程序执行完毕后就会释放锁,后面排队的线程会抢这把锁,没抢到的线程会继续等待。
3.3、解决火车票问题
3.3.1、同步代码块
private Object obj = new Object();
/**
* 买票方法 使用静态代码快的方式
*/
public void sell(){
//让一个obj对象成为一把锁
synchronized (obj){
if (count > 0 ){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
count -- ;
System.out.println(Thread.currentThread().getName()+ ":正在出售第"+(100-count)+"张 火车票,卖完还剩"+count+"张!");
}
}
}
public void sell(){
//这里我们每次使用不同的对象做为锁
synchronized (new Object()){
if (count > 0 ){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
count -- ;
System.out.println(Thread.currentThread().getName()+ ":正在出售第"+(100-count)+"张火车票,卖完还剩"+count+"张!");
}
}
}
结论一: 在多线程中,要使用同步,必须使用同一把锁。
另:
如果使用String 类型的锁,我们改变了String对象的值,就会让锁发生改变
如果我们用对象锁,我们改变对象属性的值,不会改变锁
3.3.2、同步方法
/**
* 买票方法 方法加 synchronized 关键字
*/
public synchronized void sell(){
if (count > 0 ){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
count -- ;
System.out.println(Thread.currentThread().getName()+ ":正在出售第"+(100-count)+"张火车票,卖完还剩"+count+"张!");
}
}
4、同步方法锁的进阶
这里我们就只演示结论,不演示不成功的来对比了。
package com.fly.thread_demo.demo_2;
/**
* 非静态同步方法 和 静态同步方法
*/
public class ThreadSafe {
private Object obj = new Object();
public void printNum_1(){
synchronized (obj){
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ ": i = " + i);
}
}
}
public void printNum_2(){
synchronized (obj){
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ ": i = " + i);
}
}
}
/**
* 现在有两个线程,分别调用printNum_1和printNum_2方法
* 如果我们给printNum_1 和 printNum_2 加锁
* 我们使用前面的结论:如果使用同一把锁 两个方法将会同步 如果不是同一把锁 将不会同步
*/
public static void main(String[] args) {
ThreadSafe threadSafe = new ThreadSafe();
new Thread(new Runnable() {
@Override
public void run() {
threadSafe.printNum_1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
threadSafe.printNum_2();
}
}).start();
}
}
在线程Thread-0执行完毕后再执行线程Thread-1,说明前面的结论没有错
4.1、非静态同步方法
package com.fly.thread_demo.demo_2;
/**
* 非静态同步方法 和 静态同步方法
*/
public class ThreadSafe {
private Object obj = new Object();
/**
* 在非静态方法上加synchronized锁
*/
public synchronized void printNum_1(){
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ ": i = " + i);
}
}
/**
* 使用当前对象作为锁
*/
public void printNum_2(){
synchronized (this){
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ ": i = " + i);
}
}
}
/**
* 现在有两个线程,分别调用printNum_1和printNum_2方法
* 如果我们给printNum_1 和 printNum_2 加锁
* 如果使用同一把锁 他们将会同步 如果不是同一把锁 将不会同步
*/
public static void main(String[] args) {
ThreadSafe threadSafe = new ThreadSafe();
new Thread(new Runnable() {
@Override
public void run() {
threadSafe.printNum_1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
threadSafe.printNum_2();
}
}).start();
}
}
结论二: 非静态方法上加synchronize锁,其实使用的是this锁,也就是把当前对象作为锁
4.2、静态同步方法
package com.fly.thread_demo.demo_2;
/**
* 非静态同步方法 和 静态同步方法
*/
public class ThreadSafe {
private Object obj = new Object();
/**
* 在静态方法上加synchronized锁
*/
public synchronized static void printNum_1(){
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ ": i = " + i);
}
}
/**
* 使用当前类的 字节码文件 作为锁
*/
public void printNum_2(){
synchronized (ThreadSafe.class){
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ ": i = " + i);
}
}
}
/**
* 现在有两个线程,分别调用printNum_1和printNum_2方法
* 如果我们给printNum_1 和 printNum_2 加锁
* 如果使用同一把锁 他们将会同步 如果不是同一把锁 将不会同步
*/
public static void main(String[] args) {
ThreadSafe threadSafe = new ThreadSafe();
new Thread(new Runnable() {
@Override
public void run() {
threadSafe.printNum_1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
threadSafe.printNum_2();
}
}).start();
}
}
结论三:在静态方法上加synchronize锁,其实是把当前类的字节码文件当做锁。
5、死锁
5.1、死锁案例
package com.fly.thread_demo.demo_2;
/**
* 死锁 一般是由锁的嵌套引起的
* 小明和小红打架,小明揪着小红的头发,小红揪着小明的头发,
* 小明要等小红放手自己才会放手,小红也要等着小明放手自己才会放手
* 这样他们永远也不会松手
*/
public class DeathThread implements Runnable{
private String tag;
public void setTag(String tag){
this.tag = tag;
}
private static Object obj1 = new Object();
private static Object obj2 = new Object();
@Override
public void run() {
if ("小明怒了".equals(tag)) {
synchronized (obj1) {
System.out.println("小明揪住了小红的头发...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj2) {
System.out.println("因为小红放开了小明,所以小明放开了小红...");
}
}
}
if ("小红怒了".equals(tag)) {
synchronized (obj2) {
System.out.println("小红揪住了小明的头发...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj1) {
System.out.println("因为小明放开了小红,所以小红放开了小明...");
}
}
}
}
public static void main(String[] args) {
DeathThread dt1 = new DeathThread();
dt1.setTag("小明怒了");
DeathThread dt2 = new DeathThread();
dt2.setTag("小红怒了");
Thread t1 = new Thread(dt1);
Thread t2 = new Thread(dt2);
t1.start();
t2.start();
}
}
程序一直没结束,但是小明和小红都不放手,这是因为小明拿到先拿到了obj1锁,然后休眠了1秒钟,与此同时,小红拿到了obj2锁,也休眠了1秒钟,一秒钟过后,小明要obj2锁才能放手,但是obj2锁在小红手上,要等小红执行完毕才能释放obj2锁,但是小红想要执行完毕要先获得obj1锁,同样obj1锁在小明手上,这样就造成了死锁问题
5.2、快速定位死锁位置
我们一般使用jdk工具
打开控制台输入jconsole
这个时候会打开Java简直和管理控制台
- 打开对应的类
选择不安全链接
找到对应的线程名称,查看详情