什么是线程安全问题
多个线程同时操作同一个共享资源的时候可能会出现业务安全问题,称之为线程安全问题。
同步代码块
同步代码块的出现就是为了解决线程的安全问题,其主要作用就是把操作共享数据的代码锁起来。
同步代码块格式
synchronized (锁){
操作共享数据的代码
}
特点1:锁默认打开,有一个线程进去了,锁自动关闭
特点2:里面的代码全部执行完毕,线程出来,锁自动打开
synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁锁对象可以自己随意指定但是一定要是唯一的(建议:直接使用类的字节码文件)
同步方法
简单理解:就是把synchronized关键字加到方法上
同步方法格式
修饰符 synchronized 返回值类型 方法名 (方法参数) {...}
同步方法的锁对象是什么呢?
this
静态同步方法格式
修饰符 static synchronized 返回值类型 方法名(方法参数) {...}
同步静态方法的锁对象是什么呢?
类名.class
特点1:同步方法是锁住方法里面所有的代码
特点2:锁对象不能自己指定(this、字节码文件不用我们自己去写)
特点3:非静态锁用this,静态锁用当前类的字节码文件对象
案列
电影院新上映一部电影准备买票,共开设了3个窗口,票的总量为100张,请用多线程实现。
方式一:
package com.liming.mysynchronized;
public class MyTread extends Thread{
//表示这个类的所有对象可以共享ticket数据
static int ticket = 0;
@Override
public void run() {
while (true){
// 同步代码块
synchronized (MyTread.class){//锁对象,一定要是唯一的,同一个文件中字节码文件一定是唯一的
if (ticket < 100){
ticket++;
System.out.println(getName()+"正在卖第"+ticket+"张电影票");
}else {
break;
}
}
}
}
}
package com.liming.mysynchronized;
public class ThreadDemo {
public static void main(String[] args) {
MyTread t1 = new MyTread();
MyTread t2 = new MyTread();
MyTread t3 = new MyTread();
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
方式二:
package com.liming.mysynchronized;
public class MyRun implements Runnable{
int tick = 0;
@Override
public void run() {
while (true){
synchronized (this){
if (tick < 100){
tick++;
System.out.println(Thread.currentThread().getName()+"卖出了"+tick+"张电影票");
}else {
break;
}
}
}
}
}
package com.liming.mysynchronized;
public class MyThread01 {
public static void main(String[] args) {
MyRun mr = new MyRun();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
Thread t3 = new Thread(mr);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
Lock锁
虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5之后提供了一个新的锁对象Lock锁。
Lock获取锁、释放锁
void lock():获取锁
void unlock():释放锁
上述两个方法都需要去手动开启
Lock的构造方法
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
ReentrantLock():创建一个Reentrantlock的实例
代码演示:
package com.liming.mylock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyThread extends Thread{
static int ticket = 0;
static Lock lock = new ReentrantLock();
@Override
public void run() {
while (true){
lock.lock(); //上锁
try {
if (ticket == 100){
break;
}else {
Thread.sleep(10);
ticket++;
System.out.println(getName()+"卖了第"+ticket+"张票");
}
}catch (Exception e){
e.printStackTrace();
} finally {
lock.unlock();//释放锁
}
}
}
}
package com.liming.mylock;
public class ThreadDemo {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
死锁
线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行
什么情况下会产生死锁
1、资源有限
2、锁的同步嵌套
代码演示:
package com.liming.mysynchronized;
public class MyThread02 extends Thread {
static Object objA = new Object();
static Object objB = new Object();
@Override
public void run() {
while (true) {
if ("线程A".equals(getName())) {
synchronized (objA) {
System.out.println("线程A拿到了A锁,准备拿B锁");
synchronized (objB) {
System.out.println("线程A拿到了B锁,顺利执行完一轮");
}
}
} else if ("线程B".equals(getName())) {
if ("线程B".equals(getName())) {
synchronized (objB) {
System.out.println("线程B拿到了B锁,准备拿A锁");
synchronized (objA) {
System.out.println("线程B拿到了A锁,顺利执行完一轮");
}
}
}
}
}
}
}
class Test{
public static void main(String[] args) {
MyThread02 t1 = new MyThread02();
MyThread02 t2 = new MyThread02();
t1.setName("线程A");
t2.setName("线程B");
t1.start();
t2.start();
}
}