- 线程安全概念:当多个线程访问某一个类(对象或方法)时,这个类始终都能表现出正确的行为,那么这个类(对象或方法)就是线程安全的。
- 实现一个线程:
extends Thread(重写run方法)
或
implements Runnable
- synchronized:可以在任意对象及方法上加锁,而加锁的这段代码称为互斥区,或临界区。
- 注:一个线程想要执行syschronized修饰的方法里的代码,首先是尝试获得锁,如果拿到锁,执行syschronzied代码体内容,拿不到锁,这个线程就会不断的尝试获得这把锁,直到拿到为止,而且是多个线程同时去竞争这把锁。(也就是会有锁竞争的问题)。
1 public class MyThread extends Thread{ 2 3 private int count=5; 4 5 public synchronized void run() { 6 count--; 7 System.out.println(this.currentThread().getName()+":"+count); 8 } 9 10 public static void main(String args[]) { 11 12 MyThread thread=new MyThread();//当前线程 13 Thread t1=new Thread(thread,"1号线程"); 14 Thread t2=new Thread(thread,"2号线程"); 15 Thread t3=new Thread(thread,"3号线程"); 16 Thread t4=new Thread(thread,"4号线程"); 17 Thread t5=new Thread(thread,"5号线程"); 18 t1.start(); 19 t2.start(); 20 t3.start(); 21 t4.start(); 22 t5.start(); 23 //按照线程启动,结果应该为:43210,但是run方法不加synchronized达不到预期结果
//使用synchronized可以锁住但是存在锁竞争问题
24 } 25 }
- 多个线程多个锁
- 注:关键字synchronized取得的锁都是对象锁,而不是把一段代码(方法)当作锁,多个对象通过多个线程去访问synchronized修饰的方法时,线程拿到的是自己指定对象的锁(不是相同的锁),这种情况下会导致结果达不到预期,要想多线程不同对象拿到相同的锁,可以将synchronzied修饰的方法改为静态的(static),表示锁定.class类,类一级别的锁(独占.class类)
public class MultiThread { private static int num=0; //static public static synchronized void printNum(String tag) { try { if(tag.equals("a")) { num=100; System.out.println("a"); Thread.sleep(2000); }else { num=200; System.out.println("b"); } System.out.println(tag+":"+num); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public static void main(String args[]) { MultiThread multiThread1=new MultiThread(); MultiThread multiThread2=new MultiThread(); //线程1 Thread t1=new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub multiThread1.printNum("a"); } }); //线程2 Thread t2=new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub multiThread2.printNum("b"); } }); t1.start(); t2.start(); //预期结果应该是:a,a:100,b,b:200;但是即使方法加上synchronzied修饰,结果也达不到预期 //2个线程分别得到的是multiThread1与multiThread2的对象锁,达不到同步的效果 //要想达到效果可以将方法改成静态的,此时属于类(MultiThread)一级别的锁 //2个线程要做到不同时调用printNum方法,锁.class类 } }
- 同步与异步
- 同步:synchronzied,同步的概念就是共享,不是共享资源没必要同步,同步的目的就是为了线程安全,线程安全条件:原子性(同步),可见性。
- 异步:asynchronized,异步的概念就是独立,互相之间不受任何制约(类似Ajax)。
- 脏读
1.当数据库的一个事务A正在使用一个数据但还没有提交,另外一个事务B也访问到了这个数据,还使用了这个数据,这就会导致事务B使用了事务A没有提交之前的数据。(如果当前事务A回滚,那么事务B拿到的就是脏数据) 2.线程A去查某一数据,在还没有拿到该数据的时候,线程B修改了该数据,线程A拿到的是B没修改之前的数据,这是由于数据库一致性读的特性造成的。(在数据库删改的过程中,会将数据存入快照中,如果监听到数据有改动会去快照中找旧数据,所以拿到的是之前的数据)
- synchronized锁重入:使用synchronized时,当一个线程得到了一个对象的锁后,再次请求此对象时可以再次得到该对象的锁。
/** * synchronized锁重入 */ public class SyncDubbo1 { public synchronized void method1() { System.out.println("1"); method2(); } public synchronized void method2() { System.out.println("2"); method3(); } public synchronized void method3() { System.out.println("3"); } public static void main(String args[]) { SyncDubbo1 dubbo=new SyncDubbo1(); //线程1 Thread t1=new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub dubbo.method1(); } }); t1.start(); } }
- 父子继承关系中要想实现同步,都得使用synchronized,否则存在线程安全问题。
/** * 存在继承关系时,synchronzied修饰的父类子类方法,可以实现线程安全 */ public class SyncDubbo2 { static class A{ public int i=10; public synchronized void add(){ try { i--; System.out.println("A:"+i); Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } static class B extends A{ public synchronized void add(){ try { while(i>0){ i--; System.out.println("B:"+i); Thread.sleep(1000); super.add(); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } public static void main(String args[]){ //线程1 Thread t1=new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub B b=new B(); b.add(); } }); t1.start(); } }
- synchronized使用String的常量加锁,会出现死循环问题。
- volatile关键字:volatile关键字主要作用是使变量在多个线程之间可见。
- volatile的作用就是强制线程到主内存(共享内存)里去读取变量,而不去线程工作内存区里去读取,从而实现多个线程间的变量可见,也就是满足线程安全的可见性。
- volatile不具备同步性(原子性),可以算是一个轻量级的synchronized,性能要比synchronzied强大很多,不会造成阻塞(netty底层大量使用volatile)。
- volatile用于多个线程之间变量可见,不能代替synchronzied的同步功能。(AtomicInteger类可以实现原子性,但是它只保证本身方法的原子性,不能保证多个方法的原子性)。
- 线程通讯
- while轮询的方式(通过条件判断是否达到实现通讯,用volatile)。
- 同步synchronized:线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体,线程间的通讯就成为整体的必要方法之一。
- 使用wait/notify:wait和notify必须配合synchronized关键字使用。wait方法释放锁,notify方法不释放锁。