线程
1、基本概念:程序 线程 进程
程序:
进程:
线程:
进程可以进一步化为多个线程,是一个程序内的一条执行路径
若一个进程同一时间并行多个线程,就是支持多线程
一个Java程序至少有三个线程 main() 主线程 gc 垃圾回收线程 异常处理线程
1、并行和并发
- 并行:多个cpu同事执行多个任务
-
2、多线程的优点
提高应用程序的响应
- 提高计算cpu的使用效率
-
2、线程的创建和使用
jvm允许程序运行多个线程,它通过java.lang.Thread类来体现
Thread类的特点
每个线程都是通过特定的Thread类的对象的run()方法来完成操作,经常把run()方法的主体叫线程体
-
1、线程的创建
方式一:继承Thread类
步骤
- 继承Thread类
- 重写Thread类的run()方法
- 创建Thread的子类对象
- 调用子类对象的start()方法
```java
public class ThreadTest {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start();
//匿名子类对象
new Thread(){
@Override
public void run() {
} }.start();for (int i = 0; i < 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
} }
class MyThread extends Thread { @Override public void run() { for (int i = 0; i < 100; i++) { if (i % 2 == 0) { System.out.println(Thread.currentThread().getName() + “:” + i); } } } }
线程的常用方法
1. start() 启动当前线程,调用线程的run()方法
1. run() 通常需要重写Thread类中的run方法 、将创建线程要执行的操作申明在此方法中
1. getName() 获取线程名称
1. setName() 设置线程名称
1. yield() 释放当前cpu的执行权
1. join() 在线程a中调用线程b的join()方法,线程a进入阻塞状态,知道线程b执行完毕,线程a才结束阻塞状态
1. stop() 已过时 强制结束当前线程
1. sleep() 让当前线程睡眠XXX毫秒,指定时间内线程是阻塞状态
1. isAlive() 判断当前线程是否存活
<a name="ytn8Q"></a>
#### 方式二:实现 Runnable 接口
1. 创建实现Runnable接口的类
1. 实现类去实现Runnable中的抽象方法run()
1. 创建实现类的对象
1. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
1. 调用Thread类对象的run()方法
```java
public class ThreadTest1 {
public static void main(String[] args) {
MyThread1 m1 = new MyThread1();
Thread t1 = new Thread(m1);
t1.start();
Thread t2 = new Thread(m1);
t2.start();
}
}
class MyThread1 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
方式三:实现Callable接口
与Runnable 相比 Callable更强大
- 相比run方法 可以有返回值
- 方法可以抛出异常
- 支持泛行的返回值
- 需要借助FutureTask类,比如获取返回结果
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
1.创建Callable的实现类
2.实现call方法,并将线程需要执行的操作放在call方法内
3.创建Callable接口实现类的对象
4.将Callable接口实现类的对象作为参数传递到 FutureTask 的构造器当中,创建FutureTask的对象
5.将FutureTask的对象作为参数传递到Thread的构造器当中创建Thread对象并掉用start()方法
6.获取Callable call 方法的返回你
*/
public class CallableTest {
public static void main(String[] args) {
ThreadNew tn = new ThreadNew();
FutureTask task = new FutureTask(tn);
new Thread(task).start();
try {
Object o = task.get();
System.out.println(o);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class ThreadNew implements Callable {
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
sum += i;
}
}
return sum;
}
}
方式四:线程池
- 提高响应速度
- 降低资源消耗
- 便于线程管理 ```java import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor;
public class ThreadPool { public static void main(String[] args) { //1.提供执行数量的线程池 ExecutorService eService = Executors.newFixedThreadPool(10); ThreadPoolExecutor service = (ThreadPoolExecutor) eService; service.setCorePoolSize(15); //2.执行指定线程的操作,需要实现Runnable或者Callable接口 service.execute(new MyThreadNew());//适用于 Runnable //service.submit();//适用于 Callable //关闭线程池 service.shutdown(); } }
class MyThreadNew implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(i);
}
}
}
}
<a name="cVyJG"></a>
#### 如何理解实现Callable接口创建多线程比实现Runnable接口创建多线程功能强大
1. call() 有返回值
1. call() 可以抛出异常
1. Callable支持泛型
<a name="NQTva"></a>
#### 线程优先级
MIN_PRIORITY 1<br />MAX_PRIORITY 10<br />NORM_PRIORITY 5
> 高优先级线程要抢占低优先级线程cpu的执行权,高优先级的线程高概率优先执行,并不意味着高优先级执行完以后才执行低优先级
<br />
<a name="8f6dede4"></a>
#### 比较线程创建的两种方式
1. 开发中优先使用 实现 Runnable 接口 没有单继承的限制
1. 实现方式更适合来处理多个线程有共享数据的情况
<a name="h2p6M"></a>
#### 二者之间的联系
1. Thread 类也实现了Runnable 接口
1. 都需要重写 run()方法,将线程要执行的逻辑写在run()方法中
<a name="128fl"></a>
## 3、线程的生命周期
<a name="Fjs2r"></a>
### 新建
<a name="HCvui"></a>
### 就绪
<a name="dXf6x"></a>
### 运行
<a name="jiENc"></a>
### 阻塞
<a name="iXCtM"></a>
### 死亡
<a name="rIK9g"></a>
## 4、线程的同步
> 好处:解决线程安全的问题。
> 局限性:操作同步代码时只能有一个线程参与,相当于但线程,效率低
<a name="PPTsN"></a>
### 方式一:同步代码块
synchronized (同步监视器){<br />//需要被同步的代码<br />}<br />说明:需要操作共享数据的代码,极为需要被同步的代码块<br />同步监视器:俗称锁,任何一个类的对象,都可以充当锁<br />要求所有线程都使用同一把锁。
```java
public class WindowTest {
public static void main(String[] args) {
Window w = new Window();
Thread t1 = new Thread(w);
t1.setName("窗口一");
t1.start();
Thread t2 = new Thread(w);
t2.setName("窗口二");
t2.start();
Thread t3 = new Thread(w);
t3.setName("窗口三");
t3.start();
}
}
class Window implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
//这里可以是用this或者当前类的对象 Window.class
synchronized (this) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
方式二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明为同步方法
public class SingletonTest {
public static void main(String[] args) {
System.out.println(MySingle.getInstance());
System.out.println(MySingle.getInstance());
}
}
class MySingle{
private static MySingle instance = null;
private MySingle() {
}
public static synchronized MySingle getInstance() {
if (instance == null ){
instance = new MySingle();
}
return instance;
}
}
死锁的问题: 不同的线程分别占用对方需要的资源不放弃,都在等待对方放弃自己所的资源,就形成了死锁。 出现死锁后不会出现异常,不会出现提示,只是所有的线程都处在阻塞状态,无法继续。
解决方法:
public class WindowTest3 { public static void main(String[] args) { Window3 w = new Window3(); Thread t1 = new Thread(w); t1.setName(“窗口一”); t1.start(); Thread t2 = new Thread(w); t2.setName(“窗口二”); t2.start(); Thread t3 = new Thread(w); t3.setName(“窗口三”); t3.start(); } }
class Window3 implements Runnable { private int ticket = 100; private ReentrantLock lock = new ReentrantLock(true); @Override public void run() { while (true) { try { lock.lock(); if (ticket > 0) { System.out.println(Thread.currentThread().getName() + “:” + ticket); ticket—; } else { break; } } finally { lock.unlock(); } } } }
<a name="bHTyE"></a>
### synchronized 和lock的异同?
相同: 都可以解决线程安全问题<br />不同: synchronized在执行响应的同步代码块以后,自动的释放同步监视器,lock需要手动启动同步,同时结束也需要手动实现,<br />lock 只有代码块锁,synchronized既有代码块锁,也有方法锁。
<a name="DFYqV"></a>
## 5、线程的通信
<a name="bR87D"></a>
### 练习:交替打印
```java
public class NumberTest {
public static void main(String[] args) {
Number n = new Number();
Thread t1 = new Thread(n);
t1.setName("线程一:");
Thread t2 = new Thread(n);
t2.setName("线程二:");
t1.start();
t2.start();
}
}
class Number implements Runnable {
private int num = 1;
@Override
public void run() {
while (true) {
synchronized (this) {
notify();
if (num <= 100) {
System.out.println(Thread.currentThread().getName() + ":" + num);
num++;
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
涉及到三个方法:
- wait() 一旦执行此方法,线程就会进入阻塞状态,同时释放锁
- notify() 执行此方法,就会唤醒一个被wati的线程 ,优先级高的优先被唤醒
- notifyAll() 执行此方法,就会唤醒所有被wait的线程
说明: 1. wait() notify() notifyAll() 三个方法只能使用在同步方法、和同步代码块当中。 2.三个方法的调用者必须是同步代码块或 3.三个方法定义在Object类当中
面试题1:sleep 和wait的异同
相同点:一旦执行方法,都可以是当前线程进入阻塞状态。
不同点:1. 声明的位置不同
2.调用的范围要求不一样,sleep 可以在任何需要的场景调用,wait方法必须使用在同步方法和同步代码块中
3.两个方法都是用在同步方法块和同步方法中,sleep不会释放锁,wait会释放锁。