【
线程的同步
线程的安全问题:当多个线程访问共享数据的时候,就有可能出现线程安全的问题。
产生的原因: 如果当一个线程正在访问一个数据的时候,另一个线程也参与了进来,那么就会出现线程安全问题。
解决思路:我们可以将操作共享数据的代码封装起来,当有一个线程正在执行这部分代码的时候,其他线程不能参加执行
在Java中可以通过同步代码块和同步方法来实现这个操作
语法:
1.synchronized(对象){//同步锁,通常会使用this作为同步锁
//被同步的代码块
}
2.同步方法:直接用synchronized修饰方法
同步方法使用的同步锁是默认的
1)非静态方法使用的同步锁是当前类的对象(this)
2)静态方法使用的同步锁是当前类的Class对象(类名.class)
问:同步处理能够有效的解决多线程的数据安全问题,那么是不是所有的方法或代码块都进行同步处理?
答:不能这么做,因为使用同步处理虽然能够解决多线程的数据安全问题,但是同时会降低代码的执行效率
答:当多线程操作共享数据的时候,需要进行同步处理
关键词:多线程、共享数据
注意事项:不要乱用同步处理
】
【1:
public classListThread extends Thread {
private Vector<Integer> list;
public ListThread(Vector<Integer>list){
this.list = list;
}
public void run(){
for(int i=1;i<=10000;i++){
list.add(i);
}
}
}
public static void main(String[] args){
Vector<Integer> list =new Vector<Integer>(); //共享资源
ListThread t1 = newListThread(list); //构造线程对象,传入list
ListThread t2 = newListThread(list); //t1和t2共享一个list
t1.start();
t2.start();
try {
t1.join();
t2.join();//等待子线程执行结束
} catch (InterruptedExceptione) {
// TODOAuto-generated catch block
e.printStackTrace();
}
System.out.println(list.size());
}
2:
】
线程安全问题多个线程对同一资源进行争用时产生的问题(关于线程同步问题)
异步 多线程,你走你的,我走我的
同步 多线程执行到某一环节,要进行协调,让某一线程先执行,另一线程后执行,避免对同一资源的争抢
1.1 同步对象锁synchronized
【
public classDressingRoom {
public void use(){
System.out.println(Thread.currentThread().getName()+"进入试衣间");
try {
Thread.sleep(1000);
} catch (InterruptedExceptione) {
// TODOAuto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"离开试衣间");
}
}
public classCustomerRunnable implements Runnable {
private DressingRoom room;
public CustomerRunnable(DressingRoomroom) {
this.room = room;
}
@Override
public void run() {
// TODO Auto-generated methodstub
for(int i=0;i<10;i++){
synchronized (room) {//获取试衣间的锁,其他顾客、线程不能再进入
room.use();//使用试衣间
}//释放锁,其他顾客可以进入
try {
Thread.sleep(1000);//挑选、休息
} catch(InterruptedException e) {
// TODOAuto-generated catch block
e.printStackTrace();
}
}
}
}
DressingRoom room =new DressingRoom();
CustomerRunnable r = newCustomerRunnable(room);
Thread zhangsan = newThread(r);
Thread lisi = new Thread(r);
zhangsan.setName("张三");
lisi.setName("李四");
zhangsan.start();
lisi.start();
】
1.2 线程同步
1.2.1 多线程安全问题
当run()方法体内的代码操作到了成员变量(共享数据)时,就可能会出现多线程安全问题
产生的原因
当一个线程执行操作共享数据的相关的代码块时,其他线程也参与了运算,就会导致线程安全问题的产生
解决思路
将操作共享数据的所有代码封装起来,当有线程在执行这些代码的时候,其他线程不能参与运算
1.2.2 同步的手段
同步代码块
synchronized(锁对象){ // 需要同步的代码 }
同步方法
publicsynchronized void 方法名(参数列表){ // 方法体 }
Lock(JDK1.5新特性)
1.2.3 同步的好处和弊端
好处:解决了线程的安全问题
弊端:降低了效率
1.2.4 同步的前提和准则
必须是多线程并且使用了同一把锁
切记:不要乱用同步处理
同步的准则
1、使用代码保持剪短,把不随线程变化的预处理和后处理移出同步块
2、不要阻塞,比如InputStream.read()
3、在持有锁额时候,不要对其他对象调用同步方法
1.2.5 验证同步锁
静态方法默认同步锁是当前类的类对象(Class对象)
1.2.6 同步对象锁
【
public classDressingRoom {
public void use(){
synchronized (this) {
System.out.println(Thread.currentThread().getName()+"进入试衣间");
try {
Thread.sleep(1000);
} catch (InterruptedExceptione) {
// TODOAuto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"离开试衣间");
}
}
}
public classCustomerRunnable implements Runnable {
private DressingRoom room;
public CustomerRunnable(DressingRoomroom) {
this.room = room;
}
@Override
public void run() {
for (int i = 0; i < 10;i++) {
room.use();// 使用试衣间
try {
Thread.sleep(1000);//挑选、休息
} catch(InterruptedException e) {
// TODOAuto-generated catch block
e.printStackTrace();
}
}
}
}
DressingRoom room =new DressingRoom();
CustomerRunnable r = newCustomerRunnable(room);
Thread zhangsan = newThread(r);
Thread lisi = new Thread(r);
zhangsan.setName("张三");
lisi.setName("李四");
zhangsan.start();
lisi.start();
】
同步静态类:
【
public class Singleton {
privatestatic Singleton s =null;
privateSingleton(){
System.out.println("构造方法");
}
publicstatic Singleton getInstance(){
synchronized(Singleton.class) {
if(s==null){
try{
Thread.sleep(3000);//将问题极端化
}catch (InterruptedException e) {
//TODO Auto-generated catch block
e.printStackTrace();
}
s= new Singleton();
}
}
returns;
}
}
public class SingleThread extends Thread {
public voidrun(){
Singleton.getInstance();
}
}
SingleThread t1 = new SingleThread();
SingleThreadt2 = new SingleThread();
t1.start();
t2.start();
】
同步类:【
//即使张三和王五,对他们来说图纸是同一张,描述类对象是同一个,所以是在同一个锁上进行同步的
synchronized(Microphone.class) { //在话筒的描述类对象进行同步
microphone.speakWith();
}
】
同步对象锁:
synchronized(锁对象){
语句
}
这样我称为: 语句在锁对象上进行了同步,在多线程中,多个在同一锁对向上同步的语句快,同时只能有一份进行执行;而且执行是原子的,一定要执行完才能结束,不能切换到具有相同对象锁的语句块去执行(有例外)
synchronized(this){
语句
}
【
public classMicrophone {
public void speakWith(){
synchronized(this){//在当前话筒对象上进行同步,一个线程抢到当前话筒后,其他线程就不能再抢
System.out.println(Thread.currentThread().getName()+"拿起话筒");
try {
Thread.sleep(1000);
} catch(InterruptedException e) {
}
System.out.println(Thread.currentThread().getName()+"放下话筒");
}//将当前话筒对象释放掉,其他线程可以抢到它
}
}
】
语句在当前对象上进行了同步,如果多个线程都在同一个当前对象上同步,那么同一时间只能有一个线程执行这个同步的代码块
1.2.7 同步方法
【
public classMicrophone {
public synchronized void speakWith(){
System.out.println(Thread.currentThread().getName()+"拿起话筒");
try {
Thread.sleep(1000);
} catch (InterruptedExceptione) {
// TODOAuto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"放下话筒");
}
}
】
同步方法:
[修饰符] synchronized返回类型方法名(参数列表){
语句
}
相当于:
[修饰符] 返回类型 方法名(参数列表){
synchronized(this){
语句
}
}
在描述类对象上进行同步,在图纸上进行同步
synchronized(类.class){
语句
}
所有在类.class上进行同步的代码块都会受影响,根本原因是描述类对象只有一份拷贝,所以这些线程都是在同一个对象锁上同步,都会受影响。
[修饰符] staticsynchronized 返回类型方法名(参数){
语句
}
相当于:
[修饰符] static 返回类型 方法名(参数列表){
synchronized(类名.class){
语句
}
}
1.3 同步引起的死锁
【
多线程的死锁问题:线程卡死的情况
死锁产生的情况有很多,我们这里先例举一个比较常见的情况,就是同步锁的嵌套
如果因为同步锁的嵌套使用导致死锁,那么一般就是同步锁的顺序不一致,只要保证锁的顺序是一样的,就可以避免死锁问题】
【
public class DinnerRunnableimplements Runnable {
private Object knife;
private Object fork;
public DinnerRunnable(Object knife,Object fork) {
this.knife = knife;
this.fork = fork;
}
@Override
public void run() {
synchronized (knife) {
System.out.println(Thread.currentThread().getName()+"拿到刀");
try {
Thread.sleep(100);//问题极端化
} catch(InterruptedException e) {
// TODOAuto-generated catch block
e.printStackTrace();
}
synchronized (fork){
System.out.println(Thread.currentThread().getName()+"拿到叉");
System.out.println(Thread.currentThread().getName()+"吃");
}
}
}
}
public classDinnerRunnable2 implements Runnable {
private Object knife;
private Object fork;
public DinnerRunnable2(Object knife,Object fork) {
this.knife = knife;
this.fork = fork;
}
@Override
public void run() {
synchronized (fork) {
System.out.println(Thread.currentThread().getName()+"拿到叉");
try {
Thread.sleep(100);//问题极端化
} catch(InterruptedException e) {
// TODOAuto-generated catch block
e.printStackTrace();
}
synchronized (knife){
System.out.println(Thread.currentThread().getName()+"拿到刀");
System.out.println(Thread.currentThread().getName()+"吃");
}
}
}
}
Object knife = newObject();
Object fork = new Object();
Thread zhangsan = newThread(new DinnerRunnable(knife, fork));
Thread lisi = new Thread(newDinnerRunnable2(knife, fork));
zhangsan.setName("张三");
lisi.setName("李四");
zhangsan.start();
lisi.start();
】
两个对象锁A、B,一个线程拿到A等待B,另一个拿了B等待A,形成死锁
解决方式,让这些线程获取对象锁的顺序一致