目录
1.程序、进程、线程
2.进程与线程的内存解析
3.并发与并行
4.Java程序的线程
5.多线程的创建:方式一:继承于Thread类
6.start方法的作用
7.使用start()时要注意的问题
8.Thread类中的一些方法
9.线程的优先级
10.多线程的创建:方式二:实现Runnable
11.比较创建线程的两种方式
1.开发中,优先选择:实现Runnable接口方式
2.二者的联系
12.线程的生命周期
13.线程的同步:解决线程安全问题
14.同步的方式:方式一:同步代码块
15.同步的方式二:同步方法:
16.同步方法的总结
17.同步机制的优缺点
18.单例模式的懒汉式线程安全
19.死锁
20.线程同步的方法三:Lock锁
21.面试题:synchronized和lock的异同
22.优先使用的同步方法顺序:
23.面试题:解决线程安全的方式
24.线程的通信(应用:生产者消费者问题)
25.说明:wait、notify、notifyall的注意事项
26.面试题:sleep()和wait()的异同?
27.线程创建方式三:实现Callable接口
28.为什么Callable接口比实现Runnable接口创建多线程更强大
29.线程创建方式四:使用线程池方式
30.线程池的好处
31.面试题:创建多线程有哪几种方式?
1.程序、进程、线程
1.程序(program):完成特定任务、用某种语言编写的一组指令的集合。
2.进程(process):正在运行的一个程序。进程是资源分配的单位。
3.线程(thread):一个程序内部的一条执行路径。线程是调度和执行的单位,每个线程拥有独立的运行栈和程序计数器。
2.进程与线程的内存解析
1.一个进程公用一个堆和方法区,每个线程独立的拥有一个程序计数器和虚拟机栈。
2.因此一个进程中的多个线程可以共享堆中的对象,使线程通信更简单高效,但也因此带来安全隐患。
3.并发与并行
并行:多个CPU可以执行多个任务
并发:一个CPU(采用时间片)同时执行多个任务。
4.Java程序的线程
一个java程序java.exe,至少三个线程:main()主线程,gc()垃圾回收线程,异常处理线程。
5.多线程的创建:方式一:继承于Thread类
1.创建一个继承于thread类的子类。
2.重写thread类的run()
3.创建thread类的子类的对象
4.通过此对象调用start()
public class MyThreadTest {
public static void main(String[] args) {
//3.创建Thread类的子类的对象
Thread1 thread1 = new Thread1();
//4.通过该对象调用start()方法
thread1.start();
}
}
//1.创建一个继承了Thread类的子类
class Thread1 extends Thread {
//2.重写Thread类的run()方法
@Override
public void run() {
System.out.println("线程实现方式一:继承Thread类方式");
}
}
6.start方法的作用
启动当前线程;调用当前线程的run()。
7.使用start()时要注意的问题
问题一:我们不能通过直接调用run()的方式启动线程,这样相当于在main中启动。
问题二:不能够让已经start()的线程在执行一次start,会报IllegalThreadException。只能重新创建一个线程对象来start。
8.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):让当前线程睡眠指定的millitime毫秒。在指定的millitime毫秒时间内,当前线程是阻塞状态。
10.isAlive():判断当前线程是否存活。
9.线程的优先级
1.MAX_PRIORITY :最大优先级:10
2.MIN_PRIORITY :最小优先级:1
3.NORM_PRIORITY :5;默认优先级
4.如何获取和设置当前线程优先级
getPriority():获取线程的优先级
setPriority(int p):设置当前线程的优先级
5.高优先级的线程抢占低优先级线程CPU的执行权。但只是从概率上讲,高优先级的线程高概率的情况下被执行,并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行。
10.多线程的创建:方式二:实现Runnable
1.创建一个实现了Runnable接口的类
2.实现类去实现Runnable中的抽象方法run
3.创建实现类的对象
4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
5.通过Thread类的对象调用start()
public class MyThreadTest {
public static void main(String[] args) {
//3.创建该实现类的对象
Thread2 thread2 = new Thread2();
//4.将该对象作为参数传递到Thread类的构造器中,创建Thread类的对象
Thread thread3 = new Thread(thread2);
//5.通过Thread类的对象调用start()方法
thread3.start();
}
}
//1.创建一个实现了Runnable接口的实现类
class Thread2 implements Runnable{
//2.重写run()方法
@Override
public void run() {
System.out.println("线程实现方式二:实现Runnable接口方式");
}
}
11.比较创建线程的两种方式
1.开发中,优先选择:实现Runnable接口方式
原因:
1.实现的方式没有类的单继承性的局限性。
2.实现的方式更适合来处理多个线程有共享数据的情况。
2.二者的联系
联系:Thread类也继承了Runnable
相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。
两种方式想要启动线程,都是要调用Thread类中的start()。
12.线程的生命周期
13.线程的同步:解决线程安全问题
1.线程的安全问题:冲票、错票问题
2.问题出现的原因:某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来也进行该操作引起的。(使用了共享数据)
3.如何解决:当一个线程在操作共享数据时,其他线程无法参与,直到该线程操作完毕其他线程才可以参与。即使该线程出现阻塞也不能改变。
4.java中通过同步机制来解决线程安全问题
5.共享数据:多个线程共同操作的变量
14.同步的方式:方式一:同步代码块
1.同步代码块:实现Runnable的形式
synchronized(同步监视器){需要被同步代码}
2.说明:操作共享数据的代码,即为需要被同步的代码。
3.同步监视器,俗称锁;任何类的对象都可以充当锁;
4.要求:多个线程必须共用同一把锁。
5.在实现Runnable接口创建多线程的方式中,可以考虑使用this作为锁;
6.在继承Thread类创建多线程的方式中,慎用this充当锁,可以考虑使用当前类充当同步监视器
class Window1 implements Runnable{
private int ticket = 100;
Object obj = new Object();
@Override
public void run() {
while(true){
//使用类的对象充当同步监视器
// synchronized(obj){
//使用当前类充当同步监视器
// synchronized(Window1.class){
//使用this充当同步监视器
synchronized(this){
if (ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":购票成功,票号为:" + ticket);
ticket--;
}else{
break;
}
}
}
}
}
7.注意:synchronized不能包多了也不能包少了,包多了会变成单线程执行,包少了就没有实现线程安全。
15.同步的方式二:同步方法:
1.如果操作共享数据的代码完整的声明在一个方法中,我们可以将此方法声明为同步
2.需要将共享数据和同步方法写成静态形式
class Window2 implements Runnable{
private static int ticket = 100;
@Override
public void run() {
while(true){
getTicket();
}
}
private static synchronized void getTicket(){//默认是一个this
if (ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":购票成功,票号为:" + ticket);
ticket--;
}
}
16.同步方法的总结
1.同步方法虽然仍然涉及到同步监视器,只是不需要我们显式的声明。
2.非静态的同步方法,同步监视器是this
静态的同步方法,同步监视器是:当前类本身
17.同步机制的优缺点
好处:同步的方式,解决了线程的安全问题;
局限性:操作同步代码时只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率会变低。
18.单例模式的懒汉式线程安全
public class Lan {
private Lan(){};
private static Lan lan = null;
public static Lan getLan(){
synchronized(Lan.class) {
if (lan == null) {
lan = new Lan();
}
}
return lan;
}
}
19.死锁
1.死锁概念:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,形成了线程的死锁。
2.说明:所有线程都处于阻塞状态,但不会出现异常。平时要避免出现死锁。
20.线程同步的方法三:Lock锁
1.先使用reentrant lock实例化
2.在try中调用锁定方法:lock()
3.在finally中调用解锁方法:unlock()
class Window implements Runnable{
private int ticket = 100;
ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while(true){
try {
lock.lock();
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":购票成功,票号为:" + ticket);
ticket--;
} else {
break;
}
}finally{
lock.unlock();
}
}
}
}
21.面试题:synchronized和lock的异同
相同点:都是用来解决线程安全问题的;
不同点:
synchronized:在执行完相应的同步代码后,自动地释放同步监视器。
lock:手动的启动同步(lock()),同时结束时也需要手动的实现(unlock())。
22.优先使用的同步方法顺序:
1.lock
2.同步代码块(已经进入了方法体,分配了相应资源)
3.同步方法(将整个方法全置为单线程)
23.面试题:解决线程安全的方式
同步代码块,同步方法,lock锁
24.线程的通信(应用:生产者消费者问题)
1.调用wait()方法的线程进入阻塞状态,一旦wait就会释放所有同步监视器
2.使用notify()和notifyall()唤醒wait的线程
3.notify()唤醒一个被wait的线程,若有多个线程被wait,则唤醒优先级高的那个
4.使用notifyall会唤醒所有被wait的线程
25.说明:wait、notify、notifyall的注意事项
1.wait、notify、notifyall三个方法必须使用在同步代码块或同步方法中。
2.wait、notify、notifyall的调用者(要么全是this,要么全是自定义的任意对象)必须是同步代码块或者同步方法中的同步监视器,否则会出现异常。
3.wait、notify、notifyall三个方法是调用在object类中的。
26.面试题:sleep()和wait()的异同?
相同点:都会使得当前线程进入阻塞状态;
不同点:
1.sleep到时间后会主动进入就绪,wait方法必须调用notify唤醒
2.方法声明的位置不同:thread类中声明sleep(),object类中声明wait()
3.调用的要求不同:sleep()方法可以在任何需要的场景下调用,wait()必须使用在同步代码块或同步方法中
4.是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会主动释放锁。
27.线程创建方式三:实现Callable接口
1.创建一个实现Callable的实现类
2.实现call()方法,将此线程需要执行的方法声明在call()中
3.创建callable实现类的对象
4.将此实现类对象作为参数传递到FutureTask构造器中,创建FutureTask对象
5.将FutureTask对象作为参数传递到Thread类中调用start()
6.可以获取Callable中call方法的返回值
class CallableTest implements Callable {
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100; i++) {
if (i % 2 == 0){
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + ":" + i);
sum += i;
}
}
return sum;
}
}
public class Demo1 {
public static void main(String[] args) {
CallableTest c = new CallableTest();
FutureTask f = new FutureTask(c);
Thread t1 = new Thread(f);
Thread t2 = new Thread(f);
t1.setName("线程1");
t2.setName("线程2");
t2.start();
t1.start();
try {
Object sum = f.get();
System.out.println(sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
28.为什么Callable接口比实现Runnable接口创建多线程更强大
1.call()可以有返回值
2.call()可以抛出异常,被外面的操作捕获,获取异常的信息
3.Callable是支持泛型的
29.线程创建方式四:使用线程池方式
1.提供指定线程数量的线程池
2.执行指定的线程的操作;需要提供实现Runnable接口或Callable接口实现类的对象
3.关闭连接池
class A implements Runnable {
@Override
public void run() {
System.out.println("a");
}
}
public class Test {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(10);
service.execute(new A());
service.shutdown();
String s = "abc";
}
}
30.线程池的好处
1.提高响应速度(减少了创建新线程的时间)
2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
3.便于线程管理。
31.面试题:创建多线程有哪几种方式?
继承Thread类、实现Runnable接口、实现Callable接口、线程池