线程安全问题
线程安全问题:分析:4个线程共用了一个数据,出现了-1,-2,-3等错误的数据。
具体分析:
1、共用了一个数据
2、共享语句有多条,一个线程使用cpu,没有执行完cpu就被抢走,当再次抢到cpu的时候,直接执行后面的语句,造成了错误的发生。
解决:
在代码中使用同步代码块儿(同步锁)
解释:在某一段任务中,同一时间只允许一个线程执行任务,其他的线程即使抢到了cpu,也无法进入当前的任务区间,只有当当前的线程将任务执行完后,其他的线程才能有资格进入。
同步代码块儿的构成:
synchronized(锁(对象)){
同步的代码
}
对作为锁的对象的要求:1、必须是对象;2、必须保证被多个线程共享
可以充当锁的:1、一个普通的对象;2、当前对象的引用--this;3、类的字节码文件
同步代码块儿的特点:1、可以保证线程的安全;2、由于每次都要进行判断处理,所以降低了执行效率。
总结:什么时候使用同步代码块儿
1、多个线程共享一个数据
2、至少有两个线程
public class Demo {
Ticket ticket = new Ticket();
Thread thread1 = new Thread(ticket);
Thread thread2 = new Thread(ticket);
Thread thread3 = new Thread(ticket);
Thread thread4 = new Thread(ticket);
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
class Ticket implements Runnable{
//所有的线程共享num
int num = 20;
boolean flag = false;
/*
* 可以作为锁的条件:
* 1.必须是对象
* 2.锁必须被多个线程共享
*
* 可以作为锁的:普通的对象,this,类的字节码文件(.class)
*/
Object object = new Object();
public void run() {
while(!flag){
synchronized (object) {//同步代码块儿---实现了多个线程间的互斥
try {
Thread.sleep(100);
//制造一个延迟,相当于让当前的线程睡一会儿(临时让出cpu)
//注意:线程在哪里睡的,就会停留在哪里.
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (num > 0) {
System.out.println(Thread.currentThread().getName()+" "+ --num);
}else {
flag = true;
}
}
}
}
}
代码上,普通方法上,静态方法上实现同步(加锁)
/*
实例:
两个人同时向银行账户存钱
两个人相当于两个线程
操作的是一个数据
*/
public class Demo {
public static void main(String[] args) {
//创建任务对象
CunQian cunQian = new CunQian();
//创建线程绑定任务
Thread thread1 = new Thread(cunQian);
Thread thread2 = new Thread(cunQian);
//开始存钱
thread1.start();
thread2.start();
}
}
class Bank{
int sum;
//使用同步代码块儿实现同步
public void addMoney(int num) {
synchronized (this) {
sum+=num;
System.out.println(sum);
}
}
//同步函数---将函数直接变成同步的
//非静态的同步函数
/*
* 默认在synchronized后面有锁,这把锁默认是this
*/
public synchronized void addMoney(int num) {
sum+=num;
System.out.println(sum);
}
//静态的同步函数
/*
* 默认在synchronized后面有锁,这把锁默认是当前类的字节码文件
*/
public synchronized static void addMoney1(int num) {
//sum+=num;
//System.out.println(sum);
}
}
class CunQian implements Runnable{
Bank bank = new Bank();
public void run() {
for(int i=0;i<4;i++){
bank.addMoney(100);
}
}
}
理解synchronized关键字
1、synchronized关键字的作用域有二种:
1)是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法;
2)是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。
2、除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,
表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){/区块/},它的作用域是当前对象;
3、synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){}
在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法;
单例多线程下的安全问题
//饿汉式
//由于共享的代码只有一行,不会发生线程安全
class SingleInstance{
private final static SingleInstance singleInstance = new SingleInstance();
private SingleInstance() {
}
public static SingleInstance getInstance() {
return singleInstance;
}
}
//懒汉式
class SingleInstance{
private static SingleInstance singleInstance;
private SingleInstance(){
}
public static SingleInstance getInstance() {
if(singleInstance == null) {
//目的:尽量减少线程安全代码的判断次数,提高效率
synchronized (SingleInstance.class) {
if(singleInstance == null)
//保证只创建一个对象
singleInstance = new SingleInstance();
}
}
return singleInstance;
}
}
线程之间的协作
/*
实例:
打印机打印----不断输入不断输出
*/
//分析:
/*
我们可以将数据抽成一个类,类内包含数据的准备工作
要有两个线程,一个输入线程一个输出线程。任务区,要实现操作一份数据的输入,输出。
因为两个线程要操作同一份的数据,所以我们要考虑线程安全问题
使用唤醒等待机制---notify(),notifyAll(),wait()
wait():让当前的线程就地等待,放入一个池子(线程池),失去了抢cpu的能力,等待被唤醒(锁相当于给当前的线程做了一个标记)
notify():让当前的线程从等待状态唤醒,相当于从池子中取出线程.(唤醒的是同一把锁下的任意一个线程)
notifyAll():唤醒的是同一把锁下的所有线程
注意:我们只能在锁内或者说是同步的情况下使用这三个方法,否则会有InterruptedException异常
*/
class Des{
String name;
String sex;
boolean flag;//控制唤醒和等待之间的切换
//给输入准备数据
public synchronized void setData(String name,String sex){
if (flag == true) {//当flag值为true的时候,让当前的线程处于等待
try {
//当执行这行代码的时候,这里对应的是哪个线程,就操作的是哪个线程
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name;
this.sex = sex;
//更换flag的值
flag = !flag;
//唤醒输出线程,如果输出线程现在本身是活跃状态,即我们没有线程可唤醒,称为空唤醒,程序允许空唤醒.
//原理:唤醒同一把锁下的任意一个线程.
notify();
}
//给输出准备数据
public synchronized void getData() {
if (flag == false) {//当flag值为false的时候,让当前的线程处于等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}//让输出线程等待
}
System.out.println("姓名:"+name+" 性别:"+sex);
flag = !flag;
//唤醒输入线程
notify();
}
}
//建立输入任务,输出任务
class Input implements Runnable{
Des des;
public Input(Des des) {
super();
this.des = des;
}
public void run() {
int i=0;
while (true) {
/*
* 需要给输入任务和输出任务同时加一把锁,保证两个任务之间是同步的
* 给两个任务加一把锁:可以是des或者Object.class
* 分析:
* 不建议使用Object.class:由于Object的使用范围太大,可能造成不必要的错误.
* 使用des最合适,因为他只被当前的两个任务共享.
*
*注意:对于当前的情况只给一个线程加锁,无法实现两个线程的同步.
*/
if (i==1) {
des.setData("特没谱", "男");
}else {
des.setData("小晋三", "女");
}
i=(i+1)%2;
}
}
}
class Output implements Runnable{
Des des;
public Output(Des des) {
super();
this.des = des;
}
public void run() {
while (true) {
des.getData();
}
}
}
生产者与消费者
单生产者与消费者
public class Demo {
public static void main(String[] args) {
//准备数据
Product product = new Product();
//准备任务
Producer producer = new Producer(product);
Consumer consumer = new Consumer(product);
//准备线程
Thread proThread = new Thread(producer);
Thread conThread = new Thread(consumer);
//开启线程
proThread.start();
conThread.start();
}
}
//创建产品
class Product{
String name;//产品的名字
double price;//产品的价格
int count;//生产的产品数量
//标识
boolean flag = false;
//准备生产
public synchronized void setProduce(String name,double price){
if (flag == true) {
try {
wait();//让生产线程等待
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
this.name = name;
this.price = price;
System.out.println(Thread.currentThread().getName()+" 生产了:"+this.name+" 产品的数量:"+this.count+" 价格:"+this.price);
count++;
flag = ! flag;
notify();//唤醒消费线程
}
//准备消费
public synchronized void getConsume() {
if (flag == false) {
try {
wait();//让消费线程等待
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+" 消费了:"+this.name+" 产品的数量:"+this.count+" 价格:"+this.price);
count--;
flag = ! flag;
notify();//唤醒生产线程
}
}
//创建生产任务
class Producer implements Runnable{
Product product;
public Producer(Product product) {
super();
this.product = product;
}
public void run() {
while (true) {
product.setProduce("bingbing", 10);
}
}
}
//创建消费任务
class Consumer implements Runnable{
Product product;
public Consumer(Product product) {
super();
this.product = product;
}
public void run() {
while (true) {
product.getConsume();
}
}
}
多消费者多生产者
/*
当我们在上一个Demo中直接再加上两个线程的时候,会发生一些安全问题:
错误描述:
当有两个生产线程,两个消费线程同时存在的时候,有可能出现生产一次,消费多次或者生产多次消费一次的情况.
原因:当线程被重新唤醒之后,没有判断标记,直接执行了下面的代码
解决办法:将标记处的if改成while
问题描述:继续运行程序,会出现死锁的情况(4个线程同时处于等待状态)
原因:唤醒的是本方的线程,最后导致所有的线程都处于等待状态.
解决办法:将notify改成notifyAll.保证将对方的线程唤醒
*/
public class Demo {
public static void main(String[] args) {
//准备数据
Product product = new Product();
//准备任务
Producer producer = new Producer(product);
Consumer consumer = new Consumer(product);
//准备线程
Thread t0 = new Thread(producer);
Thread t1 = new Thread(producer);
Thread t2 = new Thread(consumer);
Thread t3 = new Thread(consumer);
//开启线程
t0.start();
t1.start();
t2.start();
t3.start();
}
}
//创建产品
class Product{
String name;//产品的名字
double price;//产品的价格
int count;//生产的产品数量
//标识
boolean flag = false;
//准备生产
public synchronized void setProduce(String name,double price){
while (flag == true) {
try {
wait();//让生产线程等待
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
this.name = name;
this.price = price;
System.out.println(Thread.currentThread().getName()+" 生产了:"+this.name+" 产品的数量:"+this.count+" 价格:"+this.price);
count++;
flag = ! flag;
//notify();//唤醒消费线程
notifyAll();
}
//准备消费
public synchronized void getConsume() {
while (flag == false) {
try {
wait();//让消费线程等待
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+" 消费了:"+this.name+" 产品的数量:"+this.count+" 价格:"+this.price);
count--;
flag = ! flag;
//notify();//唤醒生产线程
notifyAll();
}
}
//创建生产任务
class Producer implements Runnable{
Product product;
public Producer(Product product) {
super();
this.product = product;
}
public void run() {
while (true) {
product.setProduce("bingbing", 10);
}
}
}
//创建消费任务
class Consumer implements Runnable{
Product product;
public Consumer(Product product) {
super();
this.product = product;
}
public void run() {
while (true) {
product.getConsume();
}
}
}
死锁的情况
1、所有的线程处于等待状态 ---上个Demo中把notifyAll()方法改成notify()方法会出现这种情况
2、锁之间进行嵌套调用
//了解
class MyModel1 {
public synchronized void action1(MyModel2 myModel2) {
System.out.println("MyModel1---action1");
myModel2.action2();
}
public synchronized void action2() {
System.out.println("MyModel1---action2");
}
}
class MyModel2 {
public synchronized void action1(MyModel1 myModel1) {
System.out.println("MyModel2---action1");
myModel1.action2();
}
public synchronized void action2() {
System.out.println("MyModel2---action2");
}
}
class DeadAction implements Runnable {
private MyModel1 myModel1;
private MyModel2 myModel2;
public DeadAction(MyModel1 myModel1, MyModel2 myModel2) {
this.myModel1 = myModel1;
this.myModel2 = myModel2;
new Thread(this).start();
this.myModel1.action1(this.myModel2);
}
@Override
public void run() {
this.myModel2.action1(this.myModel1);
}
}
//入口
public class MyDemo {
public static void main(String[] args) {
new DeadAction(new MyModel1(),new MyModel2());
}
}
lock锁
比较synchronized和Lock
1、synchronized:从jdk1.0就开始使用的同步方法-称为隐式同步
synchronized(锁对象){ //获取锁 我们将锁还可以称为锁旗舰或者监听器
同步的代码
}//释放锁
2、Lock:从jdk1.5开始使用的同步方法-称为显示同步
原理:Lock本身是接口,要通过他的子类创建对象干活儿
使用过程:
首先调用lock()方法获取锁
进行同步的代码块儿
使用unlock()方法释放锁
使用的场景:
当进行多生产者多消费者的功能时,使用Lock,其他的一般都使用synchronized
使用效率上:Lock高于synchronized
package com.qf;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo {
public static void main(String[] args) {
//准备数据
Product product = new Product();
//准备任务
Producer producer = new Producer(product);
Consumer consumer = new Consumer(product);
//准备线程
Thread t0 = new Thread(producer);
Thread t1 = new Thread(producer);
Thread t2 = new Thread(consumer);
Thread t3 = new Thread(consumer);
//开启线程
t0.start();
t1.start();
t2.start();
t3.start();
}
}
//创建产品
class Product{
String name;//产品的名字
double price;//产品的价格
int count;//生产的产品数量
//标识
boolean flag = false;
//创建Lock对象
Lock lock = new ReentrantLock();
//创建用于生产线程的Condition对象
Condition proCon = lock.newCondition();
//创建用于消费线程的Condition对象
Condition conCon = lock.newCondition();
//准备生产
public void setProduce(String name,double price){
try {
lock.lock();//获取锁
while (flag == true) {
try {
proCon.await();//让生产线程等待
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
this.name = name;
this.price = price;
System.out.println(Thread.currentThread().getName()+" 生产了:"+this.name+" 产品的数量:"+this.count+" 价格:"+this.price);
count++;
flag = ! flag;
conCon.signal();//唤醒消费线程
} finally {//必须实现的代码,用于资源的释放
lock.unlock();//释放锁
}
}
//准备消费
public void getConsume() {
try{
lock.lock();
while (flag == false) {
try {
conCon.await();//让消费线程等待
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+" 消费了:"+this.name+" 产品的数量:"+this.count+" 价格:"+this.price);
count--;
//唤醒生产线程
flag = ! flag;
proCon.signal();
}finally {
lock.unlock();
}
}
}
//创建生产任务
class Producer implements Runnable{
Product product;
public Producer(Product product) {
super();
this.product = product;
}
public void run() {
while (true) {
product.setProduce("bingbing", 10);
}
}
}
//创建消费任务
class Consumer implements Runnable{
Product product;
public Consumer(Product product) {
super();
this.product = product;
}
public void run() {
while (true) {
product.getConsume();
}
}
}
让线程停止的方法:
1、通过建立一个标识的(flag)的方法
public class Demo {
public static void main(String[] args) {
Test1 test1 = new Test1();
Thread thread1 = new Thread(test1);
thread1.start();
//让主线程睡一会儿
try{
Thread.sleep(100);
}catch (Exception e) {
}
int i=0;
while (true) {
if (++i == 10) {
test1.flag = false;//当主线程运行到某个阶段的时候,让flag值变成false,来间接的控制子线程的结束
break;//目的让主线程结束
}
}
}
}
class Test1 implements Runnable{
boolean flag = true;
public void run() {
while (flag) {
System.out.println(Thread.currentThread().getName()+"很开心");
}
}
}
2、通过调用stop---已经被弃用,因为他有固有的安全问题
3、调用interrupt()方法将一个处于等待状态的线程停止
public class Demo {
public static void main(String[] args) {
Test1 test1 = new Test1();
Thread thread1 = new Thread(test1);
thread1.start();
//让主线程睡一会儿
try{
Thread.sleep(100);
}catch (Exception e) {
}
int i=0;
while (true) {
if (++i == 10) {
thread1.interrupt();//当主线程运行到某个阶段的时候,让子线程调用interrupt()方法
//,来触发InterruptedException异常,从而将flag的值 设置成false
break;//目的让主线程结束
}
}
}
}
class Test1 implements Runnable{
boolean flag = true;
public synchronized void run() {
while (flag) {
try {
wait();
} catch (InterruptedException e) {
flag =false;
}
System.out.println(Thread.currentThread().getName()+"很开心");
}
}
}
守护线程
守护线程:相当于是后台线程,依赖于前台线程,会随着前台线程的结束而结束,不管他当前处于什么状态。
典型的守护线程:垃圾回收线程
守护线程必须在start之前执行
public class Demo{
public static void main(String[] args) throws InterruptedException {
Text text = new Text();
Thread t = new Thread(text);
t.setDaemon(true);
//将thread1变成守护线程
//当线程调用setDaemon方法,并将参数设置成true时,当前线程就变成了守护线程
//注意:这行代码必须在start之前执行
t.start();
Thread.sleep(100);
}
}
class Text implements Runnable {
@Override
public void run() {
while(true){
System.out.println("ha ha ha");
}
}
}
join()方法
原理:线程一旦调用了join方法,优先级会高于主线程,主线程会等当前的线程执行完任务再去执行自己的任务。
注意点:这个线程优先级只是比主线程高,对其他的线程没有影响。
join方法必须在线程执行了start方法之后执行
public class Demo{
public static void main(String[] args) {
Text text = new Text();
Thread t1 = new Thread(text);
Thread t2 = new Thread(text);
t1.start();
t2.start();
try {
t2.join();
//注意:join方法必须在线程执行了start方法之后执行
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
int i = 0;
while(++i < 10) {
System.out.println(Thread.currentThread().getName()+" ha ha ha");
}
}
}
class Text implements Runnable {
@Override
public void run() {
int i = 0;
while(++i < 10){
System.out.println(Thread.currentThread().getName()+" ha ha ha");
}
}
}