并发编程基础2
一:线程安全
当多个线程同时访问一个实例(对象或者方法)时,输入的行为是正确的,那么可以认为这个程序是线程安全的。
看下面这段代码,10个线程同时访问1个实例,那么运行结果会怎样呢?
/** * */ package com.day1; import java.io.UnsupportedEncodingException; /** * @author Administrator 线程安全:多个线程访问同一个实例时,如果输入的行为正确,那么我们可以称作线程安全, * 下面的实例,10个线程访问同一个对象,存在线程安全问题,但是如果我们在可能存在安全问题 * 的方法或者代码片段上加上锁synchronized,就可以避免线程安全问题 * a:线程获取cpu的处理权,是随机的,可以配置优先级,而不是按照程序的书写顺序 * b:在使用synchronized关键字时,应该尽量缩小使用范围,这样可以提高系统处理效率(前提是避免线程安全问题) */ public class MultiThread2 extends Thread { private int num = 10; public void run() { num--; String threadName = Thread.currentThread().getName(); System.out.println(threadName + ":::" + num); } public static void main(String[] args) throws UnsupportedEncodingException { MultiThread2 task = new MultiThread2(); Thread thread1 = new Thread(task, "线程1"); Thread thread2 = new Thread(task, "线程2"); Thread thread3 = new Thread(task, "线程3"); Thread thread4 = new Thread(task, "线程4"); Thread thread5 = new Thread(task, "线程5"); Thread thread6 = new Thread(task, "线程6"); Thread thread7 = new Thread(task, "线程7"); Thread thread8 = new Thread(task, "线程8"); Thread thread9 = new Thread(task, "线程9"); Thread thread10 = new Thread(task, "线程10"); thread1.start(); thread2.start(); thread3.start(); thread4.start(); thread5.start(); thread6.start(); thread7.start(); thread8.start(); thread9.start(); thread10.start(); } }
运行结果:
线程2:::8 线程5:::6 线程1:::7 线程3:::7 线程7:::5 线程4:::4 线程9:::3 线程6:::2 线程8:::1 线程10:::0
通过运行结果,可以看出存在线程安全问题 ,因为10个线程共享一个成员变量num的值,都去操作它
如果想是运行行为正确,可以在方法上面加个同步synchronized关键字:
public synchronized void run() { num--; String threadName = Thread.currentThread().getName(); System.out.println(threadName + ":::" + num); }
线程1:::9 线程3:::8 线程4:::7 线程7:::6 线程2:::5 线程5:::4 线程6:::3 线程8:::2 线程9:::1 线程10:::0
这时运行结果就是正确的了,顺序num的值是对的,有人会说线程输出顺序不对,这是因为线程
的执行要看cpu的分配,是随机的,不是按照程序书写顺序。
一般情况下,在保证同步安全的前提下,要尽可能的缩小锁定的范围,这样可以提高系统的处理效率,如下:
public void run() { synchronized (this) { System.out.println(Thread.currentThread().getName() + ":::" + --num); } }
线程1:::9 线程2:::8 线程4:::7 线程6:::6 线程8:::5 线程3:::4 线程10:::3 线程5:::2 线程7:::1 线程9:::0
运行结果依然是正确的。
二:同步与异步
只有多个线程存在同享时,才需要在同步锁synchronized,不共享的情况下不需要加,一个类中如果有的方法是同步的
,有的方法是异步的,那么当多个线程访问该实例时(一个实例),会发生什么情况呢?
/** * */ package com.day1; /** * @author Administrator * 两个线程访问同一个实例,如果访问的都是同步方法(加synchronized关键字,可以是同一个方法,也可能是 * 两个方法),那么一个线程正在执行程序时,另一个线程处于阻塞状态。 * 如果一个线程访问同步方法,另一个线程访问异步方法,那么不存在阻塞问题。 */ public class MultiThread3 { private synchronized void method1() { System.out.println("running method1..."); try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } } private void method2() { System.out.println("running method2..."); } public static void main(String[] args) { MultiThread3 task = new MultiThread3(); Thread t1 = new Thread(new Runnable(){ @Override public void run() { task.method1(); } }); Thread t2 = new Thread(new Runnable(){ @Override public void run() { task.method2(); } }); t1.start(); t2.start(); } }
两个线程访问同一个类时,如果一个线程访问同步方法,另一个线程访问异步方法时,那么不存在线程阻塞。
running method1... running method2...
运行结果:
两行结果都打印完之后,然后jvm等待1s后停止,说明两个线程都是正常执行,没有存在阻塞问题。
如果把第二个方法上面也加上synchronized关键字,
private synchronized void method2() { System.out.println("running method2..."); }
在运行:
running method1... running method2...
那么运行结果是,先输出第一行,然后等待1s后又输出了第二行,说明第一个线程访问method1时,
第二个线程没有拿到对象锁,处于线程等待状态,待一个线程执行完毕后,释放对象锁,然后第二个
线程执行method2方法。
三:多个线程多个实例控制并发
如果有多个线程访问多个 实例,那么他们之间是没有任何关系的,不存在线程安全问题,但是如果
也想使用锁控制它们,则需要类锁
/** * */ package com.day1; /** * @author Administrator * 两个线程访问同一个实例,如果访问的都是同步方法(加synchronized关键字,可以是同一个方法,也可能是 * 两个方法),那么一个线程正在执行程序时,另一个线程处于阻塞状态。 * 如果一个线程访问同步方法,另一个线程访问异步方法,那么不存在阻塞问题。 */ public class MultiThread3 { private synchronized void method1() { System.out.println("running method1..."); try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } } private synchronized void method2() { System.out.println("running method2..."); } public static void main(String[] args) { final MultiThread3 task1 = new MultiThread3(); final MultiThread3 task2 = new MultiThread3(); Thread t1 = new Thread(new Runnable(){ @Override public void run() { task1.method1(); } }); Thread t2 = new Thread(new Runnable(){ @Override public void run() { task2.method2(); } }); t1.start(); t2.start(); } }
运行结果:
running method1... running method2...
两行数据同时输出,等待1s然后停止,说明不存在线程安全问题,因为这里是两个实例,不同的锁
如果也想让它们串行执行,则需要使用类锁
1:在两个方法上加上static,则两个同步方法的锁变成了类的字节码文件
private synchronized static void method1() { System.out.println("running method1..."); try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } } private synchronized static void method2() { System.out.println("running method2..."); }
2:使用同步代码块,将类的字节码文件作为锁
private void method1() { synchronized (this.getClass()) { System.out.println("running method1..."); try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } } } private void method2() { synchronized (this.getClass()) { System.out.println("running method2..."); } }
3:定义一个静态的对象,作为类锁
因为静态的对象,所有的实例都是共享这个对象,所以在同一时刻只能有一个实例可以持有这个锁
private static final Object lock = new Object(); private void method1() { synchronized (lock) { System.out.println("running method1..."); try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } } } private void method2() { synchronized (lock) { System.out.println("running method2..."); } }
以上3种方式都可以实现类锁!