【java学习笔记】线程

时间:2023-11-12 09:35:50

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.线程的状态

【java学习笔记】线程

创建:新创建了一个线程对象。

就绪:线程对象创建后,其他线程调用了该对象的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

成功的情况:

  【java学习笔记】线程

失败的情况:

  【java学习笔记】线程

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

死锁:

  【java学习笔记】线程

正常:

  【java学习笔记】线程

 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