多线程笔记(一)

来源:互联网 发布:jquery.tooltips.js 编辑:程序博客网 时间:2024/06/14 01:53

1.1线程安全

是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。

public class MyThread extends Thread{
private int count = 5 ;
//synchronized加锁
public void run(){
count–;
System.out.println(this.currentThread().getName() + ” count = “+ count);
}

public static void main(String[] args) {    /**     * 分析:当多个线程访问myThread的run方法时,以排队的方式进行处理(这里排对是按照CPU分配的先后顺序而定的),     *      一个线程想要执行synchronized修饰的方法里的代码:     *      1 尝试获得锁     *      2 如果拿到锁,执行synchronized代码体内容;拿不到锁,这个线程就会不断的尝试获得这把锁,直到拿到为止,     *         而且是多个线程同时去竞争这把锁。(也就是会有锁竞争的问题)     */    MyThread myThread = new MyThread();    Thread t1 = new Thread(myThread,"t1");    Thread t2 = new Thread(myThread,"t2");    Thread t3 = new Thread(myThread,"t3");    Thread t4 = new Thread(myThread,"t4");    Thread t5 = new Thread(myThread,"t5");    t1.start();    t2.start();    t3.start();    t4.start();    t5.start();}

}
例如当多个线程访问,myThread的run方法时,以排队的方式进行处理(这里的排队是按照CPU分配的先后顺序而定的),一个线程先要执行synchronized修饰的方法里的代码,首先是尝试获得锁,如果拿到锁,执行synchronized代码里的内容;拿不到锁,这个线程就会不断的尝试获得这把锁,直到拿到位置,而且是多个线程同时去竞争这个锁。(也就锁竞争的问题)

1.2多个线程多个锁

多个线程多个锁:多个线程,每个线程都可以拿到自己指定的锁,分别获得锁之后,执行synchronized方法体的内容。

/**
* 关键字synchronized取得的锁都是对象锁,而不是把一段代码(方法)当做锁,
* 所以代码中哪个线程先执行synchronized关键字的方法,哪个线程就持有该方法所属对象的锁(Lock),
*
* 在静态方法上加synchronized关键字,表示锁定.class类,类一级别的锁(独占.class类)。
* @author alienware
*
*/
public class MultiThread {
private int num = 0;

/** static */public synchronized void printNum(String tag){    try {        if(tag.equals("a")){            num = 100;            System.out.println("tag a, set num over!");            Thread.sleep(1000);        } else {            num = 200;            System.out.println("tag b, set num over!");        }        System.out.println("tag " + tag + ", num = " + num);    } catch (InterruptedException e) {        e.printStackTrace();    }}//注意观察run方法输出顺序public static void main(String[] args) {    //俩个不同的对象    final MultiThread m1 = new MultiThread();    final MultiThread m2 = new MultiThread();    Thread t1 = new Thread(new Runnable() {        @Override        public void run() {            m1.printNum("a");        }    });    Thread t2 = new Thread(new Runnable() {        @Override         public void run() {            m2.printNum("b");        }    });         t1.start();    t2.start();}

}
关键字synchronized取得的锁都是对象锁,而不是把一段代码(方法)当做锁,所以实例代码中那个线程先执行synchronized关键字的方法,那个线程就持有该方法锁属对象的锁,两个对象,线程获得的就是两个不同的锁,他们互补影响。有一种情况则是相同的锁,既在静态方法中加synchronized关键字,表示锁定.class类,类一级别的锁独占.class类。

1.3对象锁的同步和异步

