目录
1.什么是线程
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
线程,被称为轻量进程(light weight processes),进程中的一条执行路径,也是CPU的基本调度单位。一个进程由一个或多个线程组成,彼此间完成不同的工作,同时执行,称为多线程。
进程与线程的区别:
1. 进程是操作系统资源分配的基本单位,而线程是 CPU 的基本调度单位。2. 一个程序运行后至少有一个进程。3. 一个进程可以包含多个线程,但是至少需要有一个线程,否则这个进程是没有意义。4. 进程间不能共享数据段地址,但是同进程的线程之间可以。
2.线程的组成
任何一个线程都具有基本的组成部分 :
CPU时间片: 操作系统(OS)会为每个线程分配执行时间
运行数据:堆空间: 存储线程需要的对象,多个线程可以共享堆中的数据。
栈空间: 存储线程需使用的局部变量,每个线程都拥有独立的栈。线程的逻辑代码.
线程的特点:
1、线程抢占式执行
public class MyThread extends Thread{
private int ticket = 100;
@Override
public void run(){
while (true){
if(ticket>0){
ticket--;
System.out.println(Thread.currentThread().getName()+"卖了一张票,还有"+ticket+"张");
}else {
break;
}
}
}
}
Test:
public class Test {
public static void main(String[] args) {
MyThread myThread1 = new MyThread();
myThread1.setName("窗口a");
MyThread myThread2 = new MyThread();
myThread2.setName("窗口b");
MyThread myThread3 = new MyThread();
myThread3.setName("窗口c");
MyThread myThread4 = new MyThread();
myThread4.setName("窗口d");
myThread1.start();
myThread2.start();
myThread3.start();
myThread4.start();
}
}
Thread类:
public class TicketRunnable implements Runnable{
private int ticket = 100;
@Override
public void run() {
while (true){
//锁
synchronized (this) {
if (ticket > 0) {
ticket--;
System.out.println(Thread.currentThread().getName() +"卖了一张票,还有"+ticket+"张");
} else {
break;
}
}
}
}
}
public class Test {
public static void main(String[] args) {
//创建任务对象
TicketRunnable tr = new TicketRunnable();
//创建线程对象并指定要执行的任务
Thread t1 = new Thread(tr,"窗口a");
Thread t2 = new Thread(tr,"窗口b");
Thread t3 = new Thread(tr,"窗口c");
Thread t4 = new Thread(tr,"窗口d");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
public class TestBank {
public static void main(String[] args) {
BankCard bankCard = new BankCard(0);
SaveMoney save = new SaveMoney(bankCard);
TakeMoney take = new TakeMoney(bankCard);
Thread t1 = new Thread(save,"张三");
Thread t2 = new Thread(take,"李四");
t1.start();
t2.start();
}
}
//取钱任务
class TakeMoney implements Runnable{
private BankCard card;
public TakeMoney(BankCard card){
this.card = card;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if(card.getBalance()>=1000) {
card.setBalance(card.getBalance() - 1000);
System.out.println(Thread.currentThread().getName() + "取了卡中1000元,卡中余额为:" + card.getBalance());
}else {
System.out.println("卡中没钱了,赶紧存钱");
i--;
}
}
}
}
//存钱任务
class SaveMoney implements Runnable{
private BankCard card;
public SaveMoney(BankCard card){
this.card = card;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
card.setBalance(card.getBalance()+1000);
System.out.println(Thread.currentThread().getName()+"向卡中存了1000元,卡中余额为:"+card.getBalance());
}
}
}
//抽取类:--银行卡类
class BankCard{
private double balance;
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public BankCard(double balance) {
this.balance = balance;
}
}
获取线程 ID 和线程名称1. 在 Thread 的子类中调用 this.getId () 或 this.getName ()2. 使用 Thread.currentThread (). getId () 和Thread.currentThread(). getName ()修改线程名称1. 调用线程对象的 setName () 方法2. 使用线程子类的构造方法赋值
线程的状态:
常见的方法:
public class TestSleep {
public static void main(String[] args) {
T t = new T();
t.start();
for (int i = 0; i < 20; i++) {
System.out.println("main线程========"+i);
}
}
}
class T extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(100); //休眠当前线程 单位 毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("=========="+i);
}
}
}
public class TestYield {
public static void main(String[] args) {
T2 t01 = new T2();
T2 t02 = new T2();
t01.start();
t02.start();
}
}
class T2 extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
//当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片
Thread.yield(); //可能会导致t01与t02交替的频率变高
System.out.println(Thread.currentThread().getName()+"==============="+i);
}
}
}
//t1,t2,t3 如何保证这三个线程有序得值,t1执行完t2执行,t2执行完t3执行
public class TestJoin {
public static void main(String[] args) {
try {
T3 t3 = new T3();
t3.start();
//将t3线程加入当前main线程中,直到t3执行完后,执行main
t3.join();
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"========="+i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class T3 extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"==========="+i);
}
}
}
public class TestDaemon {
public static void main(String[] args) {
T4 t4 = new T4();
//设置t4为守护线程,当前台线程执行完后,守护线程也自动结束(没执行完也会强制结束)
t4.setDaemon(true);
t4.start();
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"==========="+i);
}
}
}
class T4 extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"==========="+i);
}
}
}
4.线程的安全问题
public class TestSaft {
private static String[] arr = new String[5];
private static int index = 0;
public static void main(String[] args) throws InterruptedException {
//匿名对象
Runnable hello = new Runnable() {
@Override
public void run() {
if (arr[index]==null){
arr[index]="hello";
index++;
}
}
};
Runnable world = new Runnable() {
@Override
public void run() {
if (arr[index]==null){
arr[index]="world";
index++;
}
}
};
Thread t1 = new Thread(hello);
Thread t2 = new Thread(world);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(Arrays.asList(arr));
//结果会有以下四种
//1. [hello, world, null, null, null]
//2. [world, hello, null, null, null]
//3. [hello, null, null, null, null]
//4. [world, null, null, null, null]
}
}
5.同步操作
同步代码块:
synchronized(临界资源对象){ //对临界资源对象加锁
//代码(原子操作)
}
- 每个对象都有一个互斥锁标记,用来分配给线程的
- 只有拥有对象互斥锁标记的线程,才能进入对该对象加锁的同步代码块
- 线程退出同步代码块时,会释放相应的互斥锁标记
6.线程死锁
当A线程拥有锁资源a时,这时A线程需要锁资源b, 而B线程拥有锁资源b,这时B线程需要锁资源a, 这样会导致A等待B线程释放资源b, B线程等待A线程释放锁资源a。 从而二个处于永久等待。从而操作死锁。
例子: 情人节---两个情人去餐厅吃饭---必须具有两个筷子。男方拥有一根筷子,女方拥有另一个筷子。
定义两个锁:
public class LockObject {
public static Object a = new Object();
public static Object b = new Object();
}
男方:
public class Boy extends Thread{
@Override
public void run() {
synchronized (LockObject.a){
System.out.println(Thread.currentThread().getName()+"得到第一根筷子a");
synchronized (LockObject.b){
System.out.println(Thread.currentThread().getName()+"得到第二根筷子b");
System.out.println("可以吃饭了");
}
}
}
}
女方:
public class Girl extends Thread{
@Override
public void run() {
synchronized (LockObject.b){
System.out.println(Thread.currentThread().getName()+"得到第一根筷子b");
synchronized (LockObject.a){
System.out.println(Thread.currentThread().getName()+"得到第二根筷子a");
System.out.println("可以吃饭了");
}
}
}
}
Test测试类:
public class TestDeadlock {
public static void main(String[] args) {
Boy boy = new Boy();
Girl girl = new Girl();
boy.setName("张三");
girl.setName("李四");
boy.start();
girl.start();
}
}
运行结果:
可以看到二者僵持住了,没有进行下一步,也没有结束运行,死锁了。
操作死锁的原因: 锁与锁之间有嵌套导致。
如何解决死锁:
1. 尽量减少锁得嵌套。
2. 可以使用一些安全类。
3. 可以使用Lock中得枷锁,设置枷锁时间。
7.线程通信
线程通信中使用得方法有哪些?
例子: 存钱和取钱。
银行卡类:
public class BankCard {
private double balance;
//true:表示有钱 false:表示没钱
private boolean flag = false;
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
//存钱
public synchronized void save(double money){
if(flag==true){
try{
//进入等待队列
this.wait();
}catch (Exception e){
e.printStackTrace();
}
}
this.balance = this.balance+money;
System.out.println(Thread.currentThread().getName()+"存了"+money+"余额为:"+this.balance);
flag = true;
//唤醒等待队列中的某个线程 this.notifyAll() 唤醒等待队列中的所有线程
this.notify();
}
//取钱
public synchronized void take(double money){
if(flag==false){
try{
this.wait();//使该线程进入等待队列
}catch (Exception e){
e.printStackTrace();
}
}
this.balance = this.balance-money;
System.out.println(Thread.currentThread().getName()+"取了"+money+"余额为:"+this.balance);
flag = false;
this.notify();
}
}
男方
public class Boy implements Runnable{
private BankCard bankCard;
public Boy(BankCard bankCard){
this.bankCard = bankCard;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
bankCard.save(1000);
}
}
}
女方
public class Girl implements Runnable{
private BankCard bankCard;
public Girl(BankCard bankCard){
this.bankCard = bankCard;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
bankCard.take(1000);
}
}
}
测试类
public class Test {
public static void main(String[] args) {
BankCard bankCard = new BankCard();
Boy boy = new Boy(bankCard);
Girl girl = new Girl(bankCard);
Thread t1 = new Thread(boy,"张三");
Thread t2 = new Thread(girl,"李四");
t1.start();
t2.start();
}
}
运行结果
先存钱---再取钱
思考: sleep和wait得区别?
1.所在得类不同。sleep属于Thread类,wait属于Object类。
2.使用的地方: sleep可以使用再任何代码块。wait只能再同步代码块中。
3.是否释放锁资源: sleep不释放锁资源,wait会释放锁资源。
4.sleep时间到了自动唤醒,wait必须需要使用notify和notifyAll唤醒
notify和notyfyAll区别?
8.线程池
什么是线程池?
该池子中预先存储若干个线程对象。整个池子就是线程池。
线程池的创建方式有哪些?
所有的线程池---封装了一个父接口---java.util.concurrent.Executor.
它的实现接口: ExecutorService.
有一个工具类。Executors可以帮你创建相应的线程池。
[1] 创建单一线程池 newSingleThreadExecutor()
[2] 创建定长线程池。newFixedThreadPool(n);
[3] 创建可变线程池. newCachedThreadPool()
[4] 创建延迟线程池 .newScheduledThreadPool(n);
单一线程池 newSingleThreadExecutor()
public class Test01 {
public static void main(String[] args) {
// 单一线程池:适用于 队列要求线程有序执行
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 5; i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"~~~~~~~~~~~~~~~~~~");
}
});
}
//关闭线程池。需要等待线程池中任务执行完毕后才会关闭。
executorService.shutdown();
}
}
public static void main(String[] args) {
//固定长度的线程池对象
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 5; i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"~~~~~~~~~~~~~~~~~~");
}
});
}
//关闭线程池。需要等待线程池中任务执行完毕后才会关闭。
executorService.shutdown();
}
}
可变线程池. newCachedThreadPool()
public class Test01 {
public static void main(String[] args) {
//可变长度的线程池
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"~~~~~~~~~~~~~~~~~~");
}
});
}
//关闭线程池。需要等待线程池中任务执行完毕后才会关闭。
executorService.shutdown();
}
}
延迟线程池 .newScheduledThreadPool(n); 延迟时间结束后返回结果
public class Test01 {
public static void main(String[] args) {
//延迟线程池对象
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
for (int i = 0; i < 5; i++) {
executorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"~~~~~~~~~~~~~~~~~~");
}
},10, TimeUnit.SECONDS);//设置延迟时间 10 单位秒
}
//关闭线程池。需要等待线程池中任务执行完毕后才会关闭。
executorService.shutdown();
}
}
9.使用最原始的方式创建线程池
上面讲解的使用Executors创建线程池的方式,都是使用底层ThreadPoolExecutor,而阿里开发手册,建议使用最原始的方式。
线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
ThreadPoolExecutor 执行流程:
- 线程数小于核心线程数时,创建线程。
- 线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
- 线程数大于等于核心线程数,且任务队列已满:若线程数小于最大线程数,创建线程;若线程数等于最大线程数,抛出异常,拒绝任务。
ThreadPoolExecutor 创建的线程池执行流程:
10.创建线程的第三种方式(Callable)
实现Callable接口,它和实现Runnable接口差不多,只是该接口种的方法有返回值和异常抛出。
public class Test {
public static void main(String[] args) throws Exception{
//自建创建线程对象并提交Callable类型的任务是比较麻烦的,需要封装到一个FutureTask类种, 建议使用线程池来提交任务
My my = new My();
FutureTask futureTask = new FutureTask(my);
Thread t1 = new Thread(futureTask);
t1.start();
System.out.println(futureTask.get());
ExecutorService executorService = Executors.newFixedThreadPool(5);
Future<Integer> future = executorService.submit(my);
//需要等线程执行完毕后,才会把结果返回给该变量
Integer sum = future.get();
}
}
class My implements Callable<Integer>{
//1-100的和
@Override
public Integer call() throws Exception {
int sum=0;
for (int i=1;i<=100;i++){
sum+=i;
}
return sum;
}
}
11.手动锁
Lock它是手动锁的父接口,它下面有很多实现类。
lock()方法。
unlock()释放锁资源,放在finally中
public class Test {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Thread t1 = new Thread(ticket,"窗口A");
Thread t2 = new Thread(ticket,"窗口B");
Thread t3 = new Thread(ticket,"窗口C");
Thread t4 = new Thread(ticket,"窗口D");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Ticket implements Runnable{
private int ticket = 100;
Lock s = new ReentrantLock();
@Override
public void run() {
while (true){
try {
s.lock();//查看释放获取锁资源
if (ticket > 0) {
--ticket;
System.out.println(Thread.currentThread().getName() + "卖了一张,剩余:" + ticket + "张");
} else {
break;
}
}finally {
s.unlock(); //释放锁
}
}
}
}