一、关于Java多线程中的一些概念
1.1 线程基本概念
从JDK1.5开始,Java提供了3中方式来创建、启动多线程:
方式一(不推荐)、通过继承Thread类来创建线程类,重写run()方法作为线程执行体;
方式二、实现Runnable接口来创建线程类,重写run()方法作为线程执行体;
方式三、实现Callable接口来创建线程类,重写run()方法作为线程执行体;
不同的是,其中方式一的效果最差,是因为
1、线程类继承了Thread类,无法再继承其他父类;
2、因为每条线程都是一个Thread子类的实例,因此多个线程之间共享数据比较麻烦。
二、一些关于synchronized关键字的简单Demo
2.1、多个线程单个锁
当多个线程访问myThread的run方法时,以排队的方式进行处理(这里排对是按照CPU分配的先后顺序而定的),一个线程想要执行synchronized修饰的方法里的代码首先会去尝试获得锁,如果拿到锁,执行synchronized代码体内容;
如果拿不到锁,这个线程就会不断的尝试获得这把锁,直到拿到为止,而且是多个线程同时去竞争这把锁。(也就是会有锁竞争的问题)
package com.sun.multithread.sync; public class MyThread extends Thread { private int count = 5; // synchronized加锁 public synchronized void run() { count--; System.out.println(this.currentThread().getName() + " count = " + count); } public static void main(String[] args) { 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(); } }
2.2、多个线程多把锁
关键字synchronized取得的锁都是对象锁,而不是把一段代码(方法)当成锁,所以代码中哪个线程先执行synchronized关键字的方法,哪个线程就持有该方法所属对象的锁。但是在静态(static)方法上加synchronized关键字,表示锁定class类,类级别的锁
package com.sun.multithread.sync; public class MultiThread { private static int num = 0; /** * 在静态(static)方法上加synchronized关键字,表示锁定class类,类级别的锁
* * 关键字synchronized取得的锁都是对象锁,而不是把一段代码(方法)当成锁, * 所以代码中哪个线程先执行synchronized关键字的方法,哪个线程就持有该方法所属对象的锁 * * @param tag 参数 */ public static 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(); } } 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() { m1.printNum("b"); } }); t1.start(); t2.start(); } }
2.3 银行取钱的多线程例子
1、创建一个银行账户,并在里面写好取钱的方法,其中使用synchronized关键字修饰多线程操作的方法
package com.sun.multithread.sync.bankdemo; /** * 银行账户类 * * @author ietree */ public class Account { /** * 账号 */ private String accountNo; /** * 余额 */ private double balance; public Account() { } /** * 带参构造函数 * * @param accountNo 账户 * @param balance 余额 */ public Account(String accountNo, double balance) { this.accountNo = accountNo; this.balance = balance; } // 访问该账户的余额,使用synchronized修饰符将它变成同步方法 public synchronized double getBalance() { return balance; } /** * 取钱的方法 * * @param drawAmount 取钱金额 */ public synchronized void draw(double drawAmount) { // 如果余额大于等于用户取的钱,则取款成功 if (balance >= drawAmount) { // 取款成功 System.out.println(Thread.currentThread().getName() + "取钱成功!用户取出" + drawAmount + "元"); // 修改余额 balance -= drawAmount; System.out.println("\t余额为: " + balance); } else { System.out.println(Thread.currentThread().getName() + "取钱失败!您的余额不足"); } } public String getAccountNo() { return accountNo; } public void setAccountNo(String accountNo) { this.accountNo = accountNo; } // 重写hashCode()方法 public int hashCode() { return accountNo.hashCode(); } // 重写equals()方法 public boolean equals(Object obj) { if (this == obj) { return true; } if (obj != null && obj.getClass() == Account.class) { Account target = (Account) obj; return target.accountNo.equals(accountNo); } return false; } }
2、创建多个线程同时操作一个账户
package com.sun.multithread.sync.bankdemo; class DrawThread extends Thread { /** * 模拟用户账户 */ private Account account; /** * 当前取钱线程所希望取的钱数 */ private double drawAmount; public DrawThread(String name, Account account, double drawAmount){ super(name); this.account = account; this.drawAmount = drawAmount; } // 当多条线程修改同一个共享数据时,将涉及数据安全问题 public void run(){ account.draw(drawAmount); } } public class DrawTest { public static void main(String[] args) { // 创建一个账户 Account acct = new Account("1234567", 1000); // 模拟两个线程对同一账户取钱 new DrawThread("路人甲", acct, 800).start(); new DrawThread("路人乙", acct, 800).start(); } }
2.4 业务整体需要使用完整的synchronized,保持业务的原子性
package com.ietree.multithread.sync; /** * 业务整体需要使用完整的synchronized,保持业务的原子性 * * @author ietree */ public class DirtyRead { private String username = "Jack"; private String password = "123"; public synchronized void setValue(String username, String password) { this.username = username; try { // 睡眠2秒 Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } this.password = password; System.out.println("setValue最终结果:username = " + username + ", password = " + password); } // synchronized public void getValue() { System.out.println("getValue方法得到:username = " + this.username + ", password = " + this.password); } public static void main(String[] args) throws Exception { final DirtyRead dr = new DirtyRead(); Thread t1 = new Thread(new Runnable() { @Override public void run() { dr.setValue("Dylan", "456"); } }); t1.start(); Thread.sleep(1000); dr.getValue(); } }
程序输出:
getValue方法得到:username = Dylan, password = 123
setValue最终结果:username = Dylan, password = 456
这里出现了脏读现象,应该要保持业务的原子性,修改如下:
package com.ietree.multithread.sync; /** * 业务整体需要使用完整的synchronized,保持业务的原子性 * * @author ietree */ public class DirtyRead { private String username = "Jack"; private String password = "123"; public synchronized void setValue(String username, String password) { this.username = username; try { // 睡眠2秒 Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } this.password = password; System.out.println("setValue最终结果:username = " + username + ", password = " + password); } // synchronized public synchronized void getValue() { System.out.println("getValue方法得到:username = " + this.username + ", password = " + this.password); } public static void main(String[] args) throws Exception { final DirtyRead dr = new DirtyRead(); Thread t1 = new Thread(new Runnable() { @Override public void run() { dr.setValue("Dylan", "456"); } }); t1.start(); Thread.sleep(1000); dr.getValue(); } }
程序输出:
setValue最终结果:username = Dylan, password = 456
getValue方法得到:username = Dylan, password = 456
当在set和get方法上同时使用了synchronized能确保业务原子性,不会出现脏读现象。
2.5 synchronized的重入
demo1:
package com.ietree.multithread.sync; public class SyncDemo1 { 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 SyncDemo1 sd = new SyncDemo1(); Thread t1 = new Thread(new Runnable() { @Override public void run() { sd.method1(); } }); t1.start(); } }
demo2:
package com.ietree.multithread.sync; public class SyncDemo2 { static class Parent { 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 Parent { 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(); } }
2.6 synchronized的Exception
package com.ietree.multithread.sync; public class SyncException { private int i = 0; public synchronized void operation() { while (true) { try { i++; Thread.sleep(100); System.out.println(Thread.currentThread().getName() + " , i = " + i); if (i == 20) { throw new RuntimeException(); } } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { final SyncException se = new SyncException(); Thread t1 = new Thread(new Runnable() { @Override public void run() { se.operation(); } }, "t1"); t1.start(); } }
2.7 锁对象的改变问题
demo1:
package com.ietree.multithread.sync; public class ChangeLock { private String lock = "lock"; private void method() { synchronized (lock) { try { System.out.println("当前线程 : " + Thread.currentThread().getName() + "开始"); lock = "change lock"; Thread.sleep(2000); System.out.println("当前线程 : " + Thread.currentThread().getName() + "结束"); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { final ChangeLock changeLock = new ChangeLock(); Thread t1 = new Thread(new Runnable() { @Override public void run() { changeLock.method(); } }, "t1"); Thread t2 = new Thread(new Runnable() { @Override public void run() { changeLock.method(); } }, "t2"); t1.start(); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } t2.start(); } }
程序输出:
当前线程 : t1开始
当前线程 : t2开始
当前线程 : t1结束
当前线程 : t2结束
demo2:
package com.ietree.multithread.sync; public class ChangeLock { private String lock = "lock"; private void method() { synchronized (lock) { try { System.out.println("当前线程 : " + Thread.currentThread().getName() + "开始"); // lock = "change lock"; Thread.sleep(2000); System.out.println("当前线程 : " + Thread.currentThread().getName() + "结束"); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { final ChangeLock changeLock = new ChangeLock(); Thread t1 = new Thread(new Runnable() { @Override public void run() { changeLock.method(); } }, "t1"); Thread t2 = new Thread(new Runnable() { @Override public void run() { changeLock.method(); } }, "t2"); t1.start(); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } t2.start(); } }
程序输出:
当前线程 : t1开始
当前线程 : t1结束
当前线程 : t2开始
当前线程 : t2结束
两个程序相比差异在于是否含有 lock = "change lock";这个改变了锁对象,所以输出有差异。
2.8 死锁问题,在设计程序时就应该避免双方相互持有对方的锁的情况
package com.ietree.multithread.sync; public class DeadLock implements Runnable { private String tag; private static Object lock1 = new Object(); private static Object lock2 = new Object(); public void setTag(String tag) { this.tag = tag; } @Override public void run() { if (tag.equals("a")) { synchronized (lock1) { try { System.out.println("当前线程 : " + Thread.currentThread().getName() + " 进入lock1执行"); Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock2) { System.out.println("当前线程 : " + Thread.currentThread().getName() + " 进入lock2执行"); } } } if (tag.equals("b")) { synchronized (lock2) { try { System.out.println("当前线程 : " + Thread.currentThread().getName() + " 进入lock2执行"); Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock1) { System.out.println("当前线程 : " + Thread.currentThread().getName() + " 进入lock1执行"); } } } } public static void main(String[] args) { DeadLock d1 = new DeadLock(); d1.setTag("a"); DeadLock d2 = new DeadLock(); d2.setTag("b"); Thread t1 = new Thread(d1, "t1"); Thread t2 = new Thread(d2, "t2"); t1.start(); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } t2.start(); } }
程序输出:
当前线程 : t1 进入lock1执行
当前线程 : t2 进入lock2执行
程序一直等待获取锁的状态,造成死锁问题。
2.9 同一对象属性的修改不会影响锁的情况
package com.ietree.multithread.sync; public class ModifyLock { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public synchronized void changeAttributte(String name, int age) { try { System.out.println("当前线程 : " + Thread.currentThread().getName() + " 开始"); this.setName(name); this.setAge(age); System.out.println("当前线程 : " + Thread.currentThread().getName() + " 修改对象内容为: " + this.getName() + ", " + this.getAge()); Thread.sleep(2000); System.out.println("当前线程 : " + Thread.currentThread().getName() + " 结束"); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { final ModifyLock modifyLock = new ModifyLock(); Thread t1 = new Thread(new Runnable() { @Override public void run() { modifyLock.changeAttributte("Jack", 18); } }, "t1"); Thread t2 = new Thread(new Runnable() { @Override public void run() { modifyLock.changeAttributte("Rose", 20); } }, "t2"); t1.start(); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } t2.start(); } }
程序输出:
当前线程 : t1 开始 当前线程 : t1 修改对象内容为: Jack, 18 当前线程 : t1 结束 当前线程 : t2 开始 当前线程 : t2 修改对象内容为: Rose, 20 当前线程 : t2 结束
2.10 使用synchronized代码块加锁,比较灵活
package com.ietree.multithread.sync; public class ObjectLock { public void method1() { synchronized (this) { // 对象锁 try { System.out.println("do method1.."); Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } public void method2() { // 类锁 synchronized (ObjectLock.class) { try { System.out.println("do method2.."); Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } private Object lock = new Object(); public void method3() { // 任何对象锁 synchronized (lock) { try { System.out.println("do method3.."); Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { final ObjectLock objLock = new ObjectLock(); Thread t1 = new Thread(new Runnable() { @Override public void run() { objLock.method1(); } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { objLock.method2(); } }); Thread t3 = new Thread(new Runnable() { @Override public void run() { objLock.method3(); } }); t1.start(); t2.start(); t3.start(); } }
程序输出:
do method2.. do method3.. do method1..
以上语句是同时输出的,因为他们拿到的锁都是不同的锁,所以互不影响。
2.11 使用synchronized代码块减小锁的粒度,提高性能
package com.ietree.multithread.sync; 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(); } }
程序输出:
当前线程开始:t2, 正在执行一个较长时间的业务操作,其内容不需要同步
当前线程开始:t1, 正在执行一个较长时间的业务操作,其内容不需要同步
当前线程:t1, 执行同步代码块,对其同步变量进行操作
当前线程结束:t1, 执行完毕
当前线程:t2, 执行同步代码块,对其同步变量进行操作
当前线程结束:t2, 执行完毕
2.12 避免使用字符串常量锁
package com.ietree.multithread.sync; public class StringLock { public void method() { // new String("字符串常量") synchronized ("字符串常量") { try { while (true) { 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 StringLock stringLock = new StringLock(); Thread t1 = new Thread(new Runnable() { @Override public void run() { stringLock.method(); } }, "t1"); Thread t2 = new Thread(new Runnable() { @Override public void run() { stringLock.method(); } }, "t2"); t1.start(); t2.start(); } }
程序输出:
当前线程 : t1开始
当前线程 : t1结束
当前线程 : t1开始
当前线程 : t1结束
当前线程 : t1开始
当前线程 : t1结束
当前线程 : t1开始
......