1、:为什么多线程并发是不安全的?
在操作系统中,线程是不拥有资源的,进程是拥有资源的。而线程是由进程创建的,一个进程可以创建多个线程,这些线程共享着进程中的资源。多线程并发之所以会不安全,就是因为线程不拥有资源,它们共享竞争着进程的资源,这样线程并发起来不安全,一般的解决方案便用锁,保证每时每刻一个资源最多只能被一个线程拥有。所以,当线程一起并发运行时,同时对一个数据进行修改,就可能会造成数据的不一致性,看下面的例子
假设一个简单的int字段被定义和初始化:
int counter = 0;
该counter字段在两个线程A和B之间共享。假设线程A、线程B同时对counter进行计算,递增运算:
counter ++;
那么计算的结果应该是 2 。但是真实的结果却是 1 ,这是因为:线程A得到的运算结果是1,线程B的运算结果也是1,
当线程A将结果写回到内存中的 count 后,线程B也将结果写回到内存中去,这就会把线程A的计算结果给覆盖了。
2、多线程并发不安全的原因已经知道,那么针对这个种情况,java中有两种解决思路:
给共享的资源加把锁,保证每个资源变量每时每刻至多被一个线程占用。
让线程也拥有资源,不用去共享进程中的资源。
3、如何实现多线程高并发安全?:
- 多实例、或者是多副本(ThreadLocal):ThreadLocal可以为每个线程的维护一个私有的本地变量;
- 使用锁机制 synchronize、lock方式:为资源加锁;
- 使用 java.util.concurrent 下面的类库:有JDK提供的线程安全的集合类
下面介绍方式二,使用锁机制的示例
继承Thread类,使用同步代码块
package com.exerse;
/*
*使用继承Thread类创建多线程
*使用同步代码块保证多线程安全
* synchronized(同步监视器){
* //需要同步的代码
* }
* 同步监视器,俗称锁,任何一个类的对象都可以充当锁,但要求必须要共同用一把锁
* 比如使用继承Thread类时就创建了三个对象,不是共用一把锁,不能采用this(当前对象),只能用
* MyThread.class(当前类的class)或
* private static Object obj=new Object();
*
*/
public class ThreadTest {
public static void main(String[] args) {
MyThread myThread1=new MyThread();//创建了三个对象
MyThread myThread2=new MyThread();
MyThread myThread3=new MyThread();
myThread1.setName("窗口A");
myThread2.setName("窗口B");
myThread3.setName("窗口C");
myThread1.start();
myThread2.start();
myThread3.start();
}
}
class MyThread extends Thread{
private static int ticket=100;//使用static作为3个对象共享一百张票
@Override
public void run() {
while (true){
synchronized (MyThread.class){
if(ticket>0){
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖票,票号"+ticket);
ticket--;
}else{
break;
}
}
}
}
}
继承Thread类使用同步方法
package com.exerse;
/*
*使用继承Thread类创建多线程
*使用同步方法保证多线程安全
* 1 同步方法仍然涉及到同步监视器(锁),只是不需要我们显示的声明、
* 2 非静态的同步方法,同步监视器为:this
* 静态的同步方法,同步监视器为:当前类本身
*/
public class ThreadTest1 {
public static void main(String[] args) {
MyThread1 myThread11 = new MyThread1();
MyThread1 myThread12 = new MyThread1();
MyThread1 myThread13 = new MyThread1();
myThread11.setName("窗口A");
myThread12.setName("窗口B");
myThread13.setName("窗口C");
myThread11.start();
myThread12.start();
myThread13.start();
}
}
class MyThread1 extends Thread {
private static int ticket = 1000;//使用static作为3个对象共享一百张票
@Override
public void run() {
while (true){
show();
}
}
private static synchronized void show(){//同步监视器为,(当前类本身MyThread1.class 注意建立三个对象,不唯一,要加static(静态方法)
if (ticket>0){
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖票,票号" + ticket);
ticket--;
}
}
}
使用实现Runnable接口同步代码块
package com.exerse;
/*
*实现 Runnable 接口创建多线程
* 使用同步代码块保证多线程安全
* synchronized(同步监视器){
* //需要同步的代码
* }
* 同步监视器,俗称锁,任何一个类的对象都可以充当锁,但要求必须要共同用一把锁,此时
* 借用this。
*
*/
public class RunnableTest {
public static void main(String[] args) {
Wind w = new Wind();//只创建一个对象
Thread thread1 = new Thread(w);
Thread thread2 = new Thread(w);
Thread thread3 = new Thread(w);
thread1.setName("窗口A");
thread2.setName("窗口B");
thread3.setName("窗口C");
thread1.start();
thread2.start();
thread3.start();
}
}
class Wind implements Runnable {
private int ticket = 1000;
@Override
public void run() {
while (true) {
synchronized (this){
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖票,票号" + ticket);
ticket--;
}else{
break;
}
}
}
}
}
使用实现Runnable接口,使用同步方法
package com.exerse;
/*
*实现 Runnable 接口创建多线程
*使用同步方法实现多线程安全
*/
public class RunnableTest1 {
public static void main(String[] args) {
Wind1 w = new Wind1();
Thread thread1 = new Thread(w);
Thread thread2 = new Thread(w);
Thread thread3 = new Thread(w);
thread1.setName("窗口A");
thread2.setName("窗口B");
thread3.setName("窗口C");
thread1.start();
thread3.start();
thread2.start();
}
}
class Wind1 implements Runnable {
private int ticket = 1000;
@Override
public void run() {
while (true) {
show();
}
}
private synchronized void show() {//此时非静态方法的同步监视器为this
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖票,票号" + ticket);
ticket--;
}
}
}
实现Runnable接口,通过lock锁保证多线程安全
package com.exerse;
import java.util.concurrent.locks.ReentrantLock;
/*
*实现 Runnable 接口创建多线程
* 使用 方式三手动Lock锁保证多线程安全(通过显示定义锁来实现同步)
*解决线程安全的方式三:Lock锁
* 面试:关于synchronized与lock锁区别
* 相同:二者都可以解决线程安全的问题
* 不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器
* Lock需要手动的开启同步(lock),同时结束同步也需要手动的实现(unlock())
*/
public class RunnableTest2 {
public static void main(String[] args) {
Wind2 w = new Wind2();//只创建一个对象
Thread thread1 = new Thread(w);
Thread thread2 = new Thread(w);
Thread thread3 = new Thread(w);
thread1.setName("窗口A1");
thread2.setName("窗口B1");
thread3.setName("窗口C1");
thread1.start();
thread2.start();
thread3.start();
}
}
class Wind2 implements Runnable {
private int ticket = 100;
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
// private ReentrantLock lock = new ReentrantLock();错误的每次执行run就创建一个Lock对象,必须使用同一个lock
lock.lock();
try {
if (ticket > 0) {
try{
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖票 ,票号" + ticket);
ticket--;
} else {
break;
}
} finally {
lock.unlock();
}
}
}
}