多线程
线程安全问题
- 说明:线程安全问题其实就是由多个线程同时处理共享资源所导致的,资源竞争问题
- 代码说明
- 创建main主线程开启多个新线程
package duox.xc;
public class Dxc1 {
public static void main(String[] args) {
Doc01 doc01 = new Doc01();
Thread th1 = new Thread(doc01);
Thread th2 = new Thread(doc01);
Thread th3 = new Thread(doc01);
th1.start();
th2.start();
th3.start();
}
}
- 创建新线程
package duox.xc;
public class Doc01 implements Runnable{
private int ticket=100;
@Override
public void run() {
while (true){
if(ticket>0) {
System.out.println(Thread.currentThread().getName() + "正在买第" + ticket + "张票");
ticket--;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
- 代码的部分运行结果
Thread-0正在买第100张票
Thread-2正在买第100张票
Thread-1正在买第100张票
Thread-1正在买第97张票
Thread-0正在买第97张票
Thread-2正在买第97张票
Thread-0正在买第94张票
Thread-2正在买第94张票
Thread-1正在买第94张票
Thread-2正在买第91张票
Thread-0正在买第91张票
Thread-1正在买第89张票
Thread-2正在买第88张票
Thread-0正在买第88张票
Thread-1正在买第86张票
Thread-2正在买第85张票
Thread-0正在买第85张票
Thread-1正在买第83张票
Thread-2正在买第82张票
解决线程安全问题
- 要想解决线程安全问题,就必须得保证处理共享资源的代码在任意时刻只能有一个线程访问这就要使用线程同步技术。
- 实现线程同步技术有三种方法:1.同步代码块。2.同步方法。3.锁机制。
1.同步代码块
-
格式:synchronized(lock锁对象){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
-
注意:lock锁对象可以是任意类型的对象,但多个线程共享的锁对象必须是相同的。锁对象的创建代码不能放到run()方法中,否则每个线程运行到run()方法都会创建一个新对象,这样每个线程都会有一个不同的锁。
-
创建main主线程
package duox.xc;
public class Dxc1 {
public static void main(String[] args) {
Doc01 doc01 = new Doc01();
Thread th1 = new Thread(doc01);
Thread th2 = new Thread(doc01);
Thread th3 = new Thread(doc01);
th1.start();
th2.start();
th3.start();
}
}
- 静态代码块实现线程同步
package duox.xc;
public class Doc01 implements Runnable{
//创建一个锁对象,必须放在run方法外面
Object obj=new Object();
private int ticket=100;
@Override
public void run() {
while (true){
//静态代码块
synchronized (obj){
if(ticket>0) {
System.out.println(Thread.currentThread().getName() + "正在买第" + ticket + "张票");
ticket--;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
- 代码的部分运行结果
Thread-0正在买第100张票
Thread-0正在买第99张票
Thread-0正在买第98张票
Thread-0正在买第97张票
Thread-0正在买第96张票
Thread-0正在买第95张票
Thread-0正在买第94张票
Thread-0正在买第93张票
Thread-0正在买第92张票
Thread-0正在买第91张票
Thread-0正在买第90张票
Thread-0正在买第89张票
Thread-0正在买第88张票
Thread-0正在买第87张票
Thread-0正在买第86张票
- 同步代码块的运行原理:①当线程执行同步代码块时,首先会检查lock锁对象的标志位;②默认情况下标志位为1,此时线程会执行Synchronized同步代码块,同时将锁对象的标志位置变为0 ;③当一个新的线程执行到这段同步代码块时,由于锁对象的标志位为0,新线程会发生阻塞,等待当前线程执行完同步代码块后;④锁对象的标志位被置变为1,新线程才能进入同步代码块执行其中的代码,这样循环往复,直到共享资源理完为止。
2.同步方法
-
步骤:1.把访问了共享数据的代码抽取出来,放到一个方法中。2.在方法上添加synchronized修怖符。
-
格式:修饰符 synchronized 返回值类型 方法名(参数列表){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
-
注意:1.同步方法也有锁,它的锁就是当前调用该方法的对象,就是this指向的对象 。2.Java中静态方法的锁是该方法所在类的class对象,该对象可以直接类名.class的方式获取。3.同步代码块和同步方法解决多线程问题有好处也有弊端。同步解决了多个线程同时访问共享数据时的线程安全问题,只要加上同一个锁,在同一时间内只能有一条线程执行,但是线程在执行同步代码时每次都会判断锁的状态,非常消耗资源,效率较低。
-
创建main主线程
package duox.xc;
public class Doc03 {
public static void main(String[] args) {
Doc003 doc003 = new Doc003();
Thread th1 = new Thread(doc003);
Thread th2 = new Thread(doc003);
Thread th3 = new Thread(doc003);
th1.start();
th2.start();
th3.start();
}
}
- 创建同步方法
package duox.xc;
public class Doc003 implements Runnable{
private int ticket=100;
@Override
public void run() {
while (true){
payTicket();
}
}
//创建同步方法
public synchronized void payTicket(){
if(ticket>0) {
System.out.println(Thread.currentThread().getName() + "正在买第" + ticket + "张票");
ticket--;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
- 代码的部分运行结果
Thread-1正在买第100张票
Thread-1正在买第99张票
Thread-1正在买第98张票
Thread-1正在买第97张票
Thread-1正在买第96张票
Thread-1正在买第95张票
Thread-1正在买第94张票
Thread-1正在买第93张票
Thread-1正在买第92张票
Thread-1正在买第91张票
Thread-1正在买第90张票
Thread-1正在买第89张票
Thread-1正在买第88张票
3.锁机制(lock锁)
-
使用步骤:1.在成员位置创建一个ReentrantLock对象。2.在可能会出现安全问题的代码前调用Lock接口中的方法Lock获职锁。3.在可能会出现安全问题的代码后调用Lock接口中的方法unLock释放锁。
-
使用同步锁的优势:synchronized同步代码块和同步方法使用一种封闭式的锁机制,使用起来非常简单,也能够解决线程同步过程中出现的线程安全问题,但也有一些限制,例如它无法中断一个正在等候获得锁的线程,也无法通过轮询得到锁,如果不想等下去,也就没法得到锁。解决:从JDK 5开始,Java增加了一个功能更强大的Lock锁。Lock锁与synchronized隐式锁在功能上基本相同,其最大的优势在于Lock锁可以让某个线程在持续获取同步锁失败后返回,不再继续等待,另外Lock锁在使用时也更加灵活。
-
注意:ReentrantLock类是Lock锁接口的实现类,也是常用的同步锁,在该同步锁中除了lock()方法和unlock()方法外,还提供了一些其他同步锁操作的方法,例如tryLock()方法可以判断某个线程锁是否可用。在使用Lock同步锁时,可以根据需要在不同代码位置灵活的上锁和解锁,为了保证所有情况下都能正常解锁以确保其他线程可以执行,通常情况下会在finally{}代码块中调用unlock()方法来解锁。
-
创建main主线程
package duox.xc;
public class Doc4 {
public static void main(String[] args) {
Doc04 dox = new Doc04();
Thread th1 = new Thread(dox);
Thread th2 = new Thread(dox);
Thread th3 = new Thread(dox);
th1.start();
th2.start();
th3.start();
}
}
- 创建锁对象
package duox.xc;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Doc04 implements Runnable {
private int ticket=100;
//创建ReentrantLock锁对象
Lock lo=new ReentrantLock();
@Override
public void run() {
while (true){
//获取锁·
lo.lock();
if(ticket>0) {
System.out.println(Thread.currentThread().getName() + "正在买第" + ticket + "张票");
ticket--;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//释放锁
lo.unlock();
}
}
}
- 代码的部分运行结果
Thread-0正在买第100张票
Thread-0正在买第99张票
Thread-0正在买第98张票
Thread-0正在买第97张票
Thread-0正在买第96张票
Thread-0正在买第95张票
Thread-0正在买第94张票
Thread-0正在买第93张票
Thread-0正在买第92张票
Thread-0正在买第91张票
Thread-1正在买第90张票
Thread-1正在买第89张票
Thread-1正在买第88张票
Thread-1正在买第87张票
Thread-1正在买第86张票
Thread-1正在买第85张票