同步:synchronized
同步指两个或两个以上随时间变化的量在变化过程中保持一定的相对关系。同步(英语:Synchronization),指对在一个系统中所发生的事件(event)之间进行协调,在时间上出现一致性与统一化的现象。在系统中进行同步,也被称为及时(in time)、同步化的(synchronous、in sync)。
异步asynchronized
异步的概念和同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。异步双方不需要共同的时钟,也就是接收方不知道发送方什么时候发送,所以在发送的信息中就要有提示接收方开始接收的信息,如开始位,同时在结束时有停止位。
同步的目的就是为了线程的安全,对于线程安全,需要满足两个特性:原子性和可见性。事例:
/**
* 对象锁的同步和异步问题
* @author alienware
*
*/
public class MyObject {

public synchronized void method1(){    try {        System.out.println(Thread.currentThread().getName());        Thread.sleep(4000);    } catch (InterruptedException e) {        e.printStackTrace();    }}/** synchronized */public void method2(){        System.out.println(Thread.currentThread().getName());}public static void main(String[] args) {    final MyObject mo = new MyObject();    /**     * 分析:     * t1线程先持有object对象的Lock锁,t2线程可以以异步的方式调用对象中的非synchronized修饰的方法     * t1线程先持有object对象的Lock锁,t2线程如果在这个时候调用对象中的同步(synchronized)方法则需等待,也就是同步     */    Thread t1 = new Thread(new Runnable() {        @Override        public void run() {            mo.method1();        }    },"t1");    Thread t2 = new Thread(new Runnable() {        @Override        public void run() {            mo.method2();        }    },"t2");    t1.start();    t2.start();}

}
A线程先持有object对象的Lock锁,B线程如果在这个时候调用对象中的同步(synchronized)方法则需要等待,也就是同步.
A线程先持有object对象中的锁,B线程可以已异步的方式调用对象中的非synchronized修饰的方法。

1.4 synchronized关键字

synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到了一个对象的锁后,再次请求次对象是可以再次得到该对象的锁。

/**
* synchronized的重入
* @author alienware
*
*/
public class SyncDubbo1 {

public synchronized void method1(){    System.out.println("method1..");    method2();}public synchronized void method2(){    System.out.println("method2..");    method3();}public synchronized void method3(){    System.out.println("method3..");}public static void main(String[] args) {    final SyncDubbo1 sd = new SyncDubbo1();    Thread t1 = new Thread(new Runnable() {        @Override        public void run() {            sd.method1();        }    });    t1.start();}

}
/**
* synchronized的重入
* @author alienware
*
*/
public class SyncDubbo2 {

static class Main {    public int i = 10;    public synchronized void operationSup(){        try {            i--;            System.out.println("Main print i = " + i);            Thread.sleep(100);        } catch (InterruptedException e) {            e.printStackTrace();        }    }}static class Sub extends Main {    public synchronized void operationSub(){        try {            while(i > 0) {                i--;                System.out.println("Sub print i = " + i);                Thread.sleep(100);                      this.operationSup();            }        } catch (InterruptedException e) {            e.printStackTrace();        }    }}public static void main(String[] args) {    Thread t1 = new Thread(new Runnable() {        @Override        public void run() {            Sub sub = new Sub();            sub.operationSub();        }    });    t1.start();}

}
1.5 synchronized代码块

使用synchronized声明的方法在某些情况下是有弊端的。比如A线程调用同步的方法执行一个很长时间的任务,那么B线程就必须等待比较长的时间才能执行,这样的情况下可以使用synchronized代码块去优化代码执行时间,也就是减少锁的粒度。

