线程的概念
什么是线程
在一个程序中同时运行的多个独立流程,每一个独立的流程就是一个线程
线程并发
多个线程并发执行
主线程 CPU 优先级 时间片 资源
当JVM启动之后,加载类文件,发现main方法,那么就会为main方法创建一个线程,用于main方法执行,这个为main方法创建的线程称为主线程
线程的使用
在Java中创建线程的方法有两种
- 方法一 继承java.lang.Thread类
- 方法二 实现java.lang.Runnable接口
继承Thread类
- 自定义一个线程类继承自Thread
- 重写Thread的run方法
- 创建一个该类的对象
- 调用该类继承自Thread的start方法开启线程
代码示例
public class MyThread1 extends Thread{ public void run(){ for (int i = 0; i < 1000; i++) { System.out.println(i+" $$$"); } } }
public class MyThread2 extends Thread{ public void run(){ for (int i = 0; i < 1000; i++) { System.out.println(i+" ###"); } } }
public class ThreadTest { public static void main(String[] args) { Thread t1=new MyThread1(); Thread t2=new MyThread2(); //启动线程 t1.start(); t2.start(); } }
Runnable接口开发线程
- 用户开发一个类实现Runnable接口
- 实现run()
- 运行线程
代码示例
public class MyRunnable1 implements Runnable{ @Override public void run() { for (int i = 0; i < 1000; i++) { System.out.println(i+" $$$"); } } }
public class MyRunnable2 implements Runnable{ @Override public void run() { for (int i = 0; i < 1000; i++) { System.out.println(i+" ###"); } } }
public class ThreadTest { public static void main(String[] args) { Runnable r1=new MyRunnable1(); Thread t1=new Thread(r1); Runnable r2=new MyRunnable2(); Thread t2=new Thread(r2); //启动线程 t1.start(); t2.start(); } }
线程使用总结
- 根据学习过的知识判断,以上两种线程创建方法,哪一种更好--第二种实现了任务代码和API的分离
- 程序的输出结果固定吗?
- 程序中存在几个线程?执行的先后顺序
- 可不可以直接在main方法中直接调用run()
线程的状态
- 初始状态
- 可运行状态
- 运行状态
- 终结状态
编辑
sleep方法
void sleep(long time)方法用于使当前线程休眠指定的毫秒数
public class MyThread1 extends Thread{ public void run(){ for (int i = 0; i < 1000; i++) { System.out.println(i+" $$$"); try { //睡眠1秒 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
public class MyRunnable1 implements Runnable{ @Override public void run() { for (int i = 0; i < 1000; i++) { System.out.println(i+" $$$"); try { //睡眠1秒 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
一旦调用了sleep方法,该线程就由运行状态进入了阻塞状态
编辑join方法的使用
- 利用sleep方法对线程的控制是非常不精确的
- join方法可以精确控制线程
- join方法也会导致线程阻塞
- 特点:如果当前线程中调用了另外一个线程的 join方法,当前线程会立即阻塞,直到另外一个线程运行完成
public class MyThread1 extends Thread{ public void run(){ for (int i = 0; i < 1000; i++) { System.out.println(i+" $$$"); } } }
public class MyThread2 extends Thread{ Thread thread; public void run(){ try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } for (int i = 0; i < 1000; i++) { System.out.println(i+" ###"); } } }
public class ThreadTest { public static void main(String[] args) { MyThread1 t1=new MyThread1(); MyThread2 t2=new MyThread2(); //将线程1赋值到MyThread2的属性thread t2.thread=t1; //启动线程 t1.start(); t2.start(); } }
join方法的问题
如果2个线程彼此调用对方的join方法,会导致程序无法进行。
解决办法
调用 void join(long millis) 方法
客户存取款多线程实例
public class Account { //银行账户余额 private int balance; public Account(int balance) { this.balance = balance; } //存款方法 public void deposit(int money){ balance=balance+money; } //取款方法 public void withdrawal(int money){ if(money>balance){ System.out.println("余额不足"); }else{ balance=balance-money; } } public int getBalance() { return balance; } }
public class PersonA extends Thread{ //用户A的账户属性 private Account account; public PersonA(Account account){ this.account=account; } public void run(){ for (int i = 0; i < 2; i++) { System.out.println("此时账户还有"+account.getBalance()+"元"); account.deposit(500); System.out.println("用户A存款500元"); System.out.println("此时账户还有"+account.getBalance()+"元"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }
public class PersonB extends Thread{ //用户B的账户属性 private Account account; public PersonB(Account account){ this.account=account; } public void run(){ for (int i = 0; i < 2; i++) { System.out.println("此时账户还有"+account.getBalance()+"元"); account.withdrawal(1000); System.out.println("用户B取款1000元"); System.out.println("此时账户还有"+account.getBalance()+"元"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }
public class BankDemo { public static void main(String[] args) { //初始账户余额3000块 Account account=new Account(3000); PersonA personA=new PersonA(account); PersonB personB=new PersonB(account); //启动线程 personA.start(); personB.start(); } }
线程同步问题
- 产生数据不一致的原因:多个线程并发访问了同一个对象,如果破坏了不可分割的操作,从而就会造成数据不一致
- 被多线程并发访问时如果一个对象有可能出现数据不一致的问题,那么这个对象称为线程不安全的对象
如何解决多线程并发访问的问题
利用synchronized 同步锁
synchronized(Object o){ 代码块 }
public class Account { //银行账户余额 private int balance; private Object o=new Object(); public Account(int balance) { this.balance = balance; } //存款方法 public void deposit(int money){ synchronized (o){ balance=balance+money; } } //取款方法 public void withdrawal(int money){ synchronized (o){ if(money>balance){ System.out.println("余额不足"); }else{ balance=balance-money; } } } public int getBalance() { return balance; } }
死锁和解决办法
什么是死锁
死锁是因为多线程访问共享资源,由于访问的顺序不当所造成的,通常是一个线程锁定了一个资源A,而又想去锁定资源B;在另一个线程中,锁定了资源B,而又想去锁定资源A以完成自身的操作,两个线程都想得到对方的资源,而不愿释放自己的资源,造成两个线程都在等待,而无法执行的情况。
public class MyThread1 extends Thread{ //资源a private Object a; //资源b; private Object b; public MyThread1(Object a,Object b){ this.a=a; this.b=b; } public void run(){ //当前线程名称 String name = Thread.currentThread().getName(); System.out.println(name+"获取资源a"); synchronized (a){ System.out.println(name+"获取资源b"); await(); synchronized (b){ System.out.println(name+"释放资源b"); await(); } System.out.println(name+"释放资源a"); } } private void await() { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } }
public class MyThread2 extends Thread{ //资源a private Object a; //资源b; private Object b; public MyThread2(Object a,Object b){ this.a=a; this.b=b; } public void run(){ //当前线程名称 String name = Thread.currentThread().getName(); System.out.println("获取资源b"); synchronized (b){ System.out.println(name+"获取资源b"); await(); synchronized (a){ System.out.println(name+"释放资源a"); await(); } System.out.println(name+"释放资源b"); } } private void await() { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } }
public class ThreadTest { public static void main(String[] args) { Object a=new Object(); Object b=new Object(); MyThread1 t1=new MyThread1(a,b); MyThread2 t2=new MyThread2(a,b); //启动线程 t1.start(); t2.start(); } }
解决死锁
利用wait()方法和notify()方法
- wait():等待,如果线程执行了wait方法,那么该线程会进入等待的状态,等待状态下的线程必须要被其他线程调用notify()方法才能唤醒。
- notify():唤醒,唤醒线程池等待线程其中的一个。
- notifyAll():唤醒线程池所有等待线程。
wait与notify方法要注意的事项:
- wait方法与notify方法是属于Object对象的。
- wait方法与notify方法必须要在同步代码块或者是同步函数中才能使用。
- wait方法与notify方法必须要由所对象调用。
public class MyThread1 extends Thread{ //资源a private Object a; //资源b; private Object b; public MyThread1(Object a,Object b){ this.a=a; this.b=b; } public void run(){ //当前线程名称 String name = Thread.currentThread().getName(); System.out.println(name+"获取资源a"); synchronized (a){ try { a.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(name+"获取资源b"); await(); synchronized (b){ System.out.println(name+"释放资源b"); await(); } System.out.println(name+"释放资源a"); } } private void await() { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } }
public class MyThread2 extends Thread{ //资源a private Object a; //资源b; private Object b; public MyThread2(Object a,Object b){ this.a=a; this.b=b; } public void run(){ //当前线程名称 String name = Thread.currentThread().getName(); System.out.println("获取资源b"); synchronized (b){ System.out.println(name+"获取资源b"); await(); synchronized (a){ System.out.println(name+"释放资源a"); a.notify(); await(); } System.out.println(name+"释放资源b"); } } private void await() { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } }
public class ThreadTest { public static void main(String[] args) { Object a=new Object(); Object b=new Object(); MyThread1 t1=new MyThread1(a,b); MyThread2 t2=new MyThread2(a,b); //启动线程 t1.start(); t2.start(); } }