1.线程的定义
①继承Thread类,将执行的任务逻辑放到run方法中,调用start方法来开启线程
public class ThreadDemo {
public static void main(String[] args) {
TDemo t = new TDemo();
// 开启线程
t.start();
for (int i = 0; i < 20; i++) {
System.out.println("main:" + i);
}
}
} class TDemo extends Thread {
@Override
public void run() {
for (int i = 0; i < 9; i++) {
System.out.println("Thread:" + i);
}
}
}
②实现Runnable,重写run方法,需要利用Runnable对象来构建一个Thread对象从而启动线程
由于java是单继承的,因此当一个类已经继承了父类时,便不能继承Thread类。而又希望启用线程,此时实现Runnable接口即可达到目的。
public class RunnableDemo {
public static void main(String[] args) {
RDemo r = new RDemo();
// 通过Runnable对象来构建一个Thread对象
Thread t = new Thread(r);
t.start();
for (int i = 0; i < 20; i++) {
System.out.println("main:" + i);
}
}
} class RDemo implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("Thread:" + i);
}
}
}
③实现Callable<T>,重写call方法
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future; public class CallableDemo {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService es = Executors.newCachedThreadPool();
Future<String> f = es.submit(new CDemo());
System.out.println(f.get());
}
} class CDemo implements Callable<String> {
@Override
public String call() throws Exception {
return "hahah~~~";
}
}
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.可以使用同步代码块去解决。
synchronized(锁对象){
需要被同步的代码
}
注意事项:
①锁对象可以是任意一个对象
②一个线程在同步块中sleep,并不会释放锁对象
③如果不存在线程安全问题,千万不要使用同步代码块,因为会降低效率
④锁对象必须是多线程共享的一个资源,否则锁不住
2.同步函数 就是使用synchronized修饰的方法
注意事项:
①如果是一个非静态的同步函数,锁对象是this;如果是静态的同步函数,锁对象是当前函数所属的类的字节码文件(class)
②同步函数的锁对象是固定的,不能由开发者来指定
推荐使用同步代码块
①同步代码块的锁对象可以由开发者指定,方便控制。而同步方法是固定的。
②同步代码块可以很方便控制需要被同步代码的范围,同步函数必须是整个函数的所有代码都被同步
例:模拟取款,
public class Bank {
public static void main(String[] args) {
//创建一个账户
Account account = new Account("10086", 1000);
//模拟两个线程对同一个账户取钱
new DrawThread("张三",account,800).start();
new DrawThread("李四", account, 900).start();
}
} class DrawThread extends Thread{
//模拟用户账户
private Account account;
//当前取钱线程所希望取得钱数
private double drawMoney; public DrawThread(String name, Account account, double drawMoney) {
super(name);
this.account = account;
this.drawMoney = drawMoney;
} @Override
public void run() {
/*
* 虽然java程序允许任何对象作为同步监视器,但是同步监视器的目的:
* 阻止两个线程对同一个共享资源进行并发访问,因此推荐使用可能被并发访问的共享资源
* 当做同步监视器
*
* 加锁-修改-释放锁
*
* 字节码文件
* 静态变量
* "锁对象"
*
*/
synchronized (account) {
//synchronized (this) { //非共享,失败
//synchronized (new String("")) { //非共享,失败
//synchronized ("") {
//账户余额大于取钱数目
if(account.getBalance() >= drawMoney) {
//吐出钞票
System.out.println(getName() + "取钱成功!吐出钞票:" + drawMoney); // try {
// Thread.sleep(5000);
// } catch (Exception e) {
// e.printStackTrace();
// } //修改余额
account.setBalance(account.getBalance() - drawMoney);
System.out.println("\t余额为:" + account.getBalance());
} else {
System.out.println(getName() + "取钱失败!余额不足!");
}
}
}
} class Account{
//封装账户编号、账户余额两个成员变量
private String accountNo;
private double balance; public Account(String accountNo, double balance) {
super();
this.accountNo = accountNo;
this.balance = balance;
} public String getAccountNo() {
return accountNo;
} public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
} public double getBalance() {
return balance;
} public void setBalance(double balance) {
this.balance = balance;
}
}
synchronized代码块Demo
public class Bank2 {
public static void main(String[] args) {
//创建一个账户
Account2 account = new Account2("10086", 1000);
//模拟两个线程对同一个账户取钱
new DrawThread2("张三",account,800).start();
new DrawThread2("李四", account, 900).start();
}
} class DrawThread2 extends Thread{
//模拟用户账户
private Account2 account;
//当前取钱线程所希望取得钱数
private double drawMoney; public DrawThread2(String name, Account2 account, double drawMoney) {
super(name);
this.account = account;
this.drawMoney = drawMoney;
} @Override
public void run() {
account.draw(drawMoney); }
} class Account2{
//封装账户编号、账户余额两个成员变量
private String accountNo;
private double balance; public Account2(String accountNo, double balance) {
super();
this.accountNo = accountNo;
this.balance = balance;
} public String getAccountNo() {
return accountNo;
} public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
} public double getBalance() {
return balance;
} /*
* 锁对象是this
* 对于同一个Account账户而言,任意时刻只能有一个线程获得对account对象的锁定
*/
public synchronized void draw(double drawMoney) {
//账户余额大于取钱数目
if(balance >= drawMoney) {
//吐出钞票
System.out.println(Thread.currentThread().getName() + "取钱成功!吐出钞票:" + drawMoney); try {
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
} //修改余额
balance -= drawMoney;
System.out.println("\t余额为:" + balance);
} else {
System.out.println(Thread.currentThread().getName() + "取钱失败!余额不足!");
}
} }
synchronized方法Demo
成功的情况:
失败的情况:
5.死锁
由于多个线程之间的锁形成了嵌套导致程序无法继续运行的现象。
避免死锁:减少线程数量,统一锁对象,减少锁嵌套。
public class DeadLockDemo {
public static void main(String[] args) {
new Thread(new Runnable() { // 创建线程
public void run() {
synchronized ("资源1") {
System.out.println(Thread.currentThread().getName() + ":得不到资源2,就不释放资源1");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized ("资源2") {
System.out.println(Thread.currentThread().getName() + ":得到资源2,释放资源1");
}
}
}
}, "线程A").start();
new Thread(new Runnable() { // 美国人
public void run() {
synchronized ("资源2") { // 美国人拿到了筷子
System.out.println(Thread.currentThread().getName() + ":得不到资源1,就不释放资源2");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized ("资源1") {
System.out.println(Thread.currentThread().getName() + ":得到资源1,释放资源2");
}
}
}
}, "线程B").start();
}
}
DeadLockDemo
死锁:
正常:
6.等待唤醒机制
wait:告诉当前线程放弃执行权,并放弃监视器(锁)并进入阻塞状态,直到其他线程持有获得执行权,并持有了相同的监视器(锁)并调用notify为止。
notify:唤醒持有同一个监视器(锁)中调用wait的第一个线程,例如,餐馆有空位置后,等候就餐最久的顾客最先入座。注意:被唤醒的线程是进入了可运行状态。等待cpu执行权。
notifyAll:唤醒持有同一监视器中调用wait的所有的线程。
①notify
通过等待唤醒机制调节了线程之间的执行顺序
public class WaitNotifyDemo {
public static void main(String[] args) {
Student s = new Student();
s.setName("Tom");
s.setGender('男');
new Thread(new Ask(s)).start();
new Thread(new Change(s)).start();
}
} class Change implements Runnable {
private Student s;
public Change(Student s) {
this.s = s;
} @Override
public void run() {
while (true) {
synchronized (s) {
if (s.flag)
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
} if (s.getGender() == '男') {
s.setName("Amy");
s.setGender('女');
} else {
s.setName("Tom");
s.setGender('男');
}
s.flag = true;
// 唤醒在等待的线程
s.notify();
}
}
}
} class Ask implements Runnable {
private Student s;
public Ask(Student s) {
this.s = s;
} @Override
public void run() {
while (true) {
synchronized (s) {
if (!s.flag)
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是" + s.getName() + ",我是" + s.getGender());
s.flag = false;
s.notify();
}
}
}
} class Student { private String name;
private char gender;
// 标记位---规定flag为true,执行ask线程,如果flag为false执行change
public boolean flag = true; public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public char getGender() {
return gender;
} public void setGender(char gender) {
this.gender = gender;
} }
WaitNotifyDemo
②notifyAll
线程在等待期间是在这个锁所对应的线程池中等待的。线程池本质上是一个存储线程的队列。
若是用notify,唤醒队列中的第一个线程,执行方式如下:
第一个括号代表就绪的线程,第二个括号代表线程池中的线程。
(a1,a2,c1,c2)() -> a1 running-> (a1,a2,c1,c2)() -> a1 running -> (a2,c1,c2)(a1) -> a2 running -> (c1,c2)(a1,a2) -> c1 running -> (a1,c1,c2)(a2) -> c1 running -> (a1,c2)(a2,c1) -> c2 running -> (a1)(a2,c1,c2) -> a1 runnig -> (a1,a2)(c1,c2) -> a1 running -> (a2)(c1,c2,a1) -> a2 running -> ()(c1,c2,a1,a2)
最后,所有线程都在线程池中阻塞,发送死锁。此时需要用到notifyAll。
public class WaitNotifyAllDemo {
public static void main(String[] args) {
Student s = new Student();
s.setName("Tom");
s.setGender('男');
new Thread(new Ask2(s)).start();
new Thread(new Ask2(s)).start();
new Thread(new Change2(s)).start();
new Thread(new Change2(s)).start();
}
}
class Change2 implements Runnable {
private Student s;
public Change2(Student s) {
this.s = s;
} @Override
public void run() {
while (true) {
synchronized (s) {
while (s.flag)
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
} if (s.getGender() == '男') {
s.setName("Amy");
s.setGender('女');
} else {
s.setName("Tom");
s.setGender('男');
}
s.flag = true;
// 唤醒在等待的线程
s.notifyAll();
}
}
}
} class Ask2 implements Runnable {
private Student s;
public Ask2(Student s) {
this.s = s;
}
@Override
public void run() { while (true) {
synchronized (s) { while (!s.flag)
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
} System.out.println("我是" + s.getName() + ",我是" + s.getGender());
s.flag = false;
s.notifyAll();
}
}
}
}
WaitNotifyAllDemo
sleep与wait的区别:
①sleep在使用的时候需要指定休眠时间,到点自然醒。释放执行权,不释放锁。是一个静态方法,设计在了Thread类上
②wait在使用的时候可以指定等待时间,也可以不指定,如果不指定等待时间就需要唤醒。释放执行权,释放锁。是一个非静态方法,设计在了Object类上
例:生产消费模型
一个线程表示生产者Producer,一个线程表示消费者Consumer,商品的总数量不超过1000。
public class ProductAndConsumer { public static void main(String[] args) { Product p = new Product(); new Thread(new Producer(p)).start();
new Thread(new Consumer(p)).start(); } } class Producer implements Runnable { private Product p; public Producer(Product p) {
this.p = p;
} @Override
public void run() { while (true) { synchronized (p) { while (p.flag) {
try {
p.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} // 计算本次商品所能生产的最大数量
int max = 1000 - p.getCount(); // 计算本次生产的商品数量
int count = (int) (Math.random() * (max+1)); // 本次提供的商品的总数量
p.setCount(p.getCount() + count); System.out.println("本次生产数量:" + count + ", 本次提供商品数量为:" + p.getCount()); p.flag = true;
p.notify(); } }
} } class Consumer implements Runnable { private Product p; public Consumer(Product p) {
this.p = p;
} @Override
public void run() { while (true) {
synchronized (p) { while (!p.flag) {
try {
p.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} // 计算本次消费的数量
int count = (int) (Math.random() * (p.getCount() + 1)); // 计算本次的剩余数量
p.setCount(p.getCount() - count); System.out.println("本次消费数量:" + count + ", 本次剩余商品数量:" + p.getCount()); p.flag = false;
p.notify();
}
} } } class Product { private int count;
public boolean flag = false; public int getCount() {
return count;
} public void setCount(int count) {
this.count = count;
} }
ProductAndConsumer
7.守护线程
守护别的线程。当被守护的线程结束,守护线程无论执行完成与否都得随之结束。
一个线程要么是守护线程要么是被守护的线程。守护线程是随着最后一个被守护线程的结束而结束,例如GC。
public class DaemonDemo { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new Soilder(), "小兵1号");
Thread t2 = new Thread(new Soilder(), "小兵2号");
Thread t3 = new Thread(new Soilder(), "小兵3号");
Thread t4 = new Thread(new Soilder(), "小兵4号"); // 设置为守护线程
t1.setDaemon(true);
t2.setDaemon(true);
t3.setDaemon(true);
t4.setDaemon(true); t1.start();
t2.start();
t3.start();
t4.start(); for (int i = 10; i > 0; i--) {
System.out.println("Boss掉了一滴血,剩余" + i);
Thread.sleep(150);
}
} } class Soilder implements Runnable { @Override
public void run() { for (int i = 100; i > 0; i--) {
System.out.println(Thread.currentThread().getName() + "掉了一滴血,剩余" + i);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
} } }
DaemonDemo
8.线程的优先级
线程的优先级分为1-10,理论上数字越大等级越高,这个线程抢到资源的几率就越大。相邻的两个线程的优先级的差异性不明显。至少要相差5个等级才能体现的相对明显一点点。
public class PriorityDemo {
public static void main(String[] args) { Thread t1 = new Thread(new PDemo(), "A");
Thread t2 = new Thread(new PDemo(), "B"); t1.setPriority(1);
t2.setPriority(10); t1.start();
t2.start(); // 获取线程的优先级
// System.out.println(t1.getPriority());
// System.out.println(t2.getPriority()); }
} class PDemo implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
PriorityDemo