/**
* 使用synchronized代码块减小锁的粒度,提高性能
* @author alienware
*
*/
public class Optimize {

public void doLongTimeTask(){    try {        System.out.println("当前线程开始:" + Thread.currentThread().getName() +                 ", 正在执行一个较长时间的业务操作,其内容不需要同步");        Thread.sleep(2000);        synchronized(this){            System.out.println("当前线程:" + Thread.currentThread().getName() +                 ", 执行同步代码块,对其同步变量进行操作");            Thread.sleep(1000);        }        System.out.println("当前线程结束:" + Thread.currentThread().getName() +                ", 执行完毕");    } catch (InterruptedException e) {        e.printStackTrace();    }}public static void main(String[] args) {    final Optimize otz = new Optimize();    Thread t1 = new Thread(new Runnable() {        @Override        public void run() {            otz.doLongTimeTask();        }    },"t1");    Thread t2 = new Thread(new Runnable() {        @Override        public void run() {            otz.doLongTimeTask();        }    },"t2");    t1.start();    t2.start();}

}
synchronized可以使用任意的对象进行加锁。
另外特别注意一个问题,不要一个String的常量加锁,会出现死循环问题。
锁对象的改变的问题,当使用一个对象进行加锁时要注意对象本身发生改变的时候,那么持有的锁就不同,如果对象本身不发生改变没那么依然是同步的,既对象的属性发生改变。
死锁问题:死锁是两个甚至多个线程被永久阻塞时的一种运行局面,这种局面的生成伴随着至少两个线程和两个或者多个资源。
1.6 volatile关键字

volatile关键字使系统中所有线程对该关键字修饰的变量共享可见,可以禁止线程的工作内存对volatile修饰的变量进行缓存。

public class RunThread extends Thread{

private volatile boolean isRunning = true;private void setRunning(boolean isRunning){    this.isRunning = isRunning;}public void run(){    System.out.println("进入run方法..");    int i = 0;    while(isRunning == true){        //..    }    System.out.println("线程停止");}public static void main(String[] args) throws InterruptedException {    RunThread rt = new RunThread();    rt.start();    Thread.sleep(1000);    rt.setRunning(false);    System.out.println("isRunning的值已经被设置了false");}

}
1.Java提供了volatile关键字来保证可见性。 当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。 而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。 2.volatile关键虽然有多个线程之间的可见性,但是却不具备同步性(也就是原子性),可以算上是一个轻量级的synchronized,性能要比synchronized强很多,不会造成阻塞(在很多开源的架构中,比如netty的底层中就大量的使用volatile,可见netty性能一定是非常不错的。)这里需要注意:一般volatile用于针对与多个线程可见的变量操作,不能代替synchronized的同步功能。

import java.util.concurrent.atomic.AtomicInteger;

/**
* volatile关键字不具备synchronized关键字的原子性(同步)
* @author alienware
*
*/
public class VolatileNoAtomic extends Thread{
//private static volatile int count;
private static AtomicInteger count = new AtomicInteger(0);
private static void addCount(){
for (int i = 0; i < 1000; i++) {
//count++ ;
count.incrementAndGet();
}
System.out.println(count);
}

public void run(){    addCount();}public static void main(String[] args) {    VolatileNoAtomic[] arr = new VolatileNoAtomic[100];    for (int i = 0; i < 10; i++) {        arr[i] = new VolatileNoAtomic();    }    for (int i = 0; i < 10; i++) {        arr[i].start();    }}

}
volatile关键字只有可见性,没有原子性。要实现原子性建议使用atomic类的系列对象,支持原子性操作(atomic类只保证本身方法原子性,并不保证多次操作的原子性)

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

public class AtomicUse {

private static AtomicInteger count = new AtomicInteger(0);//多个addAndGet在一个方法内是非原子性的,需要加synchronized进行修饰,保证4个addAndGet整体原子性/**synchronized*/public synchronized int multiAdd(){        try {            Thread.sleep(100);        } catch (InterruptedException e) {            e.printStackTrace();        }        count.addAndGet(1);        count.addAndGet(2);        count.addAndGet(3);        count.addAndGet(4); //+10        return count.get();}public static void main(String[] args) {    final AtomicUse au = new AtomicUse();    List<Thread> ts = new ArrayList<Thread>();    for (int i = 0; i < 100; i++) {        ts.add(new Thread(new Runnable() {            @Override            public void run() {                System.out.println(au.multiAdd());            }        }));    }    for(Thread t : ts){        t.start();    }}

}

0 0