1.线程的定义
①继承Thread类,将执行的任务逻辑放到run方法中,调用start方法来开启线程
1 public class ThreadDemo { 2 public static void main(String[] args) { 3 TDemo t = new TDemo(); 4 // 开启线程 5 t.start(); 6 for (int i = 0; i < 20; i++) { 7 System.out.println("main:" + i); 8 } 9 } 10 } 11 12 class TDemo extends Thread { 13 @Override 14 public void run() { 15 for (int i = 0; i < 9; i++) { 16 System.out.println("Thread:" + i); 17 } 18 } 19 }
②实现Runnable,重写run方法,需要利用Runnable对象来构建一个Thread对象从而启动线程
由于java是单继承的,因此当一个类已经继承了父类时,便不能继承Thread类。而又希望启用线程,此时实现Runnable接口即可达到目的。
1 public class RunnableDemo { 2 public static void main(String[] args) { 3 RDemo r = new RDemo(); 4 // 通过Runnable对象来构建一个Thread对象 5 Thread t = new Thread(r); 6 t.start(); 7 for (int i = 0; i < 20; i++) { 8 System.out.println("main:" + i); 9 } 10 } 11 } 12 13 class RDemo implements Runnable { 14 @Override 15 public void run() { 16 for (int i = 0; i < 10; i++) { 17 System.out.println("Thread:" + i); 18 } 19 } 20 }
③实现Callable<T>,重写call方法
1 import java.util.concurrent.Callable; 2 import java.util.concurrent.ExecutionException; 3 import java.util.concurrent.ExecutorService; 4 import java.util.concurrent.Executors; 5 import java.util.concurrent.Future; 6 7 public class CallableDemo { 8 public static void main(String[] args) throws InterruptedException, ExecutionException { 9 ExecutorService es = Executors.newCachedThreadPool(); 10 Future<String> f = es.submit(new CDemo()); 11 System.out.println(f.get()); 12 } 13 } 14 15 class CDemo implements Callable<String> { 16 @Override 17 public String call() throws Exception { 18 return "hahah~~~"; 19 } 20 }
2.线程的状态
创建:新创建了一个线程对象。
就绪:线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的执行权。
运行:就绪状态的线程获取了CPU执行权,执行程序代码。
阻塞: 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
死亡:线程执行完它的任务时。
3.常见的线程方法
① Thread(String name) 初始化线程的名字
② getName() 返回线程的名字
③ setName(String name) 设置线程对象名
④ sleep() 线程睡眠指定的毫秒数。
⑤ getPriority() 返回当前线程对象的优先级 默认线程的优先级是5
⑥ setPriority(int newPriority) 设置线程的优先级。(最大的优先级是10,最小的1,默认是5)虽然设置了线程的优先级,但是具体的实现取决于底层的操作系统的实现。
⑦ currentThread() 返回CPU正在执行的线程的对象。
4.多线程的并发安全问题
线程的执行不存在先后,相互抢占执行,抢占并不是只发生在线程执行的开始,而是发生在线程执行的每一步过程中。由于多个线程并发导致出现了一些不符合常理的数据的现象,即线程安全问题。
4.1出现线程安全的根本原因
①存在两个或者两个以上的线程对象共享同一个资源
②多线程操作共享资源的代码有多句
4.2线程安全问题的解决方案
1.可以使用同步代码块去解决。
1 synchronized(锁对象){ 2 需要被同步的代码 3 }
注意事项:
①锁对象可以是任意一个对象
②一个线程在同步块中sleep,并不会释放锁对象
③如果不存在线程安全问题,千万不要使用同步代码块,因为会降低效率
④锁对象必须是多线程共享的一个资源,否则锁不住
2.同步函数 就是使用synchronized修饰的方法
注意事项:
①如果是一个非静态的同步函数,锁对象是this;如果是静态的同步函数,锁对象是当前函数所属的类的字节码文件(class)
②同步函数的锁对象是固定的,不能由开发者来指定
推荐使用同步代码块
①同步代码块的锁对象可以由开发者指定,方便控制。而同步方法是固定的。
②同步代码块可以很方便控制需要被同步代码的范围,同步函数必须是整个函数的所有代码都被同步
例:模拟取款,
1 public class Bank { 2 public static void main(String[] args) { 3 //创建一个账户 4 Account account = new Account("10086", 1000); 5 //模拟两个线程对同一个账户取钱 6 new DrawThread("张三",account,800).start(); 7 new DrawThread("李四", account, 900).start(); 8 } 9 } 10 11 class DrawThread extends Thread{ 12 //模拟用户账户 13 private Account account; 14 //当前取钱线程所希望取得钱数 15 private double drawMoney; 16 17 public DrawThread(String name, Account account, double drawMoney) { 18 super(name); 19 this.account = account; 20 this.drawMoney = drawMoney; 21 } 22 23 @Override 24 public void run() { 25 /* 26 * 虽然java程序允许任何对象作为同步监视器,但是同步监视器的目的: 27 * 阻止两个线程对同一个共享资源进行并发访问,因此推荐使用可能被并发访问的共享资源 28 * 当做同步监视器 29 * 30 * 加锁-修改-释放锁 31 * 32 * 字节码文件 33 * 静态变量 34 * "锁对象" 35 * 36 */ 37 synchronized (account) { 38 //synchronized (this) { //非共享,失败 39 //synchronized (new String("")) { //非共享,失败 40 //synchronized ("") { 41 //账户余额大于取钱数目 42 if(account.getBalance() >= drawMoney) { 43 //吐出钞票 44 System.out.println(getName() + "取钱成功!吐出钞票:" + drawMoney); 45 46 // try { 47 // Thread.sleep(5000); 48 // } catch (Exception e) { 49 // e.printStackTrace(); 50 // } 51 52 //修改余额 53 account.setBalance(account.getBalance() - drawMoney); 54 System.out.println("\t余额为:" + account.getBalance()); 55 } else { 56 System.out.println(getName() + "取钱失败!余额不足!"); 57 } 58 } 59 } 60 } 61 62 class Account{ 63 //封装账户编号、账户余额两个成员变量 64 private String accountNo; 65 private double balance; 66 67 public Account(String accountNo, double balance) { 68 super(); 69 this.accountNo = accountNo; 70 this.balance = balance; 71 } 72 73 public String getAccountNo() { 74 return accountNo; 75 } 76 77 public void setAccountNo(String accountNo) { 78 this.accountNo = accountNo; 79 } 80 81 public double getBalance() { 82 return balance; 83 } 84 85 public void setBalance(double balance) { 86 this.balance = balance; 87 } 88 }