1.谈谈你对程序、进程、线程的理解
程序:为完成特定的任务、用某种语言编写的一组指令的集合。即指一段静态的代码
进程:是程序的一次执行过程、正在运行的一个程序。作为资源分配的单位
线程:是一个程序内部的一条执行路径,作为调度和执行的最小单位,每个线程都拥有独立的运行栈,和程序计数器 (pc)
2.线程
线程是jvm调度的最小单元,也叫做轻量级进程,进程是由线程组成,线程拥有私有的程序技术器以及栈,并且能够访问堆中的共享资源。
3.多线程的创建
3.0线程的声明周期
新建状态:
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
就绪状态:
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
运行状态:
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
阻塞状态:
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态
死亡状态:
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
3.1、继承于Thread类
- 1.创建一个继承于Thread类的子类
- 2.重写Thread的run()—>将此线程执行的操作声明在run()中
- 3.创建Thread类的子类的对象
- 4.通过对象调用start():①启动当前线程② 调用当前线程的run()
//1.创建一个继承于Thread类的子类
class MyThread extends Thread {
//2.重写Thread的run()
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
// 3.创建Thread类的子类的对象
MyThread t1 = new MyThread();
//4.通过对象调用start()
t1.start();
// t1.run();错误的
//如下方法任在主线程中进行的
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
//获取当前的线程名
System.out.println(Thread.currentThread().getName() + ":" +i + "*************main()**********");
}
}
}
}
3.2、方式二:实现Runnable接口
- 1.创建一个实现了Runnable的类
- 2.实现类去实现Runnnable的抽象方法,run()
- 3.创建实现类的对象
- 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
- 5.通过Thread类的对象调用start()
//1.创建一个实现了Runnable的类
class MThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadTest1 {
// 3.创建实现类的对象
public static void main(String[] args) {
MThread mThread = new MThread();
//4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
Thread t1 = new Thread(mThread);
t1.setName("线程1");
// 5.通过Thread类的对象调用start()①启动线程②调用当前线程的run();-->调用了Runnable类型的target
t1.start();
//在开启一个线程
Thread t2 = new Thread(mThread);
t2.setName("线程2");
t2.start();
}
}
3.3 说明两个问题:
问题一:我们启动一个线程,必须调用start()方法,不能调用run()方法的方式启动线程
问题二:如果再启动一个线程,必须重新创建一个Thread子类的对象,调用此对象的start()方法
3.4、Thread的常用方法
-
1.start():启动当前线程,调用当前线程的run()
-
2.run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
-
3.currentThread():静态方法,返回执行当前代码的线程
-
4.getName():获取当前线程的名字
-
5.setName():设置当前线程的名字
-
6.yield():释放当前CPU的执行权
-
7.join():在线程a中调用线程b的join()方法,此时线程a就进入阻塞状态,直到线程b完成执行完后,线程a才结束阻塞状态
-
8.stop():强制结束当前线程,不推荐使用,已过时
-
9.sleep(long millitime):让当前线程睡眠(阻塞),指定毫秒数,在毫秒数时间内当前的线程是阻塞状态
-
1.isAlive():判断当前线程是否存活
-
-
线程的优先级
-
-
MAX_PRIORITY:10
-
MIN_PRIORITY:1
-
NORM_PRIORITY:5 默认的优先级
-
2.如何获取和设置线程的优先级:
-
getPriority():获取线程的优先级
-
setPriority(int p):设置线程的优先级
说明:高优先级的线程要抢占低优先级的CPU的执行权,但是只是概率上来讲,高优先级的线程高概率下被执行,并不一定高优先级的线程执行完后,低优先级才执行
3.5、 例子:创建三个窗口买票,总票数为100张,使用Runable接口的方式
class Window extends Thread implements Runnable {
private static int ticket = 100;
Object obj = new Object();
@Override
public void run() {
while (true) {
//解决线程安全问题
synchronized (obj){
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":买票,票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class WindowTest {
public static void main(String[] args) {
Window w = new Window();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
上述操作存在线程安全问题,待解决
-
1。买票过程 中,出现了重票、错票
-
2.问题出现的原因:当某个线程操作车票时,尚未操作完成,其他线程参与进来,也操作买票
-
3.如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来,直到线程a操作完ticket的时候,其他线程才
-
可以开始操作ticket,即使线程a出现了阻塞也不可以被改变
-
4.在java中通过同步机制,解决线程安全问题
-
方式一:同步代码块 synchronized(同步监视器){
-
//需要被同步的代码
-
}
说明:1.操作共享数据的代码,即为需要被同步代码2.共享数据:多个线程共同操作的变量
3.同步监视器:俗称:锁。任何一个类的对象都可以充当锁
要求:多个必须公用同一把锁* 补充:使用Runable方式创建多线程的方式中,可以考虑使用this充当同步监视器
在使用Thread类的方式创建线程的方式中,要慎用this充当同步监视器,可以考虑使用当前类充当同步监视器
方法二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将这个方法声明同步的。
5.使用同步的方式,解决了线程的安全问题 --好处
操作同步代码时,只有一个线程参与,其他线程等待,相当于一个单线程的过程,效率低
解决线程安全问题三:lock锁----JDK5.0新增
3.6、使用同步机制将单例模式中的懒汉式改写成线程安全的
1.面试题:synchronized 和 lock有何异同
*
- 相同点:解决线程的安全问题
- 不同:synchronized机制在执行完相应的同步代码后,自动释放同步监视器
-
lock需要手动开启同步(lock()),同时结束同步也需要手动的实现(unlock());
*2.优先使用顺序:灵活度递减
- lock锁—>同步代码块(已经进入了方法体,分配了相应资源)---->同步方法(在方法体之外)
- lock是否还存在同步监视器,不存在,lock相当于同步监视器
public class BankTest {
private BankTest() {
}
private static BankTest instance = null;
public static BankTest getInstance() {
//方式一:效率稍差
// synchronized (BankTest.class) {
//
// if (instance == null) {
// instance = new BankTest();
// }
// return instance;
// }
//方式二:效率更高
if (instance == null){
synchronized (BankTest.class){
if (instance ==null){
instance = new BankTest();
}
}
}
return instance;
}
}
3.7、演示线程死锁问题
-
1.死锁的理解:不同的线程分别占用对方需要的不同资源不放弃,
-
都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
-
2.说明:
-
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
public class ThreadTest {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread() {
@Override
public void run() {
synchronized (s1) {
s1.append("a");
s2.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2) {
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2) {
s1.append("c");
s2.append("3");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1) {
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
4、线程通信
线程的通信例子:使用两个线程打印1-100,线程1和线程2,互打印
设计到的三个方法:只能出现在同步代码块和同步方法中
- wait():一旦执行此方法,当前线程就会进入阻塞状态,并释放同步监视器
- notify():一旦执行此方法,就会唤醒被wait阻塞的一个线程,如果有多个线程被wait,就唤醒优先级高的那个
- notifyAll():一旦执行此方法,就会唤醒beiwait阻塞的所有线程。
说明:
说明:1.这三个方法都必须使用在同步代码块和同步方法中
2.这三个方法的调用者必须是同步代码块或同步方法中的同步监视器。
否则,会出现异常报错
3.这三个方法都定义在java.lang.Object中
- 同步代码块涉及到同步监视器和共享数据,
- synchronized(同步监视器){
- 操作数据的代码,保证在操作数据的过程中,是个单线程,就不会产生线程安全问题
- }
- 多个线程共同操纵的数据:共享数据,一个线程操作时如果另一个线程参与就会产生线程安全问题
面试题:sleep和wait的异同
- 相同点:一旦执行方法都可以使当前线程进入阻塞状态
- 不同点:1.两个方法声明的位置就不同 :sleep()声明在Thread类中,wait()声明在Object类中
- 2.调用的要求不同:sleep()可以在任何需要的场景下调用。wait()只能用在同步代码块和同步方法中
- 3.关于是否释放同步监视器的问题:如果两个方法中使用在同步代码块和同步方法中,sleep不会释放锁,wait()会
class Number implements Runnable{
private int number =1;
@Override
public void run() {
while (true){
synchronized (this) {
//让线程进入就绪状态
notify();
if (number <= 100){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +":" + number);
number++;
try {
//使得调用如下wait()方法的线程,进入阻塞状态
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
break;
}
}
}
}
}
public class CommunicationTest {
public static void main(String[] args) {
Number m1 = new Number();
Thread t1 = new Thread(m1);
Thread t2 = new Thread(m1);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
5. 创建线程的方式三:实现Callable接口 ----JDK5.0新增
- 1.创建一个实现Callable接口的实现类
- 2.实现call()方法,将此线程需要执行的操作声明在call()中
- 3.创建一个Callable接口实现类的对象
- 4.将此callable实现类的对象传递到FutureTask构造器中,创建FutureTas的对象
- 5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread类的对象,调用start()方法
- 6.获取Callable中call的方法作为返回值
- 如果理解实现Callable接口的方式创建多线程比Runnable接口的方式创建多线程强大?
- 1.call()可以有返回值
- 2.call()可以抛出异常,被外面的操作捕获,获取异常的信息
- 3Callable是支持泛型的
//1.创建一个实现Callable接口的实现类
class NumThread implements Callable {
//2.实现call()方法,将此线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
if (i % 2 == 0) {
sum += i;
System.out.println(i);
}
}
return sum;
}
}
public class ThreadNew {
public static void main(String[] args) {
// 3.创建一个Callable接口实现类的对象
NumThread numThread = new NumThread();
// 4.将此callable实现类的对象传递到FutureTask构造器中,创建FutureTas的对象
FutureTask futureTask = new FutureTask(numThread);
// 5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread类的对象,调用start()方法
new Thread(futureTask).start();
try {
// 6.获取Callable中call的方法作为返回值
//get()的返回值,即为FutureTask构造器参数Callable实现重写的call的返回值
Object sum = futureTask.get();
System.out.println("总和为:"+sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
6. 创建线程的方式四:使用线程池
好处:
- 1.提高响应速度(减少了创建新线程的时间)
- 2.减低资源消耗(重复利用线程池中的线程,不需要每次都创建)
- 3.便于线程管理
- corePoolsize:核心池的大小
- maximumPoolSize:最大线程数
- keepAliveTime:线程没有大小时最多保持多长时间后终止
class NumberThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//设置线程池的属性
System.out.println(service.getClass());
//2.执行指定的线程操作/需要提供Runnable接口或Callable接口实现类的对象
// service.submit(Callable callable)//提交,适用于Callable接口
service.execute(new NumberThread());//适用于Runnable接口
//3.关闭线程池
service.shutdown();
}
}