Java- - - 多线程学习笔记(二)
线程安全问题的出现
当一个进程中的多个线程共享资源或数据的时候,就会出现安全隐患
例如,三个售票窗口同时售票,如果没有进行线程安全的处理,则会出现重票,错票等线程安全问题
package com.fff;
//实现Runnable接口
class TicketWindow implements Runnable{
private static int ticketCount = 100;//三个线程的共享资源,用static
//实现接口中的抽象方法
@Override
public void run() {
while(true){
if(ticketCount > 0){
try {
//使用sleep方法,使当前进程阻塞100ms,方便通过输出结果查看线程的状态
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":卖票---票号为:"+ticketCount);
ticketCount--;
}else {
break;
}
}
}
}
public class Window {
public static void main(String[] args){
//创建实现类的对象
TicketWindow ticketWindow = new TicketWindow();
//将此对象作为参数传递到Thread类的构造器中
Thread t1 = new Thread(ticketWindow);
Thread t2 = new Thread(ticketWindow);
Thread t3 = new Thread(ticketWindow);
//设置三个线程的名字
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
//通过Thread类的对象调用start()方法,启动三个线程
t1.start();
t2.start();
t3.start();
}
}
通过输出结果可发现,三个窗口也就是三个进程在切换执行的过程中,出现错票和重票的情况,这就说明当前多线程任务是不安全的。
解决线程安全问题
在Java中,通过同步机制解决线程安全问题。
方式一:同步代码块
syncchronized(同步监视器){
//需要被同步的代码
}
说明**:操作共享数据的代码即为需要被同步的代码。**
共享数据:多个线程共同操作的变量;
同步监视器:俗称“锁”,任何一个类的对象都可以充当锁。
要求:多个线程必须共用同一把锁;
package com.fff;
//实现Runnable接口
class TicketWindow implements Runnable{
private static int ticketCount = 100;//三个线程的共享资源,用static
Object obj = new Object();//任何一个类的对象都可以充当锁;
//实现接口中的抽象方法
@Override
public void run() {
while(true){
synchronized (obj){
//需要被同步的代码
if(ticketCount > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":卖票---票号为:"+ticketCount);
ticketCount--;
}else {
break;
}
}
}
}
}
public class Window {
public static void main(String[] args){
//创建实现类的对象
TicketWindow ticketWindow = new TicketWindow();
//将此对象作为参数传递到Thread类的构造器中
//一下三个线程共用一个实现类的对象,因此三个线程共用一把锁
Thread t1 = new Thread(ticketWindow);
Thread t2 = new Thread(ticketWindow);
Thread t3 = new Thread(ticketWindow);
//设置三个线程的名字
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
//通过Thread类的对象调用start()方法,启动三个线程
t1.start();
t2.start();
t3.start();
}
}
通过结果发现,没有再出现重票错票的情况,当一个线程没有执行完,哪怕是发生了阻塞,别的线程也不会执行,而是要等到当前线程执行完毕,其他的线程才会执行。
**注意:**在通过同步代码块的方式解决继承Thread类的线程安全问题时,一定要注意锁必须是唯一的。
方式二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明为同步的;
package com.fff;
//实现Runnable接口
class TicketWindow implements Runnable{
private static int ticketCount = 100;//三个线程的共享资源,用static
Object obj = new Object();//任何一个类的对象都可以充当锁;
//实现接口中的抽象方法
@Override
public void run() {
while(true){
this.show();
}
}
private synchronized void show(){//同步监视器:this
if(ticketCount > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":卖票---票号为:"+ticketCount);
ticketCount--;
}
}
}
public class Window {
public static void main(String[] args){
//创建实现类的对象
TicketWindow ticketWindow = new TicketWindow();
//将此对象作为参数传递到Thread类的构造器中
Thread t1 = new Thread(ticketWindow);
Thread t2 = new Thread(ticketWindow);
Thread t3 = new Thread(ticketWindow);
//设置三个线程的名字
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
//通过Thread类的对象调用start()方法,启动三个线程
t1.start();
t2.start();
t3.start();
}
}
同步方法仍然涉及到同步监视器,只不过不需要显示的声明。
非静态的同步方法,同步监视器是:this
静态的同步方法,同步监视器是:当前类本身
**注意:**当我们用同步方法方式解决继承Thread线程问题时,要注意,同步方法要是静态的,保证用的是同一个锁,所有的线程共用一个同步方法,同一个锁。
方式三:Lock锁
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。
ReentrantLock类实现了Lock,可以显示加锁,释放锁。
package com.fff;
import java.util.concurrent.locks.ReentrantLock;
//实现Runnable接口
class TicketWindow implements Runnable{
private static int ticketCount = 100;//三个线程的共享资源,用static
//1.实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock();
//实现接口中的抽象方法
@Override
public void run() {
while(true){
try{
//2.调用锁定方法lock()
lock.lock();
if(ticketCount > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":卖票---票号为:"+ticketCount);
ticketCount--;
}else {
break;
}
}finally {
//3.调用解锁方法unlock()
lock.unlock();
}
}
}
}
public class Window {
public static void main(String[] args){
//创建实现类的对象
TicketWindow ticketWindow = new TicketWindow();
//将此对象作为参数传递到Thread类的构造器中
Thread t1 = new Thread(ticketWindow);
Thread t2 = new Thread(ticketWindow);
Thread t3 = new Thread(ticketWindow);
//设置三个线程的名字
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
//通过Thread类的对象调用start()方法,启动三个线程
t1.start();
t2.start();
t3.start();
}
}
synchronized和Lock的区别
synchronized机制在执行完相应的同步代码之后,自动的释放同步监视器;
Lock需要手动的启动同步,同时结束同步也需要手动的